Skip to content
Open
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
1 change: 1 addition & 0 deletions ext/openssl/extconf.rb
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ def find_openssl_library
have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h")
have_func("EVP_MD_CTX_get0_md(NULL)", evp_h)
have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h)
have_func("EVP_PKEY_get_params(NULL, NULL)", evp_h)
have_func("EVP_PKEY_eq(NULL, NULL)", evp_h)
have_func("EVP_PKEY_dup(NULL)", evp_h)
have_func("EVP_PKEY_encapsulate_init(NULL, NULL)", evp_h)
Expand Down
91 changes: 91 additions & 0 deletions ext/openssl/ossl_pkey.c
Original file line number Diff line number Diff line change
Expand Up @@ -810,6 +810,94 @@ ossl_pkey_to_text(VALUE self)
return ossl_membio2str(bio);
}

#ifdef HAVE_EVP_PKEY_GET_PARAMS
/*
* call-seq:
* pkey.get_param(key) -> String or OpenSSL::BN
*
* Gets a parameter from the key. This is a low-level interface to OpenSSL's
* EVP_PKEY_get_params() function, available in OpenSSL 3.0 or later.
*
* Check the relevant EVP_PKEY-* man page, or the documentation for the OpenSSL
* provider in use, for the supported parameter keys and their return types.
*
* See the man page EVP_PKEY_get_params(3) for details.
*/
static VALUE
ossl_pkey_get_param(VALUE self, VALUE keyv)
{
EVP_PKEY *pkey;
const OSSL_PARAM *gettable_params, *p;
OSSL_PARAM params[] = {
{ NULL, 0, NULL, 0, OSSL_PARAM_UNMODIFIED },
OSSL_PARAM_END,
};
VALUE ret, tmp = 0;

GetPKey(self, pkey);
gettable_params = EVP_PKEY_gettable_params(pkey);
if (!gettable_params)
ossl_raise(ePKeyError, "EVP_PKEY_gettable_params");
p = OSSL_PARAM_locate_const(gettable_params, StringValueCStr(keyv));
if (!p)
ossl_raise(ePKeyError, "unrecognized OSSL_PARAM key: %"PRIsVALUE, keyv);

params[0].key = p->key;
params[0].data_type = p->data_type;
if (!EVP_PKEY_get_params(pkey, params))
ossl_raise(ePKeyError, "EVP_PKEY_get_params");
if (!OSSL_PARAM_modified(&params[0]))
ossl_raise(ePKeyError, "OSSL_PARAM_modified");

switch (p->data_type) {
case OSSL_PARAM_INTEGER:
case OSSL_PARAM_UNSIGNED_INTEGER:
ret = ossl_bn_new(BN_value_one());
params[0].data_size =
params[0].return_size > 0 ? params[0].return_size : 1;
params[0].data = ALLOCV(tmp, params[0].data_size);
break;
case OSSL_PARAM_UTF8_STRING:
case OSSL_PARAM_OCTET_STRING:
ret = rb_str_new(NULL, params[0].return_size);
if (p->data_type == OSSL_PARAM_UTF8_STRING)
rb_enc_associate_index(ret, rb_utf8_encindex());
params[0].data_size = params[0].return_size;
params[0].data = RSTRING_PTR(ret);
break;
default:
/*
* As of OpenSSL 4.0, the following data types are defined, but are
* not actually used in EVP_PKEY_gettable_params(). So leave them
* unimplemented for now:
* - OSSL_PARAM_REAL (double)
* - OSSL_PARAM_UTF8_PTR (C string)
* - OSSL_PARAM_OCTET_PTR (arbitrary pointer?)
*/
ossl_raise(ePKeyError, "unsupported OSSL_PARAM data type %d for key %s",
p->data_type, p->key);
}
if (!EVP_PKEY_get_params(pkey, params))
ossl_raise(ePKeyError, "EVP_PKEY_get_params");

switch (p->data_type) {
case OSSL_PARAM_INTEGER:
case OSSL_PARAM_UNSIGNED_INTEGER: {
BIGNUM *bn = GetBNPtr(ret);
if (!OSSL_PARAM_get_BN(&params[0], &bn))
ossl_raise(eOSSLError, "OSSL_PARAM_get_BN");
break;
}
case OSSL_PARAM_UTF8_STRING:
case OSSL_PARAM_OCTET_STRING:
rb_str_set_len(ret, params[0].return_size);
break;
}
ALLOCV_END(tmp);
return ret;
}
#endif

VALUE
ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der)
{
Expand Down Expand Up @@ -1871,6 +1959,9 @@ Init_ossl_pkey(void)
rb_define_method(cPKey, "oid", ossl_pkey_oid, 0);
rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0);
rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0);
#ifdef HAVE_EVP_PKEY_GET_PARAMS
rb_define_method(cPKey, "get_param", ossl_pkey_get_param, 1);
#endif
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);
Expand Down
21 changes: 13 additions & 8 deletions test/openssl/test_pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,10 @@ def test_ed25519
omit_on_fips

# Test vector from RFC 8032 Section 7.1 TEST 2
secret_key = ["4ccd089b28ff96da9db6c346ec114e0f" \
"5b8a319f35aba624da8cf6ed4fb8a6fb"].pack("H*")
public_key = ["3d4017c3e843895a92b70aa74d1b7ebc" \
"9c982ccf2ec4968cc0cd55f12af4660c"].pack("H*")
priv_pem = <<~EOF
-----BEGIN PRIVATE KEY-----
MC4CAQAwBQYDK2VwBCIEIEzNCJso/5banbbDRuwRTg9bijGfNaumJNqM9u1PuKb7
Expand All @@ -200,14 +204,14 @@ def test_ed25519
assert_equal pub_pem, priv.public_to_pem
assert_equal pub_pem, pub.public_to_pem

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
assert_equal secret_key, priv.raw_private_key
assert_equal secret_key, priv.get_param("priv") if openssl?(3, 0, 0)
assert_equal priv.private_to_pem,
OpenSSL::PKey.new_raw_private_key("ED25519", secret_key).private_to_pem
assert_equal public_key, priv.raw_public_key
assert_equal public_key, priv.get_param("pub") if openssl?(3, 0, 0)
assert_equal pub.public_to_pem,
OpenSSL::PKey.new_raw_public_key("ED25519", public_key).public_to_pem

sig = [<<~EOF.gsub(/[^0-9a-f]/, "")].pack("H*")
92a009a9f0d4cab8720e820b5f642540
Expand Down Expand Up @@ -272,6 +276,7 @@ def test_ml_dsa

pkey = OpenSSL::PKey.generate_key("ML-DSA-44")
assert_match(/type_name=ML-DSA-44/, pkey.inspect)
assert_equal(32, pkey.get_param("seed").bytesize)
sig = pkey.sign(nil, "data")
assert_equal(2420, sig.bytesize)
assert_equal(true, pkey.verify(nil, sig, "data"))
Expand Down
11 changes: 11 additions & 0 deletions test/openssl/test_pkey_ec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,17 @@ def test_marshal
assert_equal key.to_der, deserialized.to_der
end

def test_get_param
unless openssl?(3, 0, 0) || OpenSSL::PKey::PKey.method_defined?(:get_param)
omit "EVP_PKEY_get_params() is not supported"
end
key = Fixtures.pkey("p256")
assert_equal("prime256v1", key.get_param("group"))
assert_equal(Encoding::UTF_8, key.get_param("group").encoding)
assert_equal(key.group.order, key.get_param("order"))
assert_kind_of(OpenSSL::BN, key.get_param("order"))
end

def test_check_key
omit_on_fips

Expand Down
16 changes: 16 additions & 0 deletions test/openssl/test_pkey_rsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -532,6 +532,22 @@ def test_params
end
end

def test_get_param
unless openssl?(3, 0, 0) || OpenSSL::PKey::PKey.method_defined?(:get_param)
omit "EVP_PKEY_get_params() is not supported"
end
key = Fixtures.pkey("rsa2048")
assert_kind_of(OpenSSL::BN, key.get_param("n"))
assert_equal(key.n, key.get_param("n"))
assert_equal(key.e, key.get_param("e"))
assert_equal(key.d, key.get_param("d"))
assert_equal(key.p, key.get_param("rsa-factor1"))
assert_equal(key.q, key.get_param("rsa-factor2"))
assert_equal(key.dmp1, key.get_param("rsa-exponent1"))
assert_equal(key.dmq1, key.get_param("rsa-exponent2"))
assert_equal(key.iqmp, key.get_param("rsa-coefficient1"))
end

def test_dup
key = Fixtures.pkey("rsa-1")
key2 = key.dup
Expand Down
Loading