Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions MANIFEST
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ t/error_queue.t
t/fakelib/Crypt/OpenSSL/Bignum.pm
t/format.t
t/key_lifecycle.t
t/keygen.t
t/padding.t
t/private_crypt.t
t/pss_auto_promote.t
Expand Down
163 changes: 163 additions & 0 deletions t/keygen.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
use strict;
use Test::More;

use Crypt::OpenSSL::Random;
use Crypt::OpenSSL::RSA;

# Tests for generate_key() with non-default exponents and edge cases.
# The default exponent (65537) is well-tested elsewhere; this file
# exercises the explicit exponent parameter and verifies generated keys
# are fully functional.

Crypt::OpenSSL::Random::random_seed("OpenSSL needs at least 32 bytes.");
Crypt::OpenSSL::RSA->import_random_seed();

my $HAS_BIGNUM = eval { require Crypt::OpenSSL::Bignum; 1 } ? 1 : 0;

# Use 2048-bit keys throughout — AlmaLinux 9 and other FIPS-like systems
# enforce a minimum RSA key size of 2048 bits.
my $BITS = 2048;
my $BYTES = $BITS / 8;

plan tests => 24;

# --- Default exponent (65537) explicitly passed ---
{
my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS, 65537);
ok($rsa, "generate_key with explicit default exponent 65537");
is($rsa->size(), $BYTES, "key size is $BYTES bytes ($BITS bits)");
ok($rsa->is_private(), "generated key is private");
ok($rsa->check_key(), "key passes check_key");

SKIP: {
skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM;
my (undef, $e) = $rsa->get_key_parameters();
is($e->to_decimal(), "65537", "exponent is 65537");
}
}

# --- Small valid exponent: 3 ---
{
my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 3) };
SKIP: {
skip "OpenSSL rejected exponent 3: $@", 5 if $@;
ok($rsa, "generate_key with exponent 3");
ok($rsa->check_key(), "e=3 key passes check_key");

# Verify e=3 key can sign/verify
$rsa->use_sha256_hash();
$rsa->use_pkcs1_pss_padding();
my $sig = $rsa->sign("test message");
ok($sig, "e=3 key can sign");
ok($rsa->verify("test message", $sig), "e=3 key signature verifies");

SKIP: {
skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM;
my (undef, $e) = $rsa->get_key_parameters();
is($e->to_decimal(), "3", "exponent is 3");
}
}
}

# --- Valid exponent: 17 ---
{
my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 17) };
SKIP: {
skip "OpenSSL rejected exponent 17: $@", 4 if $@;
ok($rsa, "generate_key with exponent 17");
ok($rsa->check_key(), "e=17 key passes check_key");

# Verify encrypt/decrypt works
$rsa->use_pkcs1_oaep_padding();
my $ct = $rsa->encrypt("hello");
my $pt = $rsa->decrypt($ct);
is($pt, "hello", "e=17 key encrypt/decrypt round-trip");

SKIP: {
skip "Crypt::OpenSSL::Bignum not available", 1 unless $HAS_BIGNUM;
my (undef, $e) = $rsa->get_key_parameters();
is($e->to_decimal(), "17", "exponent is 17");
}
}
}

# --- Valid exponent: 257 ---
{
my $rsa = eval { Crypt::OpenSSL::RSA->generate_key($BITS, 257) };
SKIP: {
skip "OpenSSL rejected exponent 257: $@", 2 if $@;
ok($rsa, "generate_key with exponent 257");
ok($rsa->check_key(), "e=257 key passes check_key");
}
}

# --- Invalid exponent: even number (2) ---
# OpenSSL 1.1.x may loop forever on invalid exponents instead of
# rejecting them, so we use alarm() to avoid hanging CI.
{
my $rsa = eval {
local $SIG{ALRM} = sub { die "timeout\n" };
alarm(10);
my $r = Crypt::OpenSSL::RSA->generate_key($BITS, 2);
alarm(0);
$r;
};
alarm(0);
ok(!$rsa || $@, "exponent 2 (even) is rejected or times out");
}

# --- Invalid exponent: 1 ---
{
my $rsa = eval {
local $SIG{ALRM} = sub { die "timeout\n" };
alarm(10);
my $r = Crypt::OpenSSL::RSA->generate_key($BITS, 1);
alarm(0);
$r;
};
alarm(0);
ok(!$rsa || $@, "exponent 1 is rejected or times out");
}

# --- Key reuse after error ---
# Generate a key, trigger an eval-caught error, then use the key again.
# Validates the key object isn't corrupted by a caught failure.
{
my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS);
$rsa->use_pkcs1_oaep_padding();

# Trigger an error: plaintext too long for OAEP
my $too_long = "x" x ($rsa->size());
eval { $rsa->encrypt($too_long) };
ok($@, "encrypt with oversized plaintext fails as expected");

# Key should still work for a valid operation
my $ct = eval { $rsa->encrypt("works after error") };
ok(!$@, "key is reusable after caught encrypt error") or diag $@;
my $pt = eval { $rsa->decrypt($ct) };
is($pt, "works after error", "decrypt succeeds after prior error");
}

# --- Hash mode switching ---
# Verify that changing hash modes on a key object works correctly.
{
my $rsa = Crypt::OpenSSL::RSA->generate_key($BITS);
$rsa->use_pkcs1_pss_padding();
my $msg = "hash switching test";

$rsa->use_sha256_hash();
my $sig256 = $rsa->sign($msg);
ok($rsa->verify($msg, $sig256), "SHA256 sign/verify after mode set");

# SHA1 signing may be disabled on FIPS-like systems (e.g. almalinux:9)
SKIP: {
$rsa->use_sha1_hash();
my $sig1 = eval { $rsa->sign($msg) };
skip "SHA1 signing not available: $@", 2 if $@;
ok($rsa->verify($msg, $sig1), "SHA1 sign/verify after switching from SHA256");

# SHA256 signature should NOT verify under SHA1
my $result = eval { $rsa->verify($msg, $sig256) };
ok(!$result, "SHA256 signature fails under SHA1 mode");
}
}
Loading