Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Export keys in PKCS8 format #297

Merged
merged 2 commits into from
Nov 25, 2019
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
3 changes: 3 additions & 0 deletions History.md
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ Version 2.2.0 (not yet released)
[[GitHub #185]](https://github.com/ruby/openssl/pull/185)
* Allow recipient's certificate to be omitted in PCKS7#decrypt.
[[GitHub #183]](https://github.com/ruby/openssl/pull/183)
* Add instance methods for exporting public and private keys in PKCS8 format
to `OpenSSL::PKey` classes: `private_to_der`, `private_to_pem`,
`public_to_der` and `public_to_pem`.

Version 2.1.2
=============
Expand Down
152 changes: 140 additions & 12 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -167,21 +167,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, NULL, NULL)))
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 @@ -293,6 +299,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);
rb_scan_args(argc, argv, "02", &cipher, &pass);
if (argc > 0) {
/*
* TODO: EncryptedPrivateKeyInfo actually has more options.
* Should they be exposed?
*/
enc = ossl_evp_get_cipherbyname(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 @@ -491,6 +615,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 @@ -356,6 +356,85 @@ def test_pem_passwd
}
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 = Fixtures.pkey("rsa1024")
key2 = key.dup
Expand Down