diff --git a/RSA.xs b/RSA.xs index 238bebb..92cdb93 100644 --- a/RSA.xs +++ b/RSA.xs @@ -67,6 +67,29 @@ static RSA* _load_pkcs8_der_key(BIO* bio, const char* passphrase) EVP_PKEY_free(pkey); return rsa; } + +/* Pre-3.x helper for loading unencrypted PKCS#8 DER private keys. + d2i_RSAPrivateKey_bio only handles PKCS#1 format; unencrypted + PKCS#8 (PrivateKeyInfo) requires d2i_PKCS8_PRIV_KEY_INFO_bio. */ +static RSA* _load_pkcs8_unenc_der_key(BIO* bio) +{ + PKCS8_PRIV_KEY_INFO *p8inf; + EVP_PKEY* pkey; + RSA* rsa; + + p8inf = d2i_PKCS8_PRIV_KEY_INFO_bio(bio, NULL); + if (!p8inf) + return NULL; + + pkey = EVP_PKCS82PKEY(p8inf); + PKCS8_PRIV_KEY_INFO_free(p8inf); + if (!pkey) + return NULL; + + rsa = EVP_PKEY_get1_RSA(pkey); + EVP_PKEY_free(pkey); + return rsa; +} #endif #if OPENSSL_VERSION_NUMBER >= 0x30000000L @@ -695,6 +718,13 @@ _new_private_key_der(proto, key_string_SV, passphrase_SV=&PL_sv_undef) pkey = _load_pkcs8_der_key(bio, passphrase); } else { pkey = d2i_RSAPrivateKey_bio(bio, NULL); + if (!pkey) { + ERR_clear_error(); + BIO_free(bio); + bio = BIO_new_mem_buf(keyString, keyStringLength); + if (bio) + pkey = _load_pkcs8_unenc_der_key(bio); + } } #endif BIO_free(bio); diff --git a/t/der.t b/t/der.t index 914293f..7abc323 100644 --- a/t/der.t +++ b/t/der.t @@ -6,7 +6,7 @@ use Crypt::OpenSSL::RSA; use File::Temp qw(tempfile); -BEGIN { plan tests => 30 } +BEGIN { plan tests => 35 } # --- Generate a key pair for testing --- @@ -125,6 +125,32 @@ eval { Crypt::OpenSSL::RSA->new_public_key("") }; like( $@, qr/unrecognized key format/, "new_public_key gives helpful error on empty string" ); +# --- Unencrypted PKCS#8 DER private key --- +# get_private_key_pkcs8_string() without passphrase produces unencrypted PKCS#8 PEM. +# Converting to DER gives PrivateKeyInfo (not EncryptedPrivateKeyInfo). +# On pre-3.x, this requires d2i_PKCS8_PRIV_KEY_INFO_bio, not d2i_RSAPrivateKey_bio. + +my $pkcs8_pem = $rsa->get_private_key_pkcs8_string(); +my $pkcs8_der = pem_to_der($pkcs8_pem); + +is( ord(substr($pkcs8_der, 0, 1)), 0x30, + "Unencrypted PKCS#8 DER starts with SEQUENCE tag" ); + +my $priv_from_pkcs8_der; +ok( $priv_from_pkcs8_der = Crypt::OpenSSL::RSA->new_private_key($pkcs8_der), + "new_private_key loads unencrypted PKCS#8 DER" ); + +ok( $priv_from_pkcs8_der->is_private(), + "Unencrypted PKCS#8 DER-loaded key is private" ); + +is( $priv_from_pkcs8_der->get_public_key_x509_string(), $x509_pem, + "Unencrypted PKCS#8 DER key exports same public key as original" ); + +$priv_from_pkcs8_der->use_sha256_hash(); +my $sig_pkcs8 = $priv_from_pkcs8_der->sign($plaintext); +ok( $pub_from_x509_der->verify($plaintext, $sig_pkcs8), + "Signature from unencrypted PKCS#8 DER-loaded key verifies" ); + # --- Encrypted PKCS#8 DER private key with passphrase --- my $passphrase = 'test_der_pass';