Skip to content

Commit d8e8e57

Browse files
committed
pkey: add PKey.generate_parameters and .generate_key
Add two methods to create a PKey using the generic EVP interface. This is useful for the PKey types we don't have a dedicated class.
1 parent f4c717b commit d8e8e57

File tree

2 files changed

+265
-0
lines changed

2 files changed

+265
-0
lines changed

ext/openssl/ossl_pkey.c

Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,226 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self)
191191
return ossl_pkey_new(pkey);
192192
}
193193

194+
static VALUE
195+
pkey_gen_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v))
196+
{
197+
VALUE key = rb_ary_entry(i, 0), value = rb_ary_entry(i, 1);
198+
EVP_PKEY_CTX *ctx = (EVP_PKEY_CTX *)ctx_v;
199+
200+
if (SYMBOL_P(key))
201+
key = rb_sym2str(key);
202+
value = rb_String(value);
203+
204+
if (EVP_PKEY_CTX_ctrl_str(ctx, StringValueCStr(key), StringValueCStr(value)) <= 0)
205+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_ctrl_str(ctx, %+"PRIsVALUE", %+"PRIsVALUE")",
206+
key, value);
207+
return Qnil;
208+
}
209+
210+
static VALUE
211+
pkey_gen_apply_options0(VALUE args_v)
212+
{
213+
VALUE *args = (VALUE *)args_v;
214+
215+
rb_block_call(args[1], rb_intern("each"), 0, NULL,
216+
pkey_gen_apply_options_i, args[0]);
217+
return Qnil;
218+
}
219+
220+
struct pkey_blocking_generate_arg {
221+
EVP_PKEY_CTX *ctx;
222+
EVP_PKEY *pkey;
223+
int state;
224+
int yield: 1;
225+
int genparam: 1;
226+
int stop: 1;
227+
};
228+
229+
static VALUE
230+
pkey_gen_cb_yield(VALUE ctx_v)
231+
{
232+
EVP_PKEY_CTX *ctx = (void *)ctx_v;
233+
int i, info_num;
234+
VALUE *argv;
235+
236+
info_num = EVP_PKEY_CTX_get_keygen_info(ctx, -1);
237+
argv = ALLOCA_N(VALUE, info_num);
238+
for (i = 0; i < info_num; i++)
239+
argv[i] = INT2NUM(EVP_PKEY_CTX_get_keygen_info(ctx, i));
240+
241+
return rb_yield_values2(info_num, argv);
242+
}
243+
244+
static int
245+
pkey_gen_cb(EVP_PKEY_CTX *ctx)
246+
{
247+
struct pkey_blocking_generate_arg *arg = EVP_PKEY_CTX_get_app_data(ctx);
248+
249+
if (arg->yield) {
250+
int state;
251+
rb_protect(pkey_gen_cb_yield, (VALUE)ctx, &state);
252+
if (state) {
253+
arg->stop = 1;
254+
arg->state = state;
255+
}
256+
}
257+
return !arg->stop;
258+
}
259+
260+
static void
261+
pkey_blocking_gen_stop(void *ptr)
262+
{
263+
struct pkey_blocking_generate_arg *arg = ptr;
264+
arg->stop = 1;
265+
}
266+
267+
static void *
268+
pkey_blocking_gen(void *ptr)
269+
{
270+
struct pkey_blocking_generate_arg *arg = ptr;
271+
272+
if (arg->genparam && EVP_PKEY_paramgen(arg->ctx, &arg->pkey) <= 0)
273+
return NULL;
274+
if (!arg->genparam && EVP_PKEY_keygen(arg->ctx, &arg->pkey) <= 0)
275+
return NULL;
276+
return arg->pkey;
277+
}
278+
279+
static VALUE
280+
pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
281+
{
282+
EVP_PKEY_CTX *ctx;
283+
VALUE alg, options;
284+
struct pkey_blocking_generate_arg gen_arg = { 0 };
285+
int state;
286+
287+
rb_scan_args(argc, argv, "11", &alg, &options);
288+
if (rb_obj_is_kind_of(alg, cPKey)) {
289+
EVP_PKEY *base_pkey;
290+
291+
GetPKey(alg, base_pkey);
292+
ctx = EVP_PKEY_CTX_new(base_pkey, NULL/* engine */);
293+
if (!ctx)
294+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
295+
}
296+
else {
297+
const EVP_PKEY_ASN1_METHOD *ameth;
298+
ENGINE *tmpeng;
299+
int pkey_id;
300+
301+
StringValue(alg);
302+
ameth = EVP_PKEY_asn1_find_str(&tmpeng, RSTRING_PTR(alg),
303+
RSTRING_LENINT(alg));
304+
if (!ameth)
305+
ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", alg);
306+
EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
307+
#if !defined(OPENSSL_NO_ENGINE)
308+
if (tmpeng)
309+
ENGINE_finish(tmpeng);
310+
#endif
311+
312+
ctx = EVP_PKEY_CTX_new_id(pkey_id, NULL/* engine */);
313+
if (!ctx)
314+
ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_id");
315+
}
316+
317+
if (genparam && EVP_PKEY_paramgen_init(ctx) <= 0) {
318+
EVP_PKEY_CTX_free(ctx);
319+
ossl_raise(ePKeyError, "EVP_PKEY_paramgen_init");
320+
}
321+
if (!genparam && EVP_PKEY_keygen_init(ctx) <= 0) {
322+
EVP_PKEY_CTX_free(ctx);
323+
ossl_raise(ePKeyError, "EVP_PKEY_keygen_init");
324+
}
325+
326+
if (!NIL_P(options)) {
327+
VALUE args[2];
328+
329+
args[0] = (VALUE)ctx;
330+
args[1] = options;
331+
rb_protect(pkey_gen_apply_options0, (VALUE)args, &state);
332+
if (state) {
333+
EVP_PKEY_CTX_free(ctx);
334+
rb_jump_tag(state);
335+
}
336+
}
337+
338+
gen_arg.genparam = genparam;
339+
gen_arg.ctx = ctx;
340+
gen_arg.yield = rb_block_given_p();
341+
EVP_PKEY_CTX_set_app_data(ctx, &gen_arg);
342+
EVP_PKEY_CTX_set_cb(ctx, pkey_gen_cb);
343+
if (gen_arg.yield)
344+
pkey_blocking_gen(&gen_arg);
345+
else
346+
rb_thread_call_without_gvl(pkey_blocking_gen, &gen_arg,
347+
pkey_blocking_gen_stop, &gen_arg);
348+
EVP_PKEY_CTX_free(ctx);
349+
if (!gen_arg.pkey) {
350+
if (gen_arg.state) {
351+
ossl_clear_error();
352+
rb_jump_tag(gen_arg.state);
353+
}
354+
else {
355+
ossl_raise(ePKeyError, genparam ? "EVP_PKEY_paramgen" : "EVP_PKEY_keygen");
356+
}
357+
}
358+
359+
return ossl_pkey_new(gen_arg.pkey);
360+
}
361+
362+
/*
363+
* call-seq:
364+
* OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
365+
*
366+
* Generates new parameters for the algorithm. _algo_name_ is a String that
367+
* represents the algorithm. The optional argument _options_ is a Hash that
368+
* specifies the options specific to the algorithm. The order of the options
369+
* can be important.
370+
*
371+
* A block can be passed optionally. The meaning of the arguments passed to
372+
* the block varies depending on the implementation of the algorithm. The block
373+
* may be called once or multiple times, or may not even be called.
374+
*
375+
* For the supported options, see the documentation for the 'openssl genpkey'
376+
* utility command.
377+
*
378+
* == Example
379+
* pkey = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048)
380+
* p pkey.p.num_bits #=> 2048
381+
*/
382+
static VALUE
383+
ossl_pkey_s_generate_parameters(int argc, VALUE *argv, VALUE self)
384+
{
385+
return pkey_generate(argc, argv, self, 1);
386+
}
387+
388+
/*
389+
* call-seq:
390+
* OpenSSL::PKey.generate_key(algo_name [, options]) -> pkey
391+
* OpenSSL::PKey.generate_key(pkey [, options]) -> pkey
392+
*
393+
* Generates a new key (pair).
394+
*
395+
* If a String is given as the first argument, it generates a new random key
396+
* for the algorithm specified by the name just as ::generate_parameters does.
397+
* If an OpenSSL::PKey::PKey is given instead, it generates a new random key
398+
* for the same algorithm as the key, using the parameters the key contains.
399+
*
400+
* See ::generate_parameters for the details of _options_ and the given block.
401+
*
402+
* == Example
403+
* pkey_params = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048)
404+
* pkey_params.priv_key #=> nil
405+
* pkey = OpenSSL::PKey.generate_key(pkey_params)
406+
* pkey.priv_key #=> #<OpenSSL::BN 6277...
407+
*/
408+
static VALUE
409+
ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
410+
{
411+
return pkey_generate(argc, argv, self, 0);
412+
}
413+
194414
void
195415
ossl_pkey_check_public_key(const EVP_PKEY *pkey)
196416
{
@@ -655,6 +875,8 @@ Init_ossl_pkey(void)
655875
cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
656876

657877
rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
878+
rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
879+
rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
658880

659881
rb_define_alloc_func(cPKey, ossl_pkey_alloc);
660882
rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);

test/openssl/test_pkey.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,47 @@ def test_generic_oid_inspect
2525
assert_equal "X25519", x25519.oid
2626
assert_match %r{oid=X25519}, x25519.inspect
2727
end
28+
29+
def test_s_generate_parameters
30+
# 512 is non-default; 1024 is used if 'dsa_paramgen_bits' is not specified
31+
# with OpenSSL 1.1.0.
32+
pkey = OpenSSL::PKey.generate_parameters("DSA", {
33+
"dsa_paramgen_bits" => 512,
34+
"dsa_paramgen_q_bits" => 256,
35+
})
36+
assert_instance_of OpenSSL::PKey::DSA, pkey
37+
assert_equal 512, pkey.p.num_bits
38+
assert_equal 256, pkey.q.num_bits
39+
assert_equal nil, pkey.priv_key
40+
41+
# Invalid options are checked
42+
assert_raise(OpenSSL::PKey::PKeyError) {
43+
OpenSSL::PKey.generate_parameters("DSA", "invalid" => "option")
44+
}
45+
46+
# Parameter generation callback is called
47+
cb_called = []
48+
assert_raise(RuntimeError) {
49+
OpenSSL::PKey.generate_parameters("DSA") { |*args|
50+
cb_called << args
51+
raise "exit!" if cb_called.size == 3
52+
}
53+
}
54+
assert_not_empty cb_called
55+
end
56+
57+
def test_s_generate_key
58+
assert_raise(OpenSSL::PKey::PKeyError) {
59+
# DSA key pair cannot be generated without parameters
60+
OpenSSL::PKey.generate_key("DSA")
61+
}
62+
pkey_params = OpenSSL::PKey.generate_parameters("DSA", {
63+
"dsa_paramgen_bits" => 512,
64+
"dsa_paramgen_q_bits" => 256,
65+
})
66+
pkey = OpenSSL::PKey.generate_key(pkey_params)
67+
assert_instance_of OpenSSL::PKey::DSA, pkey
68+
assert_equal 512, pkey.p.num_bits
69+
assert_not_equal nil, pkey.priv_key
70+
end
2871
end

0 commit comments

Comments
 (0)