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

OpenSSL::PKey convenience methods #373

Closed
wants to merge 2 commits into from
Closed
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
142 changes: 142 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please use EVP_PKEY_asn1_find_str() instead of OBJ_sn2nid().

pkey_generate() is currently doing this. I think it should be extracted as a function.


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
Expand Down Expand Up @@ -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;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm pretty sure I'm not doing this right. According to https://www.openssl.org/docs/man1.1.1/man3/EVP_PKEY_get_raw_private_key.html:

EVP_PKEY_get_raw_private_key() fills the buffer provided by priv with raw private key data. The size of the priv buffer should be in *len on entry to the function, and on exit *len is updated with the number of bytes actually written. If the buffer priv is NULL then *len is populated with the number of bytes required to hold the key.

However if I pass NULL instead of str the tests fail 🤔

}

VALUE
ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der)
{
Expand Down Expand Up @@ -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)
{
Expand All @@ -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
Expand All @@ -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
Expand Down Expand Up @@ -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);
Expand Down
14 changes: 14 additions & 0 deletions lib/openssl/pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 @@ -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
Expand Down Expand Up @@ -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