diff --git a/RSA.xs b/RSA.xs index 10b4a4d..95be4b4 100644 --- a/RSA.xs +++ b/RSA.xs @@ -908,14 +908,25 @@ PPCODE: iqmp = rsa->iqmp; #else #if OPENSSL_VERSION_NUMBER >= 0x30000000L - EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_N, &n); - EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_E, &e); + /* n and e are mandatory for every RSA key — croak on failure. */ + if (!EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_N, &n)) + croakSsl(__FILE__, __LINE__); + if (!EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_E, &e)) { + BN_free(n); + croakSsl(__FILE__, __LINE__); + } + /* Private components are absent for public keys — EVP_PKEY_get_bn_param() + returns 0 and may push errors onto the queue, but the pointer stays NULL + so cor_bn2sv() will return undef. This matches the pre-3.x behaviour + where RSA_get0_key/factors/crt_params simply set NULL for missing fields. */ EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_D, &d); EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_FACTOR1, &p); EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_FACTOR2, &q); EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_EXPONENT1, &dmp1); EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_EXPONENT2, &dmq1); EVP_PKEY_get_bn_param(rsa, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, &iqmp); + /* Drain any errors pushed by expected failures on public keys. */ + ERR_clear_error(); #else RSA_get0_key(rsa, &n, &e, &d); RSA_get0_factors(rsa, &p, &q); diff --git a/t/get_key_parameters.t b/t/get_key_parameters.t new file mode 100644 index 0000000..d8ddcca --- /dev/null +++ b/t/get_key_parameters.t @@ -0,0 +1,46 @@ +use strict; +use warnings; +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(); + +plan tests => 10; + +my $rsa_priv = Crypt::OpenSSL::RSA->generate_key(2048); +my $pub_pem = $rsa_priv->get_public_key_string(); +my $rsa_pub = Crypt::OpenSSL::RSA->new_public_key($pub_pem); + +# --- Private key: all 8 parameters must be defined --- + +my @priv_params = eval { $rsa_priv->get_key_parameters() }; +ok( !$@, "get_key_parameters on private key does not croak" ) + or diag "Error: $@"; +is( scalar @priv_params, 8, "get_key_parameters returns 8 values" ); + +my ($n, $e, $d, $p, $q, $dmp1, $dmq1, $iqmp) = @priv_params; +ok( defined $n && defined $e, + "private key: n and e are defined (mandatory public components)" ); +ok( defined $d, + "private key: d is defined (private exponent)" ); +ok( defined $p && defined $q, + "private key: p and q are defined (prime factors)" ); +ok( defined $dmp1 && defined $dmq1 && defined $iqmp, + "private key: CRT parameters are defined" ); + +# --- Public key: n and e defined, private components are undef --- + +my @pub_params = eval { $rsa_pub->get_key_parameters() }; +ok( !$@, "get_key_parameters on public key does not croak" ) + or diag "Error: $@"; +is( scalar @pub_params, 8, "get_key_parameters returns 8 values for public key" ); + +my ($pn, $pe, $pd, $pp, $pq, $pdmp1, $pdmq1, $piqmp) = @pub_params; +ok( defined $pn && defined $pe, + "public key: n and e are defined" ); +ok( !defined $pd && !defined $pp && !defined $pq + && !defined $pdmp1 && !defined $pdmq1 && !defined $piqmp, + "public key: private components (d, p, q, CRT) are undef" );