Skip to content

Commit 48a6c39

Browse files
committed
pkey: implement {DH,DSA,RSA}#public_key in Ruby
The low-level API that is used to implement #public_key is deprecated in OpenSSL 3.0. It is actually very simple to implement in another way, using existing methods only, in much shorter code. Let's do it. While we are at it, the documentation is updated to recommend against using #public_key. Now that OpenSSL::PKey::PKey implements public_to_der method, there is no real use case for #public_key in newly written Ruby programs.
1 parent e0b4c56 commit 48a6c39

File tree

5 files changed

+87
-168
lines changed

5 files changed

+87
-168
lines changed

ext/openssl/ossl_pkey_dh.c

Lines changed: 13 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -266,48 +266,6 @@ ossl_dh_get_params(VALUE self)
266266
return hash;
267267
}
268268

269-
/*
270-
* call-seq:
271-
* dh.public_key -> aDH
272-
*
273-
* Returns a new DH instance that carries just the public information, i.e.
274-
* the prime _p_ and the generator _g_, but no public/private key yet. Such
275-
* a pair may be generated using DH#generate_key!. The "public key" needed
276-
* for a key exchange with DH#compute_key is considered as per-session
277-
* information and may be retrieved with DH#pub_key once a key pair has
278-
* been generated.
279-
* If the current instance already contains private information (and thus a
280-
* valid public/private key pair), this information will no longer be present
281-
* in the new instance generated by DH#public_key. This feature is helpful for
282-
* publishing the Diffie-Hellman parameters without leaking any of the private
283-
* per-session information.
284-
*
285-
* === Example
286-
* dh = OpenSSL::PKey::DH.new(2048) # has public and private key set
287-
* public_key = dh.public_key # contains only prime and generator
288-
* parameters = public_key.to_der # it's safe to publish this
289-
*/
290-
static VALUE
291-
ossl_dh_to_public_key(VALUE self)
292-
{
293-
EVP_PKEY *pkey;
294-
DH *orig_dh, *dh;
295-
VALUE obj;
296-
297-
obj = rb_obj_alloc(rb_obj_class(self));
298-
GetPKey(obj, pkey);
299-
300-
GetDH(self, orig_dh);
301-
dh = DHparams_dup(orig_dh);
302-
if (!dh)
303-
ossl_raise(eDHError, "DHparams_dup");
304-
if (!EVP_PKEY_assign_DH(pkey, dh)) {
305-
DH_free(dh);
306-
ossl_raise(eDHError, "EVP_PKEY_assign_DH");
307-
}
308-
return obj;
309-
}
310-
311269
/*
312270
* call-seq:
313271
* dh.params_ok? -> true | false
@@ -384,14 +342,20 @@ Init_ossl_dh(void)
384342
* The per-session private key, an OpenSSL::BN.
385343
*
386344
* === Example of a key exchange
387-
* dh1 = OpenSSL::PKey::DH.new(2048)
388-
* der = dh1.public_key.to_der #you may send this publicly to the participating party
389-
* dh2 = OpenSSL::PKey::DH.new(der)
390-
* dh2.generate_key! #generate the per-session key pair
391-
* symm_key1 = dh1.compute_key(dh2.pub_key)
392-
* symm_key2 = dh2.compute_key(dh1.pub_key)
345+
* # you may send the parameters (der) and own public key (pub1) publicly
346+
* # to the participating party
347+
* dh1 = OpenSSL::PKey::DH.new(2048)
348+
* der = dh1.to_der
349+
* pub1 = dh1.pub_key
350+
*
351+
* # the other party generates its per-session key pair
352+
* dhparams = OpenSSL::PKey::DH.new(der)
353+
* dh2 = OpenSSL::PKey.generate_key(dhparams)
354+
* pub2 = dh2.pub_key
393355
*
394-
* puts symm_key1 == symm_key2 # => true
356+
* symm_key1 = dh1.compute_key(pub2)
357+
* symm_key2 = dh2.compute_key(pub1)
358+
* puts symm_key1 == symm_key2 # => true
395359
*/
396360
cDH = rb_define_class_under(mPKey, "DH", cPKey);
397361
rb_define_method(cDH, "initialize", ossl_dh_initialize, -1);
@@ -402,7 +366,6 @@ Init_ossl_dh(void)
402366
rb_define_alias(cDH, "to_pem", "export");
403367
rb_define_alias(cDH, "to_s", "export");
404368
rb_define_method(cDH, "to_der", ossl_dh_to_der, 0);
405-
rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0);
406369
rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0);
407370

408371
DEF_OSSL_PKEY_BN(cDH, dh, p);

ext/openssl/ossl_pkey_dsa.c

Lines changed: 0 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -264,47 +264,6 @@ ossl_dsa_get_params(VALUE self)
264264
return hash;
265265
}
266266

267-
/*
268-
* call-seq:
269-
* dsa.public_key -> aDSA
270-
*
271-
* Returns a new DSA instance that carries just the public key information.
272-
* If the current instance has also private key information, this will no
273-
* longer be present in the new instance. This feature is helpful for
274-
* publishing the public key information without leaking any of the private
275-
* information.
276-
*
277-
* === Example
278-
* dsa = OpenSSL::PKey::DSA.new(2048) # has public and private information
279-
* pub_key = dsa.public_key # has only the public part available
280-
* pub_key_der = pub_key.to_der # it's safe to publish this
281-
*
282-
*
283-
*/
284-
static VALUE
285-
ossl_dsa_to_public_key(VALUE self)
286-
{
287-
EVP_PKEY *pkey, *pkey_new;
288-
DSA *dsa;
289-
VALUE obj;
290-
291-
GetPKeyDSA(self, pkey);
292-
obj = rb_obj_alloc(rb_obj_class(self));
293-
GetPKey(obj, pkey_new);
294-
295-
#define DSAPublicKey_dup(dsa) (DSA *)ASN1_dup( \
296-
(i2d_of_void *)i2d_DSAPublicKey, (d2i_of_void *)d2i_DSAPublicKey, (char *)(dsa))
297-
dsa = DSAPublicKey_dup(EVP_PKEY_get0_DSA(pkey));
298-
#undef DSAPublicKey_dup
299-
if (!dsa)
300-
ossl_raise(eDSAError, "DSAPublicKey_dup");
301-
if (!EVP_PKEY_assign_DSA(pkey_new, dsa)) {
302-
DSA_free(dsa);
303-
ossl_raise(eDSAError, "EVP_PKEY_assign_DSA");
304-
}
305-
return obj;
306-
}
307-
308267
/*
309268
* call-seq:
310269
* dsa.syssign(string) -> aString
@@ -445,7 +404,6 @@ Init_ossl_dsa(void)
445404
rb_define_alias(cDSA, "to_pem", "export");
446405
rb_define_alias(cDSA, "to_s", "export");
447406
rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0);
448-
rb_define_method(cDSA, "public_key", ossl_dsa_to_public_key, 0);
449407
rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1);
450408
rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2);
451409

ext/openssl/ossl_pkey_rsa.c

Lines changed: 1 addition & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self)
390390
* data = "Sign me!"
391391
* pkey = OpenSSL::PKey::RSA.new(2048)
392392
* signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
393-
* pub_key = pkey.public_key
393+
* pub_key = OpenSSL::PKey.read(pkey.public_to_der)
394394
* puts pub_key.verify_pss("SHA256", signature, data,
395395
* salt_length: :auto, mgf1_hash: "SHA256") # => true
396396
*/
@@ -587,61 +587,6 @@ ossl_rsa_get_params(VALUE self)
587587
return hash;
588588
}
589589

590-
/*
591-
* call-seq:
592-
* rsa.public_key -> RSA
593-
*
594-
* Makes new RSA instance containing the public key from the private key.
595-
*/
596-
static VALUE
597-
ossl_rsa_to_public_key(VALUE self)
598-
{
599-
EVP_PKEY *pkey, *pkey_new;
600-
RSA *rsa;
601-
VALUE obj;
602-
603-
GetPKeyRSA(self, pkey);
604-
obj = rb_obj_alloc(rb_obj_class(self));
605-
GetPKey(obj, pkey_new);
606-
607-
rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey));
608-
if (!rsa)
609-
ossl_raise(eRSAError, "RSAPublicKey_dup");
610-
if (!EVP_PKEY_assign_RSA(pkey_new, rsa)) {
611-
RSA_free(rsa);
612-
ossl_raise(eRSAError, "EVP_PKEY_assign_RSA");
613-
}
614-
return obj;
615-
}
616-
617-
/*
618-
* TODO: Test me
619-
620-
static VALUE
621-
ossl_rsa_blinding_on(VALUE self)
622-
{
623-
RSA *rsa;
624-
625-
GetRSA(self, rsa);
626-
627-
if (RSA_blinding_on(rsa, ossl_bn_ctx) != 1) {
628-
ossl_raise(eRSAError, NULL);
629-
}
630-
return self;
631-
}
632-
633-
static VALUE
634-
ossl_rsa_blinding_off(VALUE self)
635-
{
636-
RSA *rsa;
637-
638-
GetRSA(self, rsa);
639-
RSA_blinding_off(rsa);
640-
641-
return self;
642-
}
643-
*/
644-
645590
/*
646591
* Document-method: OpenSSL::PKey::RSA#set_key
647592
* call-seq:
@@ -712,7 +657,6 @@ Init_ossl_rsa(void)
712657
rb_define_alias(cRSA, "to_pem", "export");
713658
rb_define_alias(cRSA, "to_s", "export");
714659
rb_define_method(cRSA, "to_der", ossl_rsa_to_der, 0);
715-
rb_define_method(cRSA, "public_key", ossl_rsa_to_public_key, 0);
716660
rb_define_method(cRSA, "public_encrypt", ossl_rsa_public_encrypt, -1);
717661
rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1);
718662
rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1);

lib/openssl/pkey.rb

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,30 @@ module OpenSSL::PKey
1010
class DH
1111
include OpenSSL::Marshal
1212

13+
# :call-seq:
14+
# dh.public_key -> dhnew
15+
#
16+
# Returns a new DH instance that carries just the \DH parameters.
17+
#
18+
# Contrary to the method name, the returned DH object contains only
19+
# parameters and not the public key.
20+
#
21+
# This method is provided for backwards compatibility. In most cases, there
22+
# is no need to call this method.
23+
#
24+
# For the purpose of re-generating the key pair while keeping the
25+
# parameters, check OpenSSL::PKey.generate_key.
26+
#
27+
# Example:
28+
# # OpenSSL::PKey::DH.generate by default generates a random key pair
29+
# dh1 = OpenSSL::PKey::DH.generate(2048)
30+
# p dh1.priv_key #=> #<OpenSSL::BN 1288347...>
31+
# dhcopy = dh1.public_key
32+
# p dhcopy.priv_key #=> nil
33+
def public_key
34+
DH.new(to_der)
35+
end
36+
1337
# :call-seq:
1438
# dh.compute_key(pub_bn) -> string
1539
#
@@ -89,6 +113,22 @@ def new(*args, &blk) # :nodoc:
89113
class DSA
90114
include OpenSSL::Marshal
91115

116+
# :call-seq:
117+
# dsa.public_key -> dsanew
118+
#
119+
# Returns a new DSA instance that carries just the \DSA parameters and the
120+
# public key.
121+
#
122+
# This method is provided for backwards compatibility. In most cases, there
123+
# is no need to call this method.
124+
#
125+
# For the purpose of serializing the public key, to PEM or DER encoding of
126+
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
127+
# PKey#public_to_der.
128+
def public_key
129+
OpenSSL::PKey.read(public_to_der)
130+
end
131+
92132
class << self
93133
# :call-seq:
94134
# DSA.generate(size) -> dsa
@@ -159,6 +199,21 @@ def to_bn(conversion_form = group.point_conversion_form)
159199
class RSA
160200
include OpenSSL::Marshal
161201

202+
# :call-seq:
203+
# rsa.public_key -> rsanew
204+
#
205+
# Returns a new RSA instance that carries just the public key components.
206+
#
207+
# This method is provided for backwards compatibility. In most cases, there
208+
# is no need to call this method.
209+
#
210+
# For the purpose of serializing the public key, to PEM or DER encoding of
211+
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
212+
# PKey#public_to_der.
213+
def public_key
214+
OpenSSL::PKey.read(public_to_der)
215+
end
216+
162217
class << self
163218
# :call-seq:
164219
# RSA.generate(size, exponent = 65537) -> RSA

test/openssl/test_pkey_rsa.rb

Lines changed: 18 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -69,29 +69,28 @@ def test_private
6969
end
7070

7171
def test_new
72-
key = OpenSSL::PKey::RSA.new 512
73-
pem = key.public_key.to_pem
74-
OpenSSL::PKey::RSA.new pem
75-
assert_equal([], OpenSSL.errors)
76-
end
72+
key = OpenSSL::PKey::RSA.new(512)
73+
assert_equal 512, key.n.num_bits
74+
assert_equal 65537, key.e
75+
assert_not_nil key.d
7776

78-
def test_new_exponent_default
79-
assert_equal(65537, OpenSSL::PKey::RSA.new(512).e)
77+
# Specify public exponent
78+
key2 = OpenSSL::PKey::RSA.new(512, 3)
79+
assert_equal 512, key2.n.num_bits
80+
assert_equal 3, key2.e
81+
assert_not_nil key2.d
8082
end
8183

82-
def test_new_with_exponent
83-
1.upto(30) do |idx|
84-
e = (2 ** idx) + 1
85-
key = OpenSSL::PKey::RSA.new(512, e)
86-
assert_equal(e, key.e)
87-
end
88-
end
84+
def test_s_generate
85+
key1 = OpenSSL::PKey::RSA.generate(512)
86+
assert_equal 512, key1.n.num_bits
87+
assert_equal 65537, key1.e
8988

90-
def test_generate
91-
key = OpenSSL::PKey::RSA.generate(512, 17)
92-
assert_equal 512, key.n.num_bits
93-
assert_equal 17, key.e
94-
assert_not_nil key.d
89+
# Specify public exponent
90+
key2 = OpenSSL::PKey::RSA.generate(512, 3)
91+
assert_equal 512, key2.n.num_bits
92+
assert_equal 3, key2.e
93+
assert_not_nil key2.d
9594
end
9695

9796
def test_new_break

0 commit comments

Comments
 (0)