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
22 changes: 20 additions & 2 deletions RSA.pm
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,13 @@ sub new_public_key {
}

sub new_key_from_parameters {
my ( $proto, $n, $e, $d, $p, $q ) = @_;
return $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q );
my ( $proto, $n, $e, $d, $p, $q, %opts ) = @_;
my $rsa = $proto->_new_key_from_parameters( map { $_ ? $_->pointer_copy() : 0 } $n, $e, $d, $p, $q );
if ( $opts{check} && $rsa && $rsa->is_private() ) {
$rsa->check_key()
or croak("RSA key check failed: inconsistent key parameters");
}
return $rsa;
}

sub import_random_seed {
Expand Down Expand Up @@ -153,6 +158,19 @@ provided and d is undef, d is computed. Note that while p and q are
not necessary for a private key, their presence will speed up
computation.

An optional C<check =E<gt> 1> parameter can be passed after the key
components to validate the key immediately after construction:

my $rsa = Crypt::OpenSSL::RSA->new_key_from_parameters(
$n, $e, $d, $p, $q, check => 1
);

When enabled, C<check_key()> is called on the resulting key. If the
key parameters are inconsistent (e.g. wrong CRT values, mismatched
n/e/d/p/q), the constructor will croak instead of returning an object
that fails at first use. The check is only performed on private keys;
public-only keys (n and e only) are returned without validation.

=item import_random_seed

Import a random seed from L<Crypt::OpenSSL::Random>, since the OpenSSL
Expand Down
62 changes: 62 additions & 0 deletions t/check_param.t
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
use strict;
use Test::More;

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

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

my $HAS_BIGNUM = $INC{'Crypt/OpenSSL/Bignum.pm'} ? 1 : 0;

$HAS_BIGNUM
? plan( tests => 7 )
: plan( skip_all => "Crypt::OpenSSL::Bignum required for check_param tests" );

my $rsa = Crypt::OpenSSL::RSA->generate_key(2048);
my ( $n, $e, $d, $p, $q ) = $rsa->get_key_parameters();

# 1. check=>1 with valid full params — should succeed
{
my $key = eval {
Crypt::OpenSSL::RSA->new_key_from_parameters(
$n, $e, $d, $p, $q, check => 1
);
};
ok( !$@, "check=>1 with valid params does not croak" )
or diag("Error: $@");
ok( $key && $key->is_private(), "check=>1 returns valid private key" );
}

# 2. check=>1 with public-only key — should succeed (check skipped)
{
my $key = eval {
Crypt::OpenSSL::RSA->new_key_from_parameters(
$n, $e, undef, undef, undef, check => 1
);
};
ok( !$@, "check=>1 with public-only key does not croak" )
or diag("Error: $@");
ok( $key && !$key->is_private(), "check=>1 returns public key without validation" );
}

# 3. check=>1 with bad d — should croak
{
my $bad_d = Crypt::OpenSSL::Bignum->new_from_word(12345);
eval {
Crypt::OpenSSL::RSA->new_key_from_parameters(
$n, $e, $bad_d, $p, $q, check => 1
);
};
ok( $@, "check=>1 with bad d croaks" );
like( $@, qr/(?:check failed|OpenSSL error)/i,
"error message mentions check failure or OpenSSL error" );
}

# 4. Without check option — no extra validation at Perl level
{
my $key = eval {
Crypt::OpenSSL::RSA->new_key_from_parameters( $n, $e, $d, $p, $q );
};
ok( !$@, "without check option, valid params succeed as before" );
}
Loading