Skip to content

Commit fbddeb7

Browse files
committed
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.
1 parent 680370b commit fbddeb7

5 files changed

Lines changed: 132 additions & 8 deletions

File tree

ext/openssl/extconf.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@ def find_openssl_library
161161
have_func("BN_check_prime(NULL, NULL, NULL)", "openssl/bn.h")
162162
have_func("EVP_MD_CTX_get0_md(NULL)", evp_h)
163163
have_func("EVP_MD_CTX_get_pkey_ctx(NULL)", evp_h)
164+
have_func("EVP_PKEY_get_params(NULL, NULL)", evp_h)
164165
have_func("EVP_PKEY_eq(NULL, NULL)", evp_h)
165166
have_func("EVP_PKEY_dup(NULL)", evp_h)
166167
have_func("EVP_PKEY_encapsulate_init(NULL, NULL)", evp_h)

ext/openssl/ossl_pkey.c

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -771,6 +771,96 @@ ossl_pkey_inspect(VALUE self)
771771
return str;
772772
}
773773

774+
#ifdef HAVE_EVP_PKEY_GET_PARAMS
775+
/*
776+
* call-seq:
777+
* pkey.get_param(key) -> String or OpenSSL::BN
778+
*
779+
* Gets a parameter from the key. This is a low-level interface to OpenSSL's
780+
* EVP_PKEY_get_params() function, available in OpenSSL 3.0 or later.
781+
*
782+
* Check the relevant EVP_PKEY-* man page, or the documentation for the OpenSSL
783+
* provider in use, for the supported parameter keys and their return types.
784+
*
785+
* See the man page EVP_PKEY_get_params(3) for details.
786+
*/
787+
static VALUE
788+
ossl_pkey_get_param(VALUE self, VALUE keyv)
789+
{
790+
EVP_PKEY *pkey;
791+
const OSSL_PARAM *gettable_params, *p;
792+
OSSL_PARAM params[] = {
793+
{ NULL, 0, NULL, 0, OSSL_PARAM_UNMODIFIED },
794+
OSSL_PARAM_END,
795+
};
796+
VALUE ret, tmp = 0;
797+
798+
GetPKey(self, pkey);
799+
gettable_params = EVP_PKEY_gettable_params(pkey);
800+
if (!gettable_params)
801+
ossl_raise(ePKeyError, "EVP_PKEY_gettable_params");
802+
p = OSSL_PARAM_locate_const(gettable_params, StringValueCStr(keyv));
803+
if (!p)
804+
ossl_raise(ePKeyError, "unrecognized OSSL_PARAM key: %"PRIsVALUE, keyv);
805+
806+
params[0].key = p->key;
807+
params[0].data_type = p->data_type;
808+
if (!EVP_PKEY_get_params(pkey, params))
809+
ossl_raise(ePKeyError, "EVP_PKEY_get_params");
810+
if (!OSSL_PARAM_modified(&params[0]))
811+
ossl_raise(ePKeyError, "OSSL_PARAM_modified");
812+
813+
switch (p->data_type) {
814+
case OSSL_PARAM_INTEGER:
815+
case OSSL_PARAM_UNSIGNED_INTEGER:
816+
ret = ossl_bn_new(BN_value_one());
817+
params[0].data_size =
818+
params[0].return_size > 0 ? params[0].return_size : 1;
819+
params[0].data = ALLOCV(tmp, params[0].data_size);
820+
break;
821+
case OSSL_PARAM_UTF8_STRING:
822+
case OSSL_PARAM_OCTET_STRING:
823+
ret = rb_str_new(NULL, params[0].return_size);
824+
if (p->data_type == OSSL_PARAM_UTF8_STRING)
825+
rb_enc_associate_index(ret, rb_utf8_encindex());
826+
params[0].data_size = params[0].return_size;
827+
params[0].data = RSTRING_PTR(ret);
828+
break;
829+
default:
830+
/*
831+
* As of OpenSSL 4.0, the following data types are defined, but are
832+
* not actually used in EVP_PKEY_gettable_params(). So leave them
833+
* unimplemented for now:
834+
* - OSSL_PARAM_REAL (double)
835+
* - OSSL_PARAM_UTF8_PTR (C string)
836+
* - OSSL_PARAM_OCTET_PTR (arbitrary pointer?)
837+
*/
838+
ossl_raise(ePKeyError, "unsupported OSSL_PARAM data type %d for key %s",
839+
p->data_type, p->key);
840+
}
841+
if (!EVP_PKEY_get_params(pkey, params))
842+
ossl_raise(ePKeyError, "EVP_PKEY_get_params");
843+
844+
switch (p->data_type) {
845+
case OSSL_PARAM_INTEGER:
846+
case OSSL_PARAM_UNSIGNED_INTEGER: {
847+
BIGNUM *bn = GetBNPtr(ret);
848+
if (!OSSL_PARAM_get_BN(&params[0], &bn))
849+
ossl_raise(eOSSLError, "OSSL_PARAM_get_BN");
850+
break;
851+
}
852+
case OSSL_PARAM_UTF8_STRING:
853+
case OSSL_PARAM_OCTET_STRING:
854+
rb_str_set_len(ret, params[0].return_size);
855+
break;
856+
}
857+
ALLOCV_END(tmp);
858+
return ret;
859+
}
860+
#else
861+
#define ossl_pkey_get_param rb_f_notimplement
862+
#endif
863+
774864
/*
775865
* call-seq:
776866
* pkey.to_text -> string
@@ -1868,6 +1958,7 @@ Init_ossl_pkey(void)
18681958
#endif
18691959
rb_define_method(cPKey, "oid", ossl_pkey_oid, 0);
18701960
rb_define_method(cPKey, "inspect", ossl_pkey_inspect, 0);
1961+
rb_define_method(cPKey, "get_param", ossl_pkey_get_param, 1);
18711962
rb_define_method(cPKey, "to_text", ossl_pkey_to_text, 0);
18721963
rb_define_method(cPKey, "private_to_der", ossl_pkey_private_to_der, -1);
18731964
rb_define_method(cPKey, "private_to_pem", ossl_pkey_private_to_pem, -1);

test/openssl/test_pkey.rb

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -182,6 +182,10 @@ def test_ed25519
182182
omit_on_fips
183183

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

203-
assert_equal "4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb",
204-
priv.raw_private_key.unpack1("H*")
205-
assert_equal OpenSSL::PKey.new_raw_private_key("ED25519", priv.raw_private_key).private_to_pem,
206-
priv.private_to_pem
207-
assert_equal "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c",
208-
priv.raw_public_key.unpack1("H*")
209-
assert_equal OpenSSL::PKey.new_raw_public_key("ED25519", priv.raw_public_key).public_to_pem,
210-
pub.public_to_pem
207+
assert_equal secret_key, priv.raw_private_key
208+
assert_equal secret_key, priv.get_param("priv") if openssl?(3, 0, 0)
209+
assert_equal priv.private_to_pem,
210+
OpenSSL::PKey.new_raw_private_key("ED25519", secret_key).private_to_pem
211+
assert_equal public_key, priv.raw_public_key
212+
assert_equal public_key, priv.get_param("pub") if openssl?(3, 0, 0)
213+
assert_equal pub.public_to_pem,
214+
OpenSSL::PKey.new_raw_public_key("ED25519", public_key).public_to_pem
211215

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

273277
pkey = OpenSSL::PKey.generate_key("ML-DSA-44")
274278
assert_match(/type_name=ML-DSA-44/, pkey.inspect)
279+
assert_equal(32, pkey.get_param("seed").bytesize)
275280
sig = pkey.sign(nil, "data")
276281
assert_equal(2420, sig.bytesize)
277282
assert_equal(true, pkey.verify(nil, sig, "data"))

test/openssl/test_pkey_ec.rb

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,17 @@ def test_marshal
8080
assert_equal key.to_der, deserialized.to_der
8181
end
8282

83+
def test_get_param
84+
unless openssl?(3, 0, 0) || OpenSSL::PKey::PKey.method_defined?(:get_param)
85+
omit "EVP_PKEY_get_params() is not supported"
86+
end
87+
key = Fixtures.pkey("p256")
88+
assert_equal("prime256v1", key.get_param("group"))
89+
assert_equal(Encoding::UTF_8, key.get_param("group").encoding)
90+
assert_equal(key.group.order, key.get_param("order"))
91+
assert_kind_of(OpenSSL::BN, key.get_param("order"))
92+
end
93+
8394
def test_check_key
8495
omit_on_fips
8596

test/openssl/test_pkey_rsa.rb

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,6 +532,22 @@ def test_params
532532
end
533533
end
534534

535+
def test_get_param
536+
unless openssl?(3, 0, 0) || OpenSSL::PKey::PKey.method_defined?(:get_param)
537+
omit "EVP_PKEY_get_params() is not supported"
538+
end
539+
key = Fixtures.pkey("rsa2048")
540+
assert_kind_of(OpenSSL::BN, key.get_param("n"))
541+
assert_equal(key.n, key.get_param("n"))
542+
assert_equal(key.e, key.get_param("e"))
543+
assert_equal(key.d, key.get_param("d"))
544+
assert_equal(key.p, key.get_param("rsa-factor1"))
545+
assert_equal(key.q, key.get_param("rsa-factor2"))
546+
assert_equal(key.dmp1, key.get_param("rsa-exponent1"))
547+
assert_equal(key.dmq1, key.get_param("rsa-exponent2"))
548+
assert_equal(key.iqmp, key.get_param("rsa-coefficient1"))
549+
end
550+
535551
def test_dup
536552
key = Fixtures.pkey("rsa-1")
537553
key2 = key.dup

0 commit comments

Comments
 (0)