diff --git a/RSA.pm b/RSA.pm index f7131a4..893cc84 100644 --- a/RSA.pm +++ b/RSA.pm @@ -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 { @@ -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 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 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, since the OpenSSL diff --git a/t/check_param.t b/t/check_param.t new file mode 100644 index 0000000..c612039 --- /dev/null +++ b/t/check_param.t @@ -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" ); +}