Skip to content

Commit f774e74

Browse files
committed
pkey/dh: use high level EVP interface to generate parameters and keys
Implement PKey::DH.new(size, gen), PKey::DH.generate(size, gen), and PKey::DH#generate_key! using OpenSSL::PKey.generate_parameters and .generate_key instead of the low level DH functions. Note that the EVP interface can enforce additional restrictions - for example, DH key shorter than 2048 bits is no longer supported by default in OpenSSL 3.0. The test code is updated as such.
1 parent e063431 commit f774e74

File tree

3 files changed

+101
-157
lines changed

3 files changed

+101
-157
lines changed

ext/openssl/ossl_pkey_dh.c

+33-153
Original file line numberDiff line numberDiff line change
@@ -32,147 +32,56 @@ VALUE eDHError;
3232
/*
3333
* Private
3434
*/
35-
struct dh_blocking_gen_arg {
36-
DH *dh;
37-
int size;
38-
int gen;
39-
BN_GENCB *cb;
40-
int result;
41-
};
42-
43-
static void *
44-
dh_blocking_gen(void *arg)
45-
{
46-
struct dh_blocking_gen_arg *gen = (struct dh_blocking_gen_arg *)arg;
47-
gen->result = DH_generate_parameters_ex(gen->dh, gen->size, gen->gen, gen->cb);
48-
return 0;
49-
}
50-
51-
static DH *
52-
dh_generate(int size, int gen)
53-
{
54-
struct ossl_generate_cb_arg cb_arg = { 0 };
55-
struct dh_blocking_gen_arg gen_arg;
56-
DH *dh = DH_new();
57-
BN_GENCB *cb = BN_GENCB_new();
58-
59-
if (!dh || !cb) {
60-
DH_free(dh);
61-
BN_GENCB_free(cb);
62-
ossl_raise(eDHError, "malloc failure");
63-
}
64-
65-
if (rb_block_given_p())
66-
cb_arg.yield = 1;
67-
BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
68-
gen_arg.dh = dh;
69-
gen_arg.size = size;
70-
gen_arg.gen = gen;
71-
gen_arg.cb = cb;
72-
if (cb_arg.yield == 1) {
73-
/* we cannot release GVL when callback proc is supplied */
74-
dh_blocking_gen(&gen_arg);
75-
} else {
76-
/* there's a chance to unblock */
77-
rb_thread_call_without_gvl(dh_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
78-
}
79-
80-
BN_GENCB_free(cb);
81-
if (!gen_arg.result) {
82-
DH_free(dh);
83-
if (cb_arg.state) {
84-
/* Clear OpenSSL error queue before re-raising. */
85-
ossl_clear_error();
86-
rb_jump_tag(cb_arg.state);
87-
}
88-
ossl_raise(eDHError, "DH_generate_parameters_ex");
89-
}
90-
91-
if (!DH_generate_key(dh)) {
92-
DH_free(dh);
93-
ossl_raise(eDHError, "DH_generate_key");
94-
}
95-
96-
return dh;
97-
}
98-
99-
/*
100-
* call-seq:
101-
* DH.generate(size [, generator]) -> dh
102-
*
103-
* Creates a new DH instance from scratch by generating the private and public
104-
* components alike.
105-
*
106-
* === Parameters
107-
* * _size_ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
108-
* * _generator_ is a small number > 1, typically 2 or 5.
109-
*
110-
*/
111-
static VALUE
112-
ossl_dh_s_generate(int argc, VALUE *argv, VALUE klass)
113-
{
114-
EVP_PKEY *pkey;
115-
DH *dh ;
116-
int g = 2;
117-
VALUE size, gen, obj;
118-
119-
if (rb_scan_args(argc, argv, "11", &size, &gen) == 2) {
120-
g = NUM2INT(gen);
121-
}
122-
obj = rb_obj_alloc(klass);
123-
GetPKey(obj, pkey);
124-
125-
dh = dh_generate(NUM2INT(size), g);
126-
if (!EVP_PKEY_assign_DH(pkey, dh)) {
127-
DH_free(dh);
128-
ossl_raise(eDHError, "EVP_PKEY_assign_DH");
129-
}
130-
return obj;
131-
}
132-
13335
/*
13436
* call-seq:
13537
* DH.new -> dh
13638
* DH.new(string) -> dh
13739
* DH.new(size [, generator]) -> dh
13840
*
139-
* Either generates a DH instance from scratch or by reading already existing
140-
* DH parameters from _string_. Note that when reading a DH instance from
141-
* data that was encoded from a DH instance by using DH#to_pem or DH#to_der
142-
* the result will *not* contain a public/private key pair yet. This needs to
143-
* be generated using DH#generate_key! first.
41+
* Creates a new instance of OpenSSL::PKey::DH.
42+
*
43+
* If called without arguments, an empty instance without any parameter or key
44+
* components is created. Use #set_pqg to manually set the parameters afterwards
45+
* (and optionally #set_key to set private and public key components).
46+
*
47+
* If a String is given, tries to parse it as a DER- or PEM- encoded parameters.
48+
* See also OpenSSL::PKey.read which can parse keys of any kinds.
49+
*
50+
* The DH.new(size [, generator]) form is an alias of DH.generate.
14451
*
145-
* === Parameters
146-
* * _size_ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
147-
* * _generator_ is a small number > 1, typically 2 or 5.
148-
* * _string_ contains the DER or PEM encoded key.
52+
* string::
53+
* A String that contains the DER or PEM encoded key.
54+
* size::
55+
* See DH.generate.
56+
* generator::
57+
* See DH.generate.
14958
*
150-
* === Examples
151-
* DH.new # -> dh
152-
* DH.new(1024) # -> dh
153-
* DH.new(1024, 5) # -> dh
154-
* #Reading DH parameters
155-
* dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet
156-
* dh.generate_key! # -> dh with public and private key
59+
* Examples:
60+
* # Creating an instance from scratch
61+
* dh = DH.new
62+
* dh.set_pqg(bn_p, nil, bn_g)
63+
*
64+
* # Generating a parameters and a key pair
65+
* dh = DH.new(2048) # An alias of DH.generate(2048)
66+
*
67+
* # Reading DH parameters
68+
* dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet
69+
* dh.generate_key! # -> dh with public and private key
15770
*/
15871
static VALUE
15972
ossl_dh_initialize(int argc, VALUE *argv, VALUE self)
16073
{
16174
EVP_PKEY *pkey;
16275
DH *dh;
163-
int g = 2;
16476
BIO *in;
165-
VALUE arg, gen;
77+
VALUE arg;
16678

16779
GetPKey(self, pkey);
168-
if(rb_scan_args(argc, argv, "02", &arg, &gen) == 0) {
169-
dh = DH_new();
170-
}
171-
else if (RB_INTEGER_TYPE_P(arg)) {
172-
if (!NIL_P(gen)) {
173-
g = NUM2INT(gen);
174-
}
175-
dh = dh_generate(NUM2INT(arg), g);
80+
/* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */
81+
if (rb_scan_args(argc, argv, "01", &arg) == 0) {
82+
dh = DH_new();
83+
if (!dh)
84+
ossl_raise(eDHError, "DH_new");
17685
}
17786
else {
17887
arg = ossl_to_der_if_possible(arg);
@@ -449,33 +358,6 @@ ossl_dh_check_params(VALUE self)
449358
return codes == 0 ? Qtrue : Qfalse;
450359
}
451360

452-
/*
453-
* call-seq:
454-
* dh.generate_key! -> self
455-
*
456-
* Generates a private and public key unless a private key already exists.
457-
* If this DH instance was generated from public DH parameters (e.g. by
458-
* encoding the result of DH#public_key), then this method needs to be
459-
* called first in order to generate the per-session keys before performing
460-
* the actual key exchange.
461-
*
462-
* === Example
463-
* dh = OpenSSL::PKey::DH.new(2048)
464-
* public_key = dh.public_key #contains no private/public key yet
465-
* public_key.generate_key!
466-
* puts public_key.private? # => true
467-
*/
468-
static VALUE
469-
ossl_dh_generate_key(VALUE self)
470-
{
471-
DH *dh;
472-
473-
GetDH(self, dh);
474-
if (!DH_generate_key(dh))
475-
ossl_raise(eDHError, "Failed to generate key");
476-
return self;
477-
}
478-
479361
/*
480362
* Document-method: OpenSSL::PKey::DH#set_pqg
481363
* call-seq:
@@ -540,7 +422,6 @@ Init_ossl_dh(void)
540422
* puts symm_key1 == symm_key2 # => true
541423
*/
542424
cDH = rb_define_class_under(mPKey, "DH", cPKey);
543-
rb_define_singleton_method(cDH, "generate", ossl_dh_s_generate, -1);
544425
rb_define_method(cDH, "initialize", ossl_dh_initialize, -1);
545426
rb_define_method(cDH, "initialize_copy", ossl_dh_initialize_copy, 1);
546427
rb_define_method(cDH, "public?", ossl_dh_is_public, 0);
@@ -552,7 +433,6 @@ Init_ossl_dh(void)
552433
rb_define_method(cDH, "to_der", ossl_dh_to_der, 0);
553434
rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0);
554435
rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0);
555-
rb_define_method(cDH, "generate_key!", ossl_dh_generate_key, 0);
556436

557437
DEF_OSSL_PKEY_BN(cDH, dh, p);
558438
DEF_OSSL_PKEY_BN(cDH, dh, q);

lib/openssl/pkey.rb

+57
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,63 @@ def compute_key(pub_bn)
2727
peer.set_key(pub_bn, nil)
2828
derive(peer)
2929
end
30+
31+
# :call-seq:
32+
# dh.generate_key! -> self
33+
#
34+
# Generates a private and public key unless a private key already exists.
35+
# If this DH instance was generated from public DH parameters (e.g. by
36+
# encoding the result of DH#public_key), then this method needs to be
37+
# called first in order to generate the per-session keys before performing
38+
# the actual key exchange.
39+
#
40+
# See also OpenSSL::PKey.generate_key.
41+
#
42+
# Example:
43+
# dh = OpenSSL::PKey::DH.new(2048)
44+
# public_key = dh.public_key #contains no private/public key yet
45+
# public_key.generate_key!
46+
# puts public_key.private? # => true
47+
def generate_key!
48+
unless priv_key
49+
tmp = OpenSSL::PKey.generate_key(self)
50+
set_key(tmp.pub_key, tmp.priv_key)
51+
end
52+
self
53+
end
54+
55+
class << self
56+
# :call-seq:
57+
# DH.generate(size [, generator = 2]) -> dh
58+
#
59+
# Creates a new DH instance from scratch by generating the private and
60+
# public components alike.
61+
#
62+
# See also OpenSSL::PKey.generate_parameters and
63+
# OpenSSL::PKey.generate_key.
64+
#
65+
# size::
66+
# An Integer representing the desired key size.
67+
# generator::
68+
# A small Integer > 1, typically 2 or 5.
69+
def generate(size, generator = 2, &blk)
70+
dhparams = OpenSSL::PKey.generate_parameters("DH", {
71+
"dh_paramgen_prime_len" => size,
72+
"dh_paramgen_generator" => generator,
73+
}, &blk)
74+
OpenSSL::PKey.generate_key(dhparams)
75+
end
76+
77+
# Handle DH.new(size, generator) form here; new(str) and new() forms
78+
# are handled by #initialize
79+
def new(*args, &blk) # :nodoc:
80+
if args[0].is_a?(Integer)
81+
generate(*args, &blk)
82+
else
83+
super
84+
end
85+
end
86+
end
3087
end
3188

3289
class DSA

test/openssl/test_pkey_dh.rb

+11-4
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,19 @@
44
if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH)
55

66
class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
7-
NEW_KEYLEN = 256
7+
NEW_KEYLEN = 2048
88

9-
def test_new
9+
def test_new_empty
10+
dh = OpenSSL::PKey::DH.new
11+
assert_equal nil, dh.p
12+
assert_equal nil, dh.priv_key
13+
end
14+
15+
def test_new_generate
16+
# This test is slow
1017
dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
1118
assert_key(dh)
12-
end
19+
end if ENV["OSSL_TEST_ALL"]
1320

1421
def test_new_break
1522
assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
@@ -80,7 +87,7 @@ def test_key_exchange
8087
end
8188

8289
def test_dup
83-
dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
90+
dh = Fixtures.pkey("dh1024")
8491
dh2 = dh.dup
8592
assert_equal dh.to_der, dh2.to_der # params
8693
assert_equal_params dh, dh2 # keys

0 commit comments

Comments
 (0)