Skip to content

Commit 61052b4

Browse files
committed
OpenSSL::PKey.from_parameters for generating RSA and EC keys from parameter values
1 parent 1ddbf28 commit 61052b4

File tree

2 files changed

+255
-0
lines changed

2 files changed

+255
-0
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,141 @@ 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+
#include <openssl/core_names.h>
429+
430+
static int
431+
add_ec_parameters_to_builder(VALUE key, VALUE value, VALUE arg) {
432+
OSSL_PARAM_BLD *params_builder = (OSSL_PARAM_BLD *) arg;
433+
434+
if(NIL_P(value))
435+
return ST_CONTINUE;
436+
437+
if (SYMBOL_P(key))
438+
key = rb_sym2str(key);
439+
440+
const char * key_ptr = StringValueCStr(key);
441+
442+
if(strcmp(OSSL_PKEY_PARAM_GROUP_NAME, key_ptr) == 0)
443+
if(!OSSL_PARAM_BLD_push_utf8_string(params_builder, key_ptr, StringValueCStr(value), 0))
444+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_utf8_string");
445+
446+
return ST_CONTINUE;
447+
}
448+
449+
static int
450+
add_rsa_parameters_to_builder(VALUE key, VALUE value, VALUE arg) {
451+
OSSL_PARAM_BLD *params_builder = (OSSL_PARAM_BLD *) arg;
452+
453+
if(NIL_P(value))
454+
return ST_CONTINUE;
455+
456+
if (SYMBOL_P(key))
457+
key = rb_sym2str(key);
458+
459+
static const struct {
460+
char alias[5];
461+
char param_name[20];
462+
} key_aliases[] = {
463+
{ "p", OSSL_PKEY_PARAM_RSA_FACTOR1 },
464+
{ "q", OSSL_PKEY_PARAM_RSA_FACTOR2 },
465+
{ "dmp1", OSSL_PKEY_PARAM_RSA_EXPONENT1 },
466+
{ "dmq1", OSSL_PKEY_PARAM_RSA_EXPONENT2 },
467+
{ "iqmp", OSSL_PKEY_PARAM_RSA_COEFFICIENT1 }
468+
};
469+
470+
const char * key_ptr = StringValueCStr(key);
471+
472+
for(int i = 0; i < (int)(sizeof(key_aliases)/sizeof((key_aliases)[0])); i++) {
473+
if(strcmp(key_aliases[i].alias, key_ptr) == 0) {
474+
key_ptr = key_aliases[i].param_name;
475+
break;
476+
}
477+
}
478+
479+
if((strcmp(OSSL_PKEY_PARAM_RSA_N, key_ptr) != 0 &&
480+
strcmp(OSSL_PKEY_PARAM_RSA_E, key_ptr) != 0 &&
481+
strcmp(OSSL_PKEY_PARAM_RSA_D, key_ptr) != 0 &&
482+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR1, key_ptr) != 0 &&
483+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR2, key_ptr) != 0 &&
484+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR3, key_ptr) != 0 &&
485+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR4, key_ptr) != 0 &&
486+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR5, key_ptr) != 0 &&
487+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR6, key_ptr) != 0 &&
488+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR7, key_ptr) != 0 &&
489+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR8, key_ptr) != 0 &&
490+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR9, key_ptr) != 0 &&
491+
strcmp(OSSL_PKEY_PARAM_RSA_FACTOR10, key_ptr) != 0 &&
492+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT1, key_ptr) != 0 &&
493+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT2, key_ptr) != 0 &&
494+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT3, key_ptr) != 0 &&
495+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT4, key_ptr) != 0 &&
496+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT5, key_ptr) != 0 &&
497+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT6, key_ptr) != 0 &&
498+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT7, key_ptr) != 0 &&
499+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT8, key_ptr) != 0 &&
500+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT9, key_ptr) != 0 &&
501+
strcmp(OSSL_PKEY_PARAM_RSA_EXPONENT10, key_ptr) != 0 &&
502+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT1, key_ptr) != 0 &&
503+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT2, key_ptr) != 0 &&
504+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT3, key_ptr) != 0 &&
505+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT4, key_ptr) != 0 &&
506+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT5, key_ptr) != 0 &&
507+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT6, key_ptr) != 0 &&
508+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT7, key_ptr) != 0 &&
509+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT8, key_ptr) != 0 &&
510+
strcmp(OSSL_PKEY_PARAM_RSA_COEFFICIENT9, key_ptr)) != 0)
511+
ossl_raise(ePKeyError, "Unsupported RSA parameter \"%s\"", key_ptr);
512+
513+
if(!OSSL_PARAM_BLD_push_BN(params_builder, key_ptr, GetBNPtr(value)))
514+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_push_BN");
515+
516+
return ST_CONTINUE;
517+
}
518+
519+
static VALUE
520+
pkey_from_parameters(int argc, VALUE *argv, VALUE self)
521+
{
522+
VALUE alg, options;
523+
rb_scan_args(argc, argv, "11", &alg, &options);
524+
525+
OSSL_PARAM_BLD *params_builder = OSSL_PARAM_BLD_new();
526+
527+
if (params_builder == NULL)
528+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_new");
529+
530+
int (*param_adder)(VALUE, VALUE, VALUE) = NULL;
531+
532+
if(strcmp("RSA", StringValueCStr(alg)) == 0)
533+
param_adder = &add_rsa_parameters_to_builder;
534+
else if (strcmp("EC", StringValueCStr(alg)) == 0)
535+
param_adder = &add_ec_parameters_to_builder;
536+
else
537+
ossl_raise(ePKeyError, "\"%s\" is not a supported algorithm", StringValueCStr(alg));
538+
539+
rb_hash_foreach(options, param_adder, (VALUE) params_builder);
540+
541+
OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(params_builder);
542+
OSSL_PARAM_BLD_free(params_builder);
543+
544+
if (params == NULL)
545+
ossl_raise(ePKeyError, "OSSL_PARAM_BLD_to_param");
546+
547+
EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, StringValueCStr(alg), NULL);
548+
EVP_PKEY *pkey = NULL;
549+
550+
if (ctx == NULL ||
551+
EVP_PKEY_fromdata_init(ctx) <= 0 ||
552+
EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_KEYPAIR, params) <= 0) {
553+
ossl_clear_error();
554+
ossl_raise(ePKeyError, "EVP_PKEY_fromdata");
555+
}
556+
557+
return ossl_pkey_new(pkey);
558+
}
559+
#endif
560+
426561
/*
427562
* call-seq:
428563
* OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
@@ -475,6 +610,17 @@ ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
475610
return pkey_generate(argc, argv, self, 0);
476611
}
477612

613+
static VALUE
614+
ossl_pkey_s_from_parameters(int argc, VALUE *argv, VALUE self)
615+
{
616+
// TODO: A version that works with OpenSSL 1.1
617+
#if OSSL_OPENSSL_PREREQ(3, 0, 0)
618+
return pkey_from_parameters(argc, argv, self);
619+
#else
620+
rb_raise(ePKeyError, "Only supported with OpenSSL 3.0");
621+
#endif
622+
}
623+
478624
/*
479625
* TODO: There is no convenient way to check the presence of public key
480626
* components on OpenSSL 3.0. But since keys are immutable on 3.0, pkeys without
@@ -1586,6 +1732,7 @@ Init_ossl_pkey(void)
15861732
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
15871733
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
15881734
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
1735+
rb_define_module_function(mPKey, "from_parameters", ossl_pkey_s_from_parameters, -1);
15891736

15901737
rb_define_alloc_func(cPKey, ossl_pkey_alloc);
15911738
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);

test/openssl/test_pkey.rb

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,4 +169,112 @@ 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_rsa_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_rsa_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_rsa_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_rsa_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_rsa_from_parameters_with_simple_names
235+
source = OpenSSL::PKey::RSA.generate(2048)
236+
new_key = OpenSSL::PKey.from_parameters("RSA", n: source.n,
237+
e: source.e,
238+
d: source.d,
239+
p: source.p,
240+
q: source.q,
241+
dmp1: source.dmp1,
242+
dmq1: source.dmq1,
243+
iqmp: source.iqmp
244+
)
245+
246+
assert_equal source.n, new_key.n
247+
assert_equal source.e, new_key.e
248+
assert_equal source.d, new_key.d
249+
assert_equal source.p, new_key.p
250+
assert_equal source.q, new_key.q
251+
assert_equal source.dmp1, new_key.dmp1
252+
assert_equal source.dmq1, new_key.dmq1
253+
assert_equal source.iqmp, new_key.iqmp
254+
255+
assert_equal source.to_pem, new_key.to_pem
256+
end
257+
258+
def test_from_parameters_with_invalid_alg
259+
e = assert_raise(OpenSSL::PKey::PKeyError) {
260+
OpenSSL::PKey.from_parameters("ASR", {})
261+
}
262+
assert_equal e.message, '"ASR" is not a supported algorithm'
263+
end
264+
265+
def test_ec_from_parameters_with_minimal_data
266+
source = OpenSSL::PKey::EC.generate("prime256v1")
267+
268+
new_key = OpenSSL::PKey.from_parameters("EC", { group: source.group.curve_name })
269+
assert_instance_of OpenSSL::PKey::EC, new_key
270+
assert_equal source.group.curve_name, new_key.group.curve_name
271+
end
272+
else
273+
def test_from_parameter_raises_on_pre_3_openssl
274+
e = assert_raise(OpenSSL::PKey::PKeyError) {
275+
OpenSSL::PKey.from_parameters("RSA", {})
276+
}
277+
assert_equal e.message, "Only supported with OpenSSL 3.0"
278+
end
279+
end
172280
end

0 commit comments

Comments
 (0)