Skip to content

Commit

Permalink
pkey: add support for PKCS ruby#8 key serialization
Browse files Browse the repository at this point in the history
OpenSSL::PKey::PKey#private_to_der, #private_to_pem are added to the
generic PKey class that serialize the private key to PKCS ruby#8
{Encrypted,}PrivateKeyInfo format, in DER- and PEM- encoding
respectively. Also add #public_to_der and #public_to_pem for symmetry.
They serializes the public key into X.509 SubjectPublicKeyInfo format.

OpenSSL::PKey.read now reads DER-encoded PKCS ruby#8 keys. PEM-encoded keys
have been already handled by PEM_read_bio_PrivateKey().
  • Loading branch information
rhenium committed Mar 18, 2017
1 parent c2a3e73 commit 27e6ca0
Show file tree
Hide file tree
Showing 2 changed files with 219 additions and 12 deletions.
152 changes: 140 additions & 12 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -151,21 +151,27 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
pass = ossl_pem_passwd_value(pass);

bio = ossl_obj2bio(data);
if (!(pkey = d2i_PrivateKey_bio(bio, NULL))) {
OSSL_BIO_reset(bio);
if (!(pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, (void *)pass))) {
OSSL_BIO_reset(bio);
if (!(pkey = d2i_PUBKEY_bio(bio, NULL))) {
OSSL_BIO_reset(bio);
pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, (void *)pass);
}
}
}
if ((pkey = d2i_PrivateKey_bio(bio, NULL)))
goto ok;
OSSL_BIO_reset(bio);
if ((pkey = d2i_PKCS8PrivateKey_bio(bio, NULL, ossl_pem_passwd_cb, (void *)pass)))
goto ok;
OSSL_BIO_reset(bio);
if ((pkey = d2i_PUBKEY_bio(bio, NULL)))
goto ok;
OSSL_BIO_reset(bio);
/* PEM_read_bio_PrivateKey() also parses PKCS #8 formats */
if ((pkey = PEM_read_bio_PrivateKey(bio, NULL, ossl_pem_passwd_cb, (void *)pass)))
goto ok;
OSSL_BIO_reset(bio);
if ((pkey = PEM_read_bio_PUBKEY(bio, NULL, ossl_pem_passwd_cb, (void *)pass)))
goto ok;

BIO_free(bio);
if (!pkey)
ossl_raise(ePKeyError, "Could not parse PKey");
ossl_raise(ePKeyError, "Could not parse PKey");

ok:
BIO_free(bio);
return ossl_pkey_new(pkey);
}

Expand Down Expand Up @@ -276,6 +282,124 @@ ossl_pkey_initialize(VALUE self)
return self;
}

static VALUE
do_pkcs8_export(int argc, VALUE *argv, VALUE self, int to_der)
{
EVP_PKEY *pkey;
VALUE cipher, pass;
const EVP_CIPHER *enc = NULL;
BIO *bio;

GetPKey(self, pkey);
if (argc != 0) {
/*
* TODO: EncryptedPrivateKeyInfo actually has more options.
* Should they be exposed?
*/
rb_scan_args(argc, argv, "2", &cipher, &pass);
enc = GetCipherPtr(cipher);
pass = ossl_pem_passwd_value(pass);
}

bio = BIO_new(BIO_s_mem());
if (!bio)
ossl_raise(ePKeyError, "BIO_new");
if (to_der) {
if (!i2d_PKCS8PrivateKey_bio(bio, pkey, enc, NULL, 0,
ossl_pem_passwd_cb, (void *)pass)) {
BIO_free(bio);
ossl_raise(ePKeyError, "i2d_PKCS8PrivateKey_bio");
}
}
else {
if (!PEM_write_bio_PKCS8PrivateKey(bio, pkey, enc, NULL, 0,
ossl_pem_passwd_cb, (void *)pass)) {
BIO_free(bio);
ossl_raise(ePKeyError, "PEM_write_bio_PKCS8PrivateKey");
}
}
return ossl_membio2str(bio);
}

/*
* call-seq:
* pkey.private_to_der -> string
* pkey.private_to_der(cipher, password) -> string
*
* Serializes the private key to DER-encoded PKCS #8 format. If called without
* arguments, unencrypted PKCS #8 PrivateKeyInfo format is used. If called with
* a cipher name and a password, PKCS #8 EncryptedPrivateKeyInfo format with
* PBES2 encryption scheme is used.
*/
static VALUE
ossl_pkey_private_to_der(int argc, VALUE *argv, VALUE self)
{
return do_pkcs8_export(argc, argv, self, 1);
}

/*
* call-seq:
* pkey.private_to_pem -> string
* pkey.private_to_pem(cipher, password) -> string
*
* Serializes the private key to PEM-encoded PKCS #8 format. See #private_to_der
* for more details.
*/
static VALUE
ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self)
{
return do_pkcs8_export(argc, argv, self, 0);
}

static VALUE
do_spki_export(VALUE self, int to_der)
{
EVP_PKEY *pkey;
BIO *bio;

GetPKey(self, pkey);
bio = BIO_new(BIO_s_mem());
if (!bio)
ossl_raise(ePKeyError, "BIO_new");
if (to_der) {
if (!i2d_PUBKEY_bio(bio, pkey)) {
BIO_free(bio);
ossl_raise(ePKeyError, "i2d_PUBKEY_bio");
}
}
else {
if (!PEM_write_bio_PUBKEY(bio, pkey)) {
BIO_free(bio);
ossl_raise(ePKeyError, "PEM_write_bio_PUBKEY");
}
}
return ossl_membio2str(bio);
}

/*
* call-seq:
* pkey.public_to_der -> string
*
* Serializes the public key to DER-encoded X.509 SubjectPublicKeyInfo format.
*/
static VALUE
ossl_pkey_public_to_der(VALUE self)
{
return do_spki_export(self, 1);
}

/*
* call-seq:
* pkey.public_to_pem -> string
*
* Serializes the public key to PEM-encoded X.509 SubjectPublicKeyInfo format.
*/
static VALUE
ossl_pkey_public_to_pem(VALUE self)
{
return do_spki_export(self, 0);
}

/*
* call-seq:
* pkey.sign(digest, data) -> String
Expand Down Expand Up @@ -473,6 +597,10 @@ Init_ossl_pkey(void)

rb_define_alloc_func(cPKey, ossl_pkey_alloc);
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);
rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1);
rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1);
rb_define_method(cPKey, "public_to_der", ossl_pkey_public_to_der, 0);
rb_define_method(cPKey, "public_to_pem", ossl_pkey_public_to_pem, 0);

rb_define_method(cPKey, "sign", ossl_pkey_sign, 2);
rb_define_method(cPKey, "verify", ossl_pkey_verify, 3);
Expand Down
79 changes: 79 additions & 0 deletions test/test_pkey_rsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,85 @@ def test_PUBKEY
assert_equal pem, dup_public(rsa1024).export
end

def test_private_encoding
rsa1024 = Fixtures.pkey("rsa1024")
asn1 = OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::Integer(0),
OpenSSL::ASN1::Sequence([
OpenSSL::ASN1::ObjectId("rsaEncryption"),
OpenSSL::ASN1::Null(nil)
]),
OpenSSL::ASN1::OctetString(rsa1024.to_der)
])
assert_equal asn1.to_der, rsa1024.private_to_der
assert_same_rsa rsa1024, OpenSSL::PKey.read(asn1.to_der)

pem = <<~EOF
-----BEGIN PRIVATE KEY-----
MIICeAIBADANBgkqhkiG9w0BAQEFAASCAmIwggJeAgEAAoGBAMvCxLDUQKc+1P4+
Q6AeFwYDvWfALb+cvzlUEadGoPE6qNWHsLFoo8RFgeyTgE8KQTduu1OE9Zz2SMcR
BDu5/1jWtsLPSVrI2ofLLBARUsWanVyki39DeB4u/xkP2mKGjAokPIwOI3oCthSZ
lzO9bj3voxTf6XngTqUX8l8URTmHAgMBAAECgYEApKX8xBqvJ7XI7Kypfo/x8MVC
3rxW+1eQ2aVKIo4a7PKGjQz5RVIVyzqTUvSZoMTbkAxlSIbO5YfJpTnl3tFcOB6y
QMxqQPW/pl6Ni3EmRJdsRM5MsPBRZOfrXxOCdvXu1TWOS1S1TrvEr/TyL9eh2WCd
CGzpWgdO4KHce7vs7pECQQDv6DGoG5lHnvbvj9qSJb9K5ebRJc8S+LI7Uy5JHC0j
zsHTYPSqBXwPVQdGbgCEycnwwKzXzT2QxAQmJBQKun2ZAkEA2W3aeAE7Xi6zo2eG
4Cx4UNMHMIdfBRS7VgoekwybGmcapqV0aBew5kHeWAmxP1WUZ/dgZh2QtM1VuiBA
qUqkHwJBAOJLCRvi/JB8N7z82lTk2i3R8gjyOwNQJv6ilZRMyZ9vFZFHcUE27zCf
Kb+bX03h8WPwupjMdfgpjShU+7qq8nECQQDBrmyc16QVyo40sgTgblyiysitvviy
ovwZsZv4q5MCmvOPnPUrwGbRRb2VONUOMOKpFiBl9lIv7HU//nj7FMVLAkBjUXED
83dA8JcKM+HlioXEAxCzZVVhN+D63QwRwkN08xAPklfqDkcqccWDaZm2hdCtaYlK
funwYkrzI1OikQSs
-----END PRIVATE KEY-----
EOF
assert_equal pem, rsa1024.private_to_pem
assert_same_rsa rsa1024, OpenSSL::PKey.read(pem)
end

def test_private_encoding_encrypted
rsa1024 = Fixtures.pkey("rsa1024")
encoded = rsa1024.private_to_der("aes-128-cbc", "abcdef")
asn1 = OpenSSL::ASN1.decode(encoded) # PKCS #8 EncryptedPrivateKeyInfo
assert_kind_of OpenSSL::ASN1::Sequence, asn1
assert_equal 2, asn1.value.size
assert_not_equal rsa1024.private_to_der, encoded
assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded, "abcdef")
assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded) { "abcdef" }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.read(encoded, "abcxyz") }

encoded = rsa1024.private_to_pem("aes-128-cbc", "abcdef")
assert_match (/BEGIN ENCRYPTED PRIVATE KEY/), encoded.lines[0]
assert_same_rsa rsa1024, OpenSSL::PKey.read(encoded, "abcdef")

# certtool --load-privkey=test/fixtures/pkey/rsa1024.pem --to-p8 --password=abcdef
pem = <<~EOF
-----BEGIN ENCRYPTED PRIVATE KEY-----
MIICojAcBgoqhkiG9w0BDAEDMA4ECLqajUdSNfzwAgIEkQSCAoCDWhxr1HUrKLXA
FsFGGQfPT0aKH4gZipaSXXQRl0KwifHwHoDtfo/mAkJVZMnUVOm1AQ4LTFS3EdTy
JUwICGEQHb7QAiokIRoi0K2yHhOxVO8qgbnWuisWpiT6Ru1jCqTs/wcqlqF7z2jM
oXDk/vuekKst1DDXDcHrzhDkwhCQWj6jt1r2Vwaryy0FyeqsWAgBDiK2LsnCgkGD
21uhNZ/iWMG6tvY9hB8MDdiBJ41YdSG/AKLulAxQ1ibJz0Tasu66TmwFvWhBlME+
QbqfgmkgWg5buu53SvDfCA47zXihclbtdfW+U3CJ9OJkx0535TVdZbuC1QgKXvG7
4iKGFRMWYJqZvZM3GL4xbC75AxjXZsdCfV81VjZxjeU6ung/NRzCuCUcmBOQzo1D
Vv6COwAa6ttQWM0Ti8oIQHdu5Qi+nuOEHDLxCxD962M37H99sEO5cESjmrGVxhEo
373L4+11geGSCajdp0yiAGnXQfwaKta8cL693bRObN+b1Y+vqtDKH26N9a4R3qgg
2XwgQ5GH5CODoXZpi0wxncXO+3YuuhGeArtzKSXLNxHzIMlY7wZX+0e9UU03zfV/
aOe4/q5DpkNxgHePt0oEpamSKY5W3jzVi1dlFWsRjud1p/Grt2zjSWTYClBlJqG1
A/3IeDZCu+acaePJjFyv5dFffIj2l4bAYB+LFrZlSu3F/EimO/dCDWJ9JGlMK0aF
l9brh7786Mo+YfyklaqMMEHBbbR2Es7PR6Gt7lrcIXmzy9XSsxT6IiD1rG9KKR3i
CQxTup6JAx9w1q+adL+Ypikoy3gGD/ccUY6TtPoCmkQwSCS+JqQnFlCiThDJbu+V
eqqUNkZq
-----END ENCRYPTED PRIVATE KEY-----
EOF
assert_same_rsa rsa1024, OpenSSL::PKey.read(pem, "abcdef")
end

def test_public_encoding
rsa1024 = Fixtures.pkey("rsa1024")
assert_equal dup_public(rsa1024).to_der, rsa1024.public_to_der
assert_equal dup_public(rsa1024).to_pem, rsa1024.public_to_pem
end

def test_dup
key = OpenSSL::PKey::RSA.generate(256, 17)
key2 = key.dup
Expand Down

0 comments on commit 27e6ca0

Please sign in to comment.