Skip to content

Commit

Permalink
Add support for raw private/public keys (#646)
Browse files Browse the repository at this point in the history
Add OpenSSL::PKey.new_raw_private_key, #raw_private_key and public
equivalents. These methods are useful for importing and exporting keys
that support "raw private/public key". Currently, OpenSSL implements
X25519/X448 and Ed25519/Ed448 keys.

[rhe: rewrote commit message]

Co-authored-by: Bart de Water <bartdewater@gmail.com>
  • Loading branch information
sylph01 and bdewater authored Jul 12, 2023
1 parent 97fb410 commit 3f29525
Show file tree
Hide file tree
Showing 2 changed files with 171 additions and 0 deletions.
132 changes: 132 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,72 @@ ossl_pkey_initialize_copy(VALUE self, VALUE other)
}
#endif

#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
/*
* call-seq:
* OpenSSL::PKey.new_raw_private_key(algo, string) -> PKey
*
* See the OpenSSL documentation for EVP_PKEY_new_raw_private_key()
*/

static VALUE
ossl_pkey_new_raw_private_key(VALUE self, VALUE type, VALUE key)
{
EVP_PKEY *pkey;
const EVP_PKEY_ASN1_METHOD *ameth;
int pkey_id;
size_t keylen;

StringValue(type);
StringValue(key);
ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type));
if (!ameth)
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type);
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);

keylen = RSTRING_LEN(key);

pkey = EVP_PKEY_new_raw_private_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen);
if (!pkey)
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_private_key");

return ossl_pkey_new(pkey);
}
#endif

#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
/*
* call-seq:
* OpenSSL::PKey.new_raw_public_key(algo, string) -> PKey
*
* See the OpenSSL documentation for EVP_PKEY_new_raw_public_key()
*/

static VALUE
ossl_pkey_new_raw_public_key(VALUE self, VALUE type, VALUE key)
{
EVP_PKEY *pkey;
const EVP_PKEY_ASN1_METHOD *ameth;
int pkey_id;
size_t keylen;

StringValue(type);
StringValue(key);
ameth = EVP_PKEY_asn1_find_str(NULL, RSTRING_PTR(type), RSTRING_LENINT(type));
if (!ameth)
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", type);
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);

keylen = RSTRING_LEN(key);

pkey = EVP_PKEY_new_raw_public_key(pkey_id, NULL, (unsigned char *)RSTRING_PTR(key), keylen);
if (!pkey)
ossl_raise(ePKeyError, "EVP_PKEY_new_raw_public_key");

return ossl_pkey_new(pkey);
}
#endif

/*
* call-seq:
* pkey.oid -> string
Expand Down Expand Up @@ -816,6 +882,35 @@ ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self)
return do_pkcs8_export(argc, argv, self, 0);
}

#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
/*
* call-seq:
* pkey.raw_private_key => string
*
* See the OpenSSL documentation for EVP_PKEY_get_raw_private_key()
*/

static VALUE
ossl_pkey_raw_private_key(VALUE self)
{
EVP_PKEY *pkey;
VALUE str;
size_t len;

GetPKey(self, pkey);
if (EVP_PKEY_get_raw_private_key(pkey, NULL, &len) != 1)
ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key");
str = rb_str_new(NULL, len);

if (EVP_PKEY_get_raw_private_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1)
ossl_raise(ePKeyError, "EVP_PKEY_get_raw_private_key");

rb_str_set_len(str, len);

return str;
}
#endif

VALUE
ossl_pkey_export_spki(VALUE self, int to_der)
{
Expand Down Expand Up @@ -865,6 +960,35 @@ ossl_pkey_public_to_pem(VALUE self)
return ossl_pkey_export_spki(self, 0);
}

#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
/*
* call-seq:
* pkey.raw_public_key => string
*
* See the OpenSSL documentation for EVP_PKEY_get_raw_public_key()
*/

static VALUE
ossl_pkey_raw_public_key(VALUE self)
{
EVP_PKEY *pkey;
VALUE str;
size_t len;

GetPKey(self, pkey);
if (EVP_PKEY_get_raw_public_key(pkey, NULL, &len) != 1)
ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key");
str = rb_str_new(NULL, len);

if (EVP_PKEY_get_raw_public_key(pkey, (unsigned char *)RSTRING_PTR(str), &len) != 1)
ossl_raise(ePKeyError, "EVP_PKEY_get_raw_public_key");

rb_str_set_len(str, len);

return str;
}
#endif

/*
* call-seq:
* pkey.compare?(another_pkey) -> true | false
Expand Down Expand Up @@ -1602,6 +1726,10 @@ Init_ossl_pkey(void)
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
rb_define_module_function(mPKey, "new_raw_private_key", ossl_pkey_new_raw_private_key, 2);
rb_define_module_function(mPKey, "new_raw_public_key", ossl_pkey_new_raw_public_key, 2);
#endif

rb_define_alloc_func(cPKey, ossl_pkey_alloc);
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);
Expand All @@ -1617,6 +1745,10 @@ Init_ossl_pkey(void)
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);
#ifdef HAVE_EVP_PKEY_NEW_RAW_PRIVATE_KEY
rb_define_method(cPKey, "raw_private_key", ossl_pkey_raw_private_key, 0);
rb_define_method(cPKey, "raw_public_key", ossl_pkey_raw_public_key, 0);
#endif
rb_define_method(cPKey, "compare?", ossl_pkey_compare, 1);

rb_define_method(cPKey, "sign", ossl_pkey_sign, -1);
Expand Down
39 changes: 39 additions & 0 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,19 @@ def test_ed25519
assert_equal pub_pem, priv.public_to_pem
assert_equal pub_pem, pub.public_to_pem

begin
assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
priv.raw_private_key.unpack1("H*")
assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem,
priv.private_to_pem
assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
priv.raw_public_key.unpack1("H*")
assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem,
pub.public_to_pem
rescue NoMethodError
pend "running OpenSSL version does not have raw public key support"
end

sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*")
92a009a9f0d4cab8720e820b5f642540
a2b27b5416503f8fb3762223ebdb69da
Expand Down Expand Up @@ -155,6 +168,32 @@ def test_x25519
assert_equal alice_pem, alice.private_to_pem
assert_equal bob_pem, bob.public_to_pem
assert_equal [shared_secret].pack("H*"), alice.derive(bob)
begin
alice_private = OpenSSL::PKey.new_raw_private_key("X25519", alice.raw_private_key)
bob_public = OpenSSL::PKey.new_raw_public_key("X25519", bob.raw_public_key)
alice_private_raw = alice.raw_private_key.unpack1("H*")
bob_public_raw = bob.raw_public_key.unpack1("H*")
rescue NoMethodError
# OpenSSL < 1.1.1
pend "running OpenSSL version does not have raw public key support"
end
assert_equal alice_private.private_to_pem,
alice.private_to_pem
assert_equal bob_public.public_to_pem,
bob.public_to_pem
assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a",
alice_private_raw
assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f",
bob_public_raw
end

def raw_initialize
pend "Ed25519 is not implemented" unless OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10101000 && # >= v1.1.1

assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("foo123", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_private_key("ED25519", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("foo123", "xxx") }
assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.new_raw_public_key("ED25519", "xxx") }
end

def test_compare?
Expand Down

0 comments on commit 3f29525

Please sign in to comment.