[ruby/openssl] 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.

https://github.com/ruby/openssl/commit/48a6c391ef
This commit is contained in:
Kazuki Yamaguchi 2021-04-15 19:11:32 +09:00
parent 5d1693aac5
commit 3fe8387950
5 changed files with 89 additions and 170 deletions

View File

@ -10,6 +10,30 @@ module OpenSSL::PKey
class DH
include OpenSSL::Marshal
# :call-seq:
# dh.public_key -> dhnew
#
# Returns a new DH instance that carries just the \DH parameters.
#
# Contrary to the method name, the returned DH object contains only
# parameters and not the public key.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of re-generating the key pair while keeping the
# parameters, check OpenSSL::PKey.generate_key.
#
# Example:
# # OpenSSL::PKey::DH.generate by default generates a random key pair
# dh1 = OpenSSL::PKey::DH.generate(2048)
# p dh1.priv_key #=> #<OpenSSL::BN 1288347...>
# dhcopy = dh1.public_key
# p dhcopy.priv_key #=> nil
def public_key
DH.new(to_der)
end
# :call-seq:
# dh.compute_key(pub_bn) -> string
#
@ -89,6 +113,22 @@ module OpenSSL::PKey
class DSA
include OpenSSL::Marshal
# :call-seq:
# dsa.public_key -> dsanew
#
# Returns a new DSA instance that carries just the \DSA parameters and the
# public key.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of serializing the public key, to PEM or DER encoding of
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
# PKey#public_to_der.
def public_key
OpenSSL::PKey.read(public_to_der)
end
class << self
# :call-seq:
# DSA.generate(size) -> dsa
@ -159,6 +199,21 @@ module OpenSSL::PKey
class RSA
include OpenSSL::Marshal
# :call-seq:
# rsa.public_key -> rsanew
#
# Returns a new RSA instance that carries just the public key components.
#
# This method is provided for backwards compatibility. In most cases, there
# is no need to call this method.
#
# For the purpose of serializing the public key, to PEM or DER encoding of
# X.509 SubjectPublicKeyInfo format, check PKey#public_to_pem and
# PKey#public_to_der.
def public_key
OpenSSL::PKey.read(public_to_der)
end
class << self
# :call-seq:
# RSA.generate(size, exponent = 65537) -> RSA

View File

@ -266,48 +266,6 @@ ossl_dh_get_params(VALUE self)
return hash;
}
/*
* call-seq:
* dh.public_key -> aDH
*
* Returns a new DH instance that carries just the public information, i.e.
* the prime _p_ and the generator _g_, but no public/private key yet. Such
* a pair may be generated using DH#generate_key!. The "public key" needed
* for a key exchange with DH#compute_key is considered as per-session
* information and may be retrieved with DH#pub_key once a key pair has
* been generated.
* If the current instance already contains private information (and thus a
* valid public/private key pair), this information will no longer be present
* in the new instance generated by DH#public_key. This feature is helpful for
* publishing the Diffie-Hellman parameters without leaking any of the private
* per-session information.
*
* === Example
* dh = OpenSSL::PKey::DH.new(2048) # has public and private key set
* public_key = dh.public_key # contains only prime and generator
* parameters = public_key.to_der # it's safe to publish this
*/
static VALUE
ossl_dh_to_public_key(VALUE self)
{
EVP_PKEY *pkey;
DH *orig_dh, *dh;
VALUE obj;
obj = rb_obj_alloc(rb_obj_class(self));
GetPKey(obj, pkey);
GetDH(self, orig_dh);
dh = DHparams_dup(orig_dh);
if (!dh)
ossl_raise(eDHError, "DHparams_dup");
if (!EVP_PKEY_assign_DH(pkey, dh)) {
DH_free(dh);
ossl_raise(eDHError, "EVP_PKEY_assign_DH");
}
return obj;
}
/*
* call-seq:
* dh.params_ok? -> true | false
@ -384,14 +342,20 @@ Init_ossl_dh(void)
* The per-session private key, an OpenSSL::BN.
*
* === Example of a key exchange
* dh1 = OpenSSL::PKey::DH.new(2048)
* der = dh1.public_key.to_der #you may send this publicly to the participating party
* dh2 = OpenSSL::PKey::DH.new(der)
* dh2.generate_key! #generate the per-session key pair
* symm_key1 = dh1.compute_key(dh2.pub_key)
* symm_key2 = dh2.compute_key(dh1.pub_key)
* # you may send the parameters (der) and own public key (pub1) publicly
* # to the participating party
* dh1 = OpenSSL::PKey::DH.new(2048)
* der = dh1.to_der
* pub1 = dh1.pub_key
*
* puts symm_key1 == symm_key2 # => true
* # the other party generates its per-session key pair
* dhparams = OpenSSL::PKey::DH.new(der)
* dh2 = OpenSSL::PKey.generate_key(dhparams)
* pub2 = dh2.pub_key
*
* symm_key1 = dh1.compute_key(pub2)
* symm_key2 = dh2.compute_key(pub1)
* puts symm_key1 == symm_key2 # => true
*/
cDH = rb_define_class_under(mPKey, "DH", cPKey);
rb_define_method(cDH, "initialize", ossl_dh_initialize, -1);
@ -402,7 +366,6 @@ Init_ossl_dh(void)
rb_define_alias(cDH, "to_pem", "export");
rb_define_alias(cDH, "to_s", "export");
rb_define_method(cDH, "to_der", ossl_dh_to_der, 0);
rb_define_method(cDH, "public_key", ossl_dh_to_public_key, 0);
rb_define_method(cDH, "params_ok?", ossl_dh_check_params, 0);
DEF_OSSL_PKEY_BN(cDH, dh, p);

View File

@ -264,47 +264,6 @@ ossl_dsa_get_params(VALUE self)
return hash;
}
/*
* call-seq:
* dsa.public_key -> aDSA
*
* Returns a new DSA instance that carries just the public key information.
* If the current instance has also private key information, this will no
* longer be present in the new instance. This feature is helpful for
* publishing the public key information without leaking any of the private
* information.
*
* === Example
* dsa = OpenSSL::PKey::DSA.new(2048) # has public and private information
* pub_key = dsa.public_key # has only the public part available
* pub_key_der = pub_key.to_der # it's safe to publish this
*
*
*/
static VALUE
ossl_dsa_to_public_key(VALUE self)
{
EVP_PKEY *pkey, *pkey_new;
DSA *dsa;
VALUE obj;
GetPKeyDSA(self, pkey);
obj = rb_obj_alloc(rb_obj_class(self));
GetPKey(obj, pkey_new);
#define DSAPublicKey_dup(dsa) (DSA *)ASN1_dup( \
(i2d_of_void *)i2d_DSAPublicKey, (d2i_of_void *)d2i_DSAPublicKey, (char *)(dsa))
dsa = DSAPublicKey_dup(EVP_PKEY_get0_DSA(pkey));
#undef DSAPublicKey_dup
if (!dsa)
ossl_raise(eDSAError, "DSAPublicKey_dup");
if (!EVP_PKEY_assign_DSA(pkey_new, dsa)) {
DSA_free(dsa);
ossl_raise(eDSAError, "EVP_PKEY_assign_DSA");
}
return obj;
}
/*
* call-seq:
* dsa.syssign(string) -> aString
@ -445,7 +404,6 @@ Init_ossl_dsa(void)
rb_define_alias(cDSA, "to_pem", "export");
rb_define_alias(cDSA, "to_s", "export");
rb_define_method(cDSA, "to_der", ossl_dsa_to_der, 0);
rb_define_method(cDSA, "public_key", ossl_dsa_to_public_key, 0);
rb_define_method(cDSA, "syssign", ossl_dsa_sign, 1);
rb_define_method(cDSA, "sysverify", ossl_dsa_verify, 2);

View File

@ -390,7 +390,7 @@ ossl_rsa_private_decrypt(int argc, VALUE *argv, VALUE self)
* data = "Sign me!"
* pkey = OpenSSL::PKey::RSA.new(2048)
* signature = pkey.sign_pss("SHA256", data, salt_length: :max, mgf1_hash: "SHA256")
* pub_key = pkey.public_key
* pub_key = OpenSSL::PKey.read(pkey.public_to_der)
* puts pub_key.verify_pss("SHA256", signature, data,
* salt_length: :auto, mgf1_hash: "SHA256") # => true
*/
@ -587,61 +587,6 @@ ossl_rsa_get_params(VALUE self)
return hash;
}
/*
* call-seq:
* rsa.public_key -> RSA
*
* Makes new RSA instance containing the public key from the private key.
*/
static VALUE
ossl_rsa_to_public_key(VALUE self)
{
EVP_PKEY *pkey, *pkey_new;
RSA *rsa;
VALUE obj;
GetPKeyRSA(self, pkey);
obj = rb_obj_alloc(rb_obj_class(self));
GetPKey(obj, pkey_new);
rsa = RSAPublicKey_dup(EVP_PKEY_get0_RSA(pkey));
if (!rsa)
ossl_raise(eRSAError, "RSAPublicKey_dup");
if (!EVP_PKEY_assign_RSA(pkey_new, rsa)) {
RSA_free(rsa);
ossl_raise(eRSAError, "EVP_PKEY_assign_RSA");
}
return obj;
}
/*
* TODO: Test me
static VALUE
ossl_rsa_blinding_on(VALUE self)
{
RSA *rsa;
GetRSA(self, rsa);
if (RSA_blinding_on(rsa, ossl_bn_ctx) != 1) {
ossl_raise(eRSAError, NULL);
}
return self;
}
static VALUE
ossl_rsa_blinding_off(VALUE self)
{
RSA *rsa;
GetRSA(self, rsa);
RSA_blinding_off(rsa);
return self;
}
*/
/*
* Document-method: OpenSSL::PKey::RSA#set_key
* call-seq:
@ -712,7 +657,6 @@ Init_ossl_rsa(void)
rb_define_alias(cRSA, "to_pem", "export");
rb_define_alias(cRSA, "to_s", "export");
rb_define_method(cRSA, "to_der", ossl_rsa_to_der, 0);
rb_define_method(cRSA, "public_key", ossl_rsa_to_public_key, 0);
rb_define_method(cRSA, "public_encrypt", ossl_rsa_public_encrypt, -1);
rb_define_method(cRSA, "public_decrypt", ossl_rsa_public_decrypt, -1);
rb_define_method(cRSA, "private_encrypt", ossl_rsa_private_encrypt, -1);

View File

@ -69,29 +69,28 @@ class OpenSSL::TestPKeyRSA < OpenSSL::PKeyTestCase
end
def test_new
key = OpenSSL::PKey::RSA.new 512
pem = key.public_key.to_pem
OpenSSL::PKey::RSA.new pem
assert_equal([], OpenSSL.errors)
end
def test_new_exponent_default
assert_equal(65537, OpenSSL::PKey::RSA.new(512).e)
end
def test_new_with_exponent
1.upto(30) do |idx|
e = (2 ** idx) + 1
key = OpenSSL::PKey::RSA.new(512, e)
assert_equal(e, key.e)
end
end
def test_generate
key = OpenSSL::PKey::RSA.generate(512, 17)
key = OpenSSL::PKey::RSA.new(512)
assert_equal 512, key.n.num_bits
assert_equal 17, key.e
assert_equal 65537, key.e
assert_not_nil key.d
# Specify public exponent
key2 = OpenSSL::PKey::RSA.new(512, 3)
assert_equal 512, key2.n.num_bits
assert_equal 3, key2.e
assert_not_nil key2.d
end
def test_s_generate
key1 = OpenSSL::PKey::RSA.generate(512)
assert_equal 512, key1.n.num_bits
assert_equal 65537, key1.e
# Specify public exponent
key2 = OpenSSL::PKey::RSA.generate(512, 3)
assert_equal 512, key2.n.num_bits
assert_equal 3, key2.e
assert_not_nil key2.d
end
def test_new_break