diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index df8b425a0..3d15fbb06 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -532,6 +532,56 @@ ossl_pkey_initialize(VALUE self) return self; } +/* + * call-seq: + * OpenSSL::PKey.private_new(algo, string) -> PKey + * + * See the OpenSSL documentation for EVP_PKEY_new_raw_private_key() + */ + +static VALUE +ossl_pkey_initialize_private(VALUE self, VALUE type, VALUE key) +{ + EVP_PKEY *pkey; + int nid; + size_t keylen; + + nid = OBJ_sn2nid(StringValueCStr(type)); + if(!nid) ossl_raise(ePKeyError, "unknown OID `%"PRIsVALUE"'", type); + + keylen = RSTRING_LEN(key); + pkey = EVP_PKEY_new_raw_private_key(nid, NULL, (unsigned char *)RSTRING_PTR(key), keylen); + if (!pkey) + ossl_raise(ePKeyError, "Could not parse PKey"); + + return ossl_pkey_new(pkey); +} + +/* + * call-seq: + * OpenSSL::PKey.public_new(algo, string) -> PKey + * + * See the OpenSSL documentation for EVP_PKEY_new_raw_public_key() + */ + +static VALUE +ossl_pkey_initialize_public(VALUE self, VALUE type, VALUE key) +{ + EVP_PKEY *pkey; + int nid; + size_t keylen; + + nid = OBJ_sn2nid(StringValueCStr(type)); + if(!nid) ossl_raise(ePKeyError, "unknown OID `%"PRIsVALUE"'", type); + + keylen = RSTRING_LEN(key); + pkey = EVP_PKEY_new_raw_public_key(nid, NULL, (unsigned char *)RSTRING_PTR(key), keylen); + if (!pkey) + ossl_raise(ePKeyError, "Could not parse PKey"); + + return ossl_pkey_new(pkey); +} + /* * call-seq: * pkey.oid -> string @@ -568,6 +618,25 @@ ossl_pkey_inspect(VALUE self) OBJ_nid2sn(nid)); } +/* + * call-seq: + * key.private? => true or false + * + * Returns whether this PKey instance has a private key. The private key can + * be retrieved with PKey#private_to_{der,pem,raw}. + */ +static VALUE ossl_pkey_is_private(VALUE self) +{ + EVP_PKEY *pkey; + size_t len; + + GetPKey(self, pkey); + len = EVP_PKEY_size(pkey); + unsigned char str[len]; + + return EVP_PKEY_get_raw_private_key(pkey, str, &len) == 1 ? Qtrue : Qfalse; +} + VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { @@ -683,6 +752,30 @@ ossl_pkey_private_to_pem(int argc, VALUE *argv, VALUE self) return do_pkcs8_export(argc, argv, self, 0); } +/* + * call-seq: + * key.private_to_raw => string + * + * See the OpenSSL documentation for EVP_PKEY_get_raw_private_key() + */ +static VALUE ossl_pkey_private_to_raw(VALUE self) +{ + EVP_PKEY *pkey; + VALUE str; + size_t len; + + GetPKey(self, pkey); + EVP_PKEY_get_raw_private_key(pkey, NULL, &len); + 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; +} + VALUE ossl_pkey_export_spki(VALUE self, int to_der) { @@ -708,6 +801,25 @@ ossl_pkey_export_spki(VALUE self, int to_der) return ossl_membio2str(bio); } +/* + * call-seq: + * key.public? => true or false + * + * Returns whether this PKey instance has a public key. The public key can + * be retrieved with PKey#public_to_{der,pem,raw}. + */ +static VALUE ossl_pkey_is_public(VALUE self) +{ + EVP_PKEY *pkey; + size_t len; + + GetPKey(self, pkey); + len = EVP_PKEY_size(pkey); + unsigned char str[len]; + + return EVP_PKEY_get_raw_public_key(pkey, str, &len) == 1 ? Qtrue : Qfalse; +} + /* * call-seq: * pkey.public_to_der -> string @@ -732,6 +844,30 @@ ossl_pkey_public_to_pem(VALUE self) return ossl_pkey_export_spki(self, 0); } +/* + * call-seq: + * key.public_to_raw => string + * + * See the OpenSSL documentation for EVP_PKEY_get_raw_public_key() + */ +static VALUE ossl_pkey_public_to_raw(VALUE self) +{ + EVP_PKEY *pkey; + VALUE str; + size_t len; + + GetPKey(self, pkey); + EVP_PKEY_get_raw_public_key(pkey, NULL, &len); + 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; +} + /* * call-seq: * pkey.sign(digest, data) -> String @@ -1022,15 +1158,21 @@ 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); + rb_define_module_function(mPKey, "private_new", ossl_pkey_initialize_private, 2); + rb_define_module_function(mPKey, "public_new", ossl_pkey_initialize_public, 2); rb_define_alloc_func(cPKey, ossl_pkey_alloc); rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0); rb_define_method(cPKey, "oid", ossl_pkey_oid, 0); rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0); + rb_define_method(cPKey, "private?", ossl_pkey_is_private, 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, "private_to_raw", ossl_pkey_private_to_raw, 0); + rb_define_method(cPKey, "public?", ossl_pkey_is_public, 0); 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, "public_to_raw", ossl_pkey_public_to_raw, 0); rb_define_method(cPKey, "sign", ossl_pkey_sign, 2); rb_define_method(cPKey, "verify", ossl_pkey_verify, 3); diff --git a/lib/openssl/pkey.rb b/lib/openssl/pkey.rb index be60ac2be..201ac7a94 100644 --- a/lib/openssl/pkey.rb +++ b/lib/openssl/pkey.rb @@ -69,6 +69,20 @@ def to_bn(conversion_form = group.point_conversion_form) end end + class PKey + def self._load(string) + OpenSSL::PKey.read(string) + end + + def _dump(_level) + if private? + private_to_der + else + public_to_der + end + end + end + class RSA include OpenSSL::Marshal end diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 5307fe5b0..5a60e5e27 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -103,8 +103,26 @@ def test_ed25519 assert_instance_of OpenSSL::PKey::PKey, priv assert_instance_of OpenSSL::PKey::PKey, pub assert_equal priv_pem, priv.private_to_pem + assert_equal true, priv.private? + assert_equal true, priv.public? + priv_deserialized = Marshal.load(Marshal.dump(priv)) + assert_equal priv.private_to_der, priv_deserialized.private_to_der + assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", + priv.private_to_raw.unpack1("H*") + assert_equal OpenSSL::PKey.private_new("ED25519", priv.private_to_raw).private_to_pem, + priv.private_to_pem + assert_equal pub_pem, priv.public_to_pem assert_equal pub_pem, pub.public_to_pem + assert_equal false, pub.private? + assert_equal true, pub.public? + pub_deserialized = Marshal.load(Marshal.dump(pub)) + assert_equal pub.public_to_der, pub_deserialized.public_to_der + assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", + priv.public_to_raw.unpack1("H*") + assert_equal OpenSSL::PKey.public_new("ED25519", priv.public_to_raw).public_to_pem, + pub.public_to_pem + sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*") 92a009a9f0d4cab8720e820b5f642540 @@ -150,5 +168,26 @@ 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) + alice_deserialized = Marshal.load(Marshal.dump(alice)) + assert_equal alice.private_to_der, alice_deserialized.private_to_der + bob_deserialized = Marshal.load(Marshal.dump(bob)) + assert_equal bob.public_to_der, bob_deserialized.public_to_der + assert_equal "77076d0a7318a57d3c16c17251b26645df4c2f87ebc0992ab177fba51db92c2a", + alice.private_to_raw.unpack1("H*") + assert_equal OpenSSL::PKey.private_new("X25519", alice.private_to_raw).private_to_pem, + alice.private_to_pem + assert_equal "de9edb7d7b7dc1b4d35b61c2ece435373f8343c85b78674dadfc7e146f882b4f", + bob.public_to_raw.unpack1("H*") + assert_equal OpenSSL::PKey.public_new("X25519", bob.public_to_raw).public_to_pem, + bob.public_to_pem + 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.private_new("foo123", "xxx") } + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.private_new("ED25519", "xxx") } + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.public_new("foo123", "xxx") } + assert_raise(OpenSSL::PKey::PKeyError) { OpenSSL::PKey.public_new("ED25519", "xxx") } end end