From 045f17bf853c25846f52b766b6fe50d9b0cf9a95 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 2 Jul 2026 02:15:13 +0900 Subject: [PATCH 1/2] pkey: add OpenSSL::PKey::PKey#get_param Expose EVP_PKEY_get_params(), added in OpenSSL 3.0, for retrieving various parameters from an EVP_PKEY object. --- ext/openssl/extconf.rb | 1 + ext/openssl/ossl_pkey.c | 78 +++++++++++++++++++++++++++++++++++ test/openssl/test_pkey.rb | 20 +++++---- test/openssl/test_pkey_ec.rb | 11 +++++ test/openssl/test_pkey_rsa.rb | 16 +++++++ 5 files changed, 118 insertions(+), 8 deletions(-) diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 6c7417899..f36d68327 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -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) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index 0763cfb8b..d93e746a0 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -810,6 +810,81 @@ 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 = Qfalse; + + 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(¶ms[0])) + ossl_raise(ePKeyError, "OSSL_PARAM_modified"); + + params[0].data_size = params[0].return_size > 0 ? params[0].return_size : 1; + params[0].data = ALLOCV(tmp, params[0].data_size); + 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: + ret = ossl_bn_new(BN_value_one()); + BIGNUM *bn = GetBNPtr(ret); + if (!OSSL_PARAM_get_BN(¶ms[0], &bn)) + ossl_raise(eOSSLError, "OSSL_PARAM_get_BN"); + break; + case OSSL_PARAM_UTF8_STRING: + ret = rb_utf8_str_new(params[0].data, params[0].return_size); + break; + case OSSL_PARAM_OCTET_STRING: + ret = rb_str_new(params[0].data, params[0].return_size); + break; + default: + /* + * As of OpenSSL 3.5, 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); + } + ALLOCV_END(tmp); + return ret; +} +#endif + VALUE ossl_pkey_export_traditional(int argc, VALUE *argv, VALUE self, int to_der) { @@ -1871,6 +1946,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); diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index 544d9e0b3..e672f7afe 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -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 @@ -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 diff --git a/test/openssl/test_pkey_ec.rb b/test/openssl/test_pkey_ec.rb index ec97a747a..9f54d552d 100644 --- a/test/openssl/test_pkey_ec.rb +++ b/test/openssl/test_pkey_ec.rb @@ -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 diff --git a/test/openssl/test_pkey_rsa.rb b/test/openssl/test_pkey_rsa.rb index 1716aef38..d56d19bd9 100644 --- a/test/openssl/test_pkey_rsa.rb +++ b/test/openssl/test_pkey_rsa.rb @@ -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 From b0099386a914d06c71cf45884820d69ab102b036 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Thu, 2 Jul 2026 03:38:43 +0900 Subject: [PATCH 2/2] fixup! pkey: add OpenSSL::PKey::PKey#get_param --- ext/openssl/ossl_pkey.c | 39 ++++++++++++++++++++++++++------------- test/openssl/test_pkey.rb | 1 + 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c index d93e746a0..ca2110017 100644 --- a/ext/openssl/ossl_pkey.c +++ b/ext/openssl/ossl_pkey.c @@ -832,7 +832,7 @@ ossl_pkey_get_param(VALUE self, VALUE keyv) { NULL, 0, NULL, 0, OSSL_PARAM_UNMODIFIED }, OSSL_PARAM_END, }; - VALUE ret, tmp = Qfalse; + VALUE ret, tmp = 0; GetPKey(self, pkey); gettable_params = EVP_PKEY_gettable_params(pkey); @@ -849,28 +849,25 @@ ossl_pkey_get_param(VALUE self, VALUE keyv) if (!OSSL_PARAM_modified(¶ms[0])) ossl_raise(ePKeyError, "OSSL_PARAM_modified"); - params[0].data_size = params[0].return_size > 0 ? params[0].return_size : 1; - params[0].data = ALLOCV(tmp, params[0].data_size); - 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: ret = ossl_bn_new(BN_value_one()); - BIGNUM *bn = GetBNPtr(ret); - if (!OSSL_PARAM_get_BN(¶ms[0], &bn)) - ossl_raise(eOSSLError, "OSSL_PARAM_get_BN"); + 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: - ret = rb_utf8_str_new(params[0].data, params[0].return_size); - break; case OSSL_PARAM_OCTET_STRING: - ret = rb_str_new(params[0].data, params[0].return_size); + 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 3.5, the following data types are defined, but are + * 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) @@ -880,6 +877,22 @@ ossl_pkey_get_param(VALUE self, VALUE keyv) 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(¶ms[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; } diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb index e672f7afe..d9503c4e7 100644 --- a/test/openssl/test_pkey.rb +++ b/test/openssl/test_pkey.rb @@ -276,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"))