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
11 changes: 7 additions & 4 deletions RSA.pm
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ sub new_private_key {
}
elsif ( substr($p_key_string, 0, 1) eq "\x30" ) {
# ASN.1 SEQUENCE tag detected — likely DER-encoded private key.
return $proto->_new_private_key_der($p_key_string);
return $proto->_new_private_key_der($p_key_string, @rest);
}
else {
croak "unrecognized key format: expected PEM-encoded key (starting with '-----BEGIN') "
Expand Down Expand Up @@ -193,15 +193,18 @@ be changed with C<use_xxx_padding>.

DER-encoded keys (raw binary ASN.1) are also accepted.

An optional parameter can be passed for passphrase-protected PEM private
An optional parameter can be passed for passphrase-protected private
keys:

=over

=item passphrase

The passphrase which protects the private key. Note: passphrase
protection is only supported for PEM-encoded keys.
The passphrase which protects the private key. For PEM keys, this
decrypts traditional encrypted PEM (C<DEK-Info> header) and encrypted
PKCS#8 PEM (C<BEGIN ENCRYPTED PRIVATE KEY>). For DER keys, this
decrypts encrypted PKCS#8 DER (C<EncryptedPrivateKeyInfo> ASN.1
structure).

=back

Expand Down
39 changes: 37 additions & 2 deletions RSA.xs
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,26 @@ static int _write_pkcs8_pem(BIO* bio, RSA* rsa, const EVP_CIPHER* enc,
}
#endif

/* Pre-3.x helper for loading encrypted PKCS#8 DER private keys.
Placed BEFORE the EVP_PKEY->RSA compatibility macros so that
EVP_PKEY, EVP_PKEY_free, and EVP_PKEY_get1_RSA resolve to their
real OpenSSL symbols. */
#if OPENSSL_VERSION_NUMBER < 0x30000000L
static RSA* _load_pkcs8_der_key(BIO* bio, const char* passphrase)
{
EVP_PKEY* pkey;
RSA* rsa;

pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, NULL, (void*)passphrase);
if (!pkey)
return NULL;

rsa = EVP_PKEY_get1_RSA(pkey);
EVP_PKEY_free(pkey);
return rsa;
}
#endif

#if OPENSSL_VERSION_NUMBER >= 0x30000000L
#define UNSIGNED_CHAR unsigned char
#define SIZE_T_INT size_t
Expand Down Expand Up @@ -630,9 +650,10 @@ _new_public_key_pkcs1_der(proto, key_string_SV)
RETVAL

SV*
_new_private_key_der(proto, key_string_SV)
_new_private_key_der(proto, key_string_SV, passphrase_SV=&PL_sv_undef)
SV* proto;
SV* key_string_SV;
SV* passphrase_SV;
PREINIT:
STRLEN keyStringLength;
char* keyString;
Expand All @@ -653,14 +674,28 @@ _new_private_key_der(proto, key_string_SV)
BIO_free(bio);
croakSsl(__FILE__, __LINE__);
}
if (SvPOK(passphrase_SV)) {
STRLEN passlen;
unsigned char* pass = (unsigned char*)SvPV(passphrase_SV, passlen);
if (!OSSL_DECODER_CTX_set_passphrase(dctx, pass, passlen)) {
OSSL_DECODER_CTX_free(dctx);
BIO_free(bio);
croakSsl(__FILE__, __LINE__);
}
}
if (!OSSL_DECODER_from_bio(dctx, bio)) {
OSSL_DECODER_CTX_free(dctx);
BIO_free(bio);
croakSsl(__FILE__, __LINE__);
}
OSSL_DECODER_CTX_free(dctx);
#else
pkey = d2i_RSAPrivateKey_bio(bio, NULL);
if (SvPOK(passphrase_SV)) {
char* passphrase = SvPV_nolen(passphrase_SV);
pkey = _load_pkcs8_der_key(bio, passphrase);
} else {
pkey = d2i_RSAPrivateKey_bio(bio, NULL);
}
#endif
BIO_free(bio);
CHECK_OPEN_SSL(pkey);
Expand Down
29 changes: 28 additions & 1 deletion t/der.t
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use Crypt::OpenSSL::RSA;

use File::Temp qw(tempfile);

BEGIN { plan tests => 24 }
BEGIN { plan tests => 30 }

# --- Generate a key pair for testing ---

Expand Down Expand Up @@ -125,6 +125,33 @@ eval { Crypt::OpenSSL::RSA->new_public_key("") };
like( $@, qr/unrecognized key format/,
"new_public_key gives helpful error on empty string" );

# --- Encrypted PKCS#8 DER private key with passphrase ---

my $passphrase = 'test_der_pass';
my $enc_pkcs8_pem = $rsa->get_private_key_pkcs8_string($passphrase, 'aes-128-cbc');
my $enc_pkcs8_der = pem_to_der($enc_pkcs8_pem);

is( ord(substr($enc_pkcs8_der, 0, 1)), 0x30,
"Encrypted PKCS#8 DER starts with SEQUENCE tag" );

my $priv_from_enc_der;
ok( $priv_from_enc_der = Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, $passphrase),
"new_private_key loads encrypted PKCS#8 DER with passphrase" );

ok( $priv_from_enc_der->is_private(),
"Encrypted PKCS#8 DER-loaded key is private" );

is( $priv_from_enc_der->get_public_key_x509_string(), $x509_pem,
"Encrypted PKCS#8 DER key exports same public key as original" );

$priv_from_enc_der->use_sha256_hash();
my $sig3 = $priv_from_enc_der->sign($plaintext);
ok( $pub_from_x509_der->verify($plaintext, $sig3),
"Signature from encrypted PKCS#8 DER-loaded key verifies" );

eval { Crypt::OpenSSL::RSA->new_private_key($enc_pkcs8_der, 'wrong_pass') };
ok( $@, "new_private_key croaks on wrong passphrase for encrypted PKCS#8 DER" );

# PEM header for wrong type
eval { Crypt::OpenSSL::RSA->new_public_key("-----BEGIN CERTIFICATE-----\nfoo\n-----END CERTIFICATE-----\n") };
like( $@, qr/unrecognized key format/,
Expand Down
Loading