From 1fd7e7ea23413d9b6d76cd1b4fd0c01642e3d96e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?K=C5=8Dan?= Date: Mon, 16 Mar 2026 05:44:31 -0600 Subject: [PATCH] test: expand key_lifecycle.t with parameter derivation coverage MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 22 tests for previously untested _new_key_from_parameters() paths: - Derive q from n/p (BN_div branch) - Derive p from n/q (BN_div branch) - Derive d from p/q (BN_mod_inverse branch) - Private key from n/e/d only (no CRT params — else branch) - Cross-derived key interop (encrypt/decrypt, sign/verify) - Error case: missing n and e Also fixes test plan: skip() reports skipped tests which count toward the total, so plan must always include all tests (9+14+20+2=45). --- t/key_lifecycle.t | 99 ++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 98 insertions(+), 1 deletion(-) diff --git a/t/key_lifecycle.t b/t/key_lifecycle.t index 21de04d..451062d 100644 --- a/t/key_lifecycle.t +++ b/t/key_lifecycle.t @@ -9,7 +9,9 @@ Crypt::OpenSSL::RSA->import_random_seed(); my $HAS_BIGNUM = $INC{'Crypt/OpenSSL/Bignum.pm'} ? 1 : 0; -plan tests => 9 + ($HAS_BIGNUM ? 14 : 0); +# skip() reports skipped tests which count toward total, so plan must +# always include them regardless of whether Bignum is available. +plan tests => 9 + 14 + 20 + 2; # --- Cross-key operations --- # Sign with key1, verify with key2 — should return false, not croak. @@ -109,3 +111,98 @@ SKIP: { ok( $rsa_pub_params->verify($plaintext, $sig1), "public-only key from params verifies original signature" ); } + +# --- Parameter derivation paths --- +# Tests for deriving missing p or q from n, and constructing keys +# from n/e/d without CRT params. + +SKIP: { + skip "Crypt::OpenSSL::Bignum required for derivation tests", 20 + unless $HAS_BIGNUM; + + my ($n, $e, $d, $p, $q) = $rsa1->get_key_parameters(); + + # --- Derive q from n and p (pass p, omit q) --- + my $rsa_derive_q = eval { + Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, $p); + }; + ok( !$@, "new_key_from_parameters(n,e,d,p) does not croak" ) + or diag "Error: $@"; + ok( $rsa_derive_q, "derive-q key constructed" ); + ok( $rsa_derive_q->is_private(), "derive-q key is private" ); + ok( $rsa_derive_q->check_key(), "derive-q key passes check_key()" ); + + # Verify the derived key can sign and the original can verify + $rsa_derive_q->use_pkcs1_pss_padding(); + my $sig_dq = $rsa_derive_q->sign($plaintext); + ok( $rsa1->verify($plaintext, $sig_dq), + "original verifies signature from derive-q key" ); + + # --- Derive p from n and q (pass q as 5th arg, omit p) --- + my $rsa_derive_p = eval { + Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d, undef, $q); + }; + ok( !$@, "new_key_from_parameters(n,e,d,undef,q) does not croak" ) + or diag "Error: $@"; + ok( $rsa_derive_p, "derive-p key constructed" ); + ok( $rsa_derive_p->is_private(), "derive-p key is private" ); + ok( $rsa_derive_p->check_key(), "derive-p key passes check_key()" ); + + # Verify the derived key can sign and the original can verify + $rsa_derive_p->use_pkcs1_pss_padding(); + my $sig_dp = $rsa_derive_p->sign($plaintext); + ok( $rsa1->verify($plaintext, $sig_dp), + "original verifies signature from derive-p key" ); + + # --- Private key from n, e, d only (no p, q — the "else" branch) --- + my $rsa_ned = eval { + Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, $d); + }; + ok( !$@, "new_key_from_parameters(n,e,d) does not croak" ) + or diag "Error: $@"; + ok( $rsa_ned, "n/e/d-only key constructed" ); + ok( $rsa_ned->is_private(), "n/e/d-only key is private" ); + is( $rsa_ned->size(), $rsa1->size(), "n/e/d-only key has correct size" ); + + # n/e/d key can encrypt/decrypt + $rsa_ned->use_pkcs1_oaep_padding(); + $rsa1->use_pkcs1_oaep_padding(); + my $ct = $rsa_ned->encrypt("round-trip test"); + my $pt = $rsa1->decrypt($ct); + is( $pt, "round-trip test", + "original decrypts ciphertext from n/e/d-only key" ); + + # Cross-derived key interop: derive-q encrypts, derive-p decrypts + $rsa_derive_q->use_pkcs1_oaep_padding(); + $rsa_derive_p->use_pkcs1_oaep_padding(); + $ct = $rsa_derive_q->encrypt("cross-derive test"); + $pt = $rsa_derive_p->decrypt($ct); + is( $pt, "cross-derive test", + "derive-p decrypts ciphertext from derive-q key" ); + + # Derive-p signs, derive-q verifies + $rsa_derive_p->use_pkcs1_pss_padding(); + $rsa_derive_q->use_pkcs1_pss_padding(); + my $sig_cross = $rsa_derive_p->sign("interop"); + ok( $rsa_derive_q->verify("interop", $sig_cross), + "derive-q verifies signature from derive-p key" ); + + # Derive d from p and q (pass p, q but omit d) + my $rsa_derive_d = eval { + Crypt::OpenSSL::RSA->new_key_from_parameters($n, $e, undef, $p, $q); + }; + ok( !$@, "new_key_from_parameters(n,e,undef,p,q) does not croak" ) + or diag "Error: $@"; + ok( $rsa_derive_d, "derive-d key constructed" ); + ok( $rsa_derive_d->check_key(), "derive-d key passes check_key()" ); +} + +# --- Error cases (no Bignum needed) --- + +eval { Crypt::OpenSSL::RSA->_new_key_from_parameters(0, 0, 0, 0, 0) }; +like( $@, qr/modulus and public key must be provided/, + "croak when both n and e are NULL" ); + +eval { Crypt::OpenSSL::RSA->_new_key_from_parameters(0, 0, 0, 0, 0) }; +like( $@, qr/modulus and public key must be provided/, + "croak when n=0 and e=0 (missing required params)" );