Skip to content

Commit d3071b7

Browse files
committed
OpenSSL::PKey.from_parameters for generating RSA keys from parameter values
1 parent 1ddbf28 commit d3071b7

File tree

2 files changed

+141
-0
lines changed

2 files changed

+141
-0
lines changed

ext/openssl/ossl_pkey.c

+65
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,59 @@ pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
423423
return ossl_pkey_new(gen_arg.pkey);
424424
}
425425

426+
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
427+
#include <openssl/param_build.h>
428+
static int
429+
add_parameters_to_builder(VALUE key, VALUE value, VALUE arg) {
430+
OSSL_PARAM_BLD *params_builder = (OSSL_PARAM_BLD *) arg;
431+
432+
if(NIL_P(value))
433+
return ST_CONTINUE;
434+
435+
if (SYMBOL_P(key))
436+
key = rb_sym2str(key);
437+
438+
//TODO: Translate GEM specific names to the OpenSSL internal names for RSA params
439+
//TODO: handle different type of parameters for EC keys etc.
440+
if(!OSSL_PARAM_BLD_push_BN(params_builder, StringValueCStr(key), GetBNPtr(value)))
441+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_BN");
442+
443+
return ST_CONTINUE;
444+
}
445+
446+
static VALUE
447+
pkey_from_parameters(int argc, VALUE *argv, VALUE self)
448+
{
449+
VALUE alg, options;
450+
rb_scan_args(argc, argv, "11", &alg, &options);
451+
452+
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, StringValueCStr(alg), NULL);
453+
EVP_PKEY *pkey = NULL;
454+
455+
OSSL_PARAM_BLD *params_builder = OSSL_PARAM_BLD_new();
456+
457+
if (params_builder == NULL)
458+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_new");
459+
460+
rb_hash_foreach(options, add_parameters_to_builder, (VALUE) params_builder);
461+
462+
OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_builder);
463+
OSSL_PARAM_BLD_free(params_builder);
464+
465+
if (params == NULL)
466+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_to_param");
467+
468+
if (ctx == NULL ||
469+
EVP_PKEY_fromdata_init(ctx) <= 0 ||
470+
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
471+
ossl_clear_error();
472+
ossl_raise(ePKeyError, "EVP_PKEY_fromdata");
473+
}
474+
475+
return ossl_pkey_new(pkey);
476+
}
477+
#endif
478+
426479
/*
427480
* call-seq:
428481
* OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
@@ -475,6 +528,17 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
475528
return pkey_generate(argc, argv, self, 0);
476529
}
477530

531+
static VALUE
532+
ossl_pkey_s_from_parameters(int argc, VALUE *argv, VALUE self)
533+
{
534+
// TODO: A version that works with OpenSSL 1.1
535+
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
536+
return pkey_from_parameters(argc, argv, self);
537+
#else
538+
rb_raise(ePKeyError, "Only supported with OpenSSL 3.0");
539+
#endif
540+
}
541+
478542
/*
479543
* TODO: There is no convenient way to check the presence of public key
480544
* components on OpenSSL 3.0. But since keys are immutable on 3.0, pkeys without
@@ -1586,6 +1650,7 @@ Init_ossl_pkey(void)
15861650
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
15871651
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
15881652
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
1653+
rb_define_module_function(mPKey, "from_parameters", ossl_pkey_s_from_parameters, -1);
15891654

15901655
rb_define_alloc_func(cPKey, ossl_pkey_alloc);
15911656
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);

test/openssl/test_pkey.rb

+76
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,80 @@ def test_to_text
169169
rsa = Fixtures.pkey("rsa1024")
170170
assert_include rsa.to_text, "publicExponent"
171171
end
172+
173+
if openssl?(3, 0, 0)
174+
def test_from_parameters_with_n_e_and_d_given_as_integers
175+
new_key = OpenSSL::PKey.from_parameters("RSA", n: 3161751493,
176+
e: 65537,
177+
d: 2064855961)
178+
179+
assert_instance_of OpenSSL::PKey::RSA, new_key
180+
assert_equal true, new_key.private?
181+
assert_equal OpenSSL::BN.new(3161751493), new_key.n
182+
assert_equal OpenSSL::BN.new(65537), new_key.e
183+
assert_equal OpenSSL::BN.new(2064855961), new_key.d
184+
end
185+
186+
187+
def test_from_parameters_with_n_e_and_d_given
188+
new_key = OpenSSL::PKey.from_parameters("RSA", "n" => OpenSSL::BN.new(3161751493),
189+
"e" => OpenSSL::BN.new(65537),
190+
"d" => OpenSSL::BN.new(2064855961))
191+
192+
assert_instance_of OpenSSL::PKey::RSA, new_key
193+
assert_equal true, new_key.private?
194+
assert_equal OpenSSL::BN.new(3161751493), new_key.n
195+
assert_equal OpenSSL::BN.new(65537), new_key.e
196+
assert_equal OpenSSL::BN.new(2064855961), new_key.d
197+
end
198+
199+
def test_from_parameters_with_n_and_e_given
200+
new_key = OpenSSL::PKey.from_parameters("RSA", n: OpenSSL::BN.new(3161751493),
201+
e: OpenSSL::BN.new(65537))
202+
203+
assert_instance_of OpenSSL::PKey::RSA, new_key
204+
assert_equal false, new_key.private?
205+
assert_equal OpenSSL::BN.new(3161751493), new_key.n
206+
assert_equal OpenSSL::BN.new(65537), new_key.e
207+
assert_equal nil, new_key.d
208+
end
209+
210+
def test_from_parameters_with_openssl_internal_names
211+
source = OpenSSL::PKey::RSA.generate(2048)
212+
new_key = OpenSSL::PKey.from_parameters("RSA", n: source.n,
213+
e: source.e,
214+
d: source.d,
215+
"rsa-factor1" => source.p,
216+
"rsa-factor2" => source.q,
217+
"rsa-exponent1" => source.dmp1,
218+
"rsa-exponent2" => source.dmq1,
219+
"rsa-coefficient1" => source.iqmp
220+
)
221+
222+
assert_equal source.n, new_key.n
223+
assert_equal source.e, new_key.e
224+
assert_equal source.d, new_key.d
225+
assert_equal source.p, new_key.p
226+
assert_equal source.q, new_key.q
227+
assert_equal source.dmp1, new_key.dmp1
228+
assert_equal source.dmq1, new_key.dmq1
229+
assert_equal source.iqmp, new_key.iqmp
230+
231+
assert_equal source.to_pem, new_key.to_pem
232+
end
233+
234+
def test_from_parameters_with_invalid_alg
235+
e = assert_raise(OpenSSL::PKey::PKeyError) {
236+
OpenSSL::PKey.from_parameters("ASR", {})
237+
}
238+
assert_equal e.message, "EVP_PKEY_fromdata"
239+
end
240+
else
241+
def test_from_parameter_raises_on_pre_3_openssl
242+
e = assert_raise(OpenSSL::PKey::PKeyError) {
243+
OpenSSL::PKey.from_parameters("ASR", {})
244+
}
245+
assert_equal e.message, "Only supported with OpenSSL 3.0"
246+
end
247+
end
172248
end

0 commit comments

Comments
 (0)