Skip to content

Commit

Permalink
pkey: implement {DH,DSA,RSA}#public_key in Ruby
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
rhenium committed Apr 15, 2021
1 parent e0b4c56 commit 48a6c39
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 168 deletions.
63 changes: 13 additions & 50 deletions ext/openssl/ossl_pkey_dh.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
*
* # 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
*
* puts symm_key1 == symm_key2 # => true
* 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);
Expand All @@ -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);
Expand Down
42 changes: 0 additions & 42 deletions ext/openssl/ossl_pkey_dsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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);

Expand Down
58 changes: 1 addition & 57 deletions ext/openssl/ossl_pkey_rsa.c
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -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:
Expand Down Expand Up @@ -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);
Expand Down
55 changes: 55 additions & 0 deletions lib/openssl/pkey.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
#
Expand Down Expand Up @@ -89,6 +113,22 @@ def new(*args, &blk) # :nodoc:
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
Expand Down Expand Up @@ -159,6 +199,21 @@ def to_bn(conversion_form = group.point_conversion_form)
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
Expand Down
37 changes: 18 additions & 19 deletions test/openssl/test_pkey_rsa.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,29 +69,28 @@ def test_private
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
key = OpenSSL::PKey::RSA.new(512)
assert_equal 512, key.n.num_bits
assert_equal 65537, key.e
assert_not_nil key.d

def test_new_exponent_default
assert_equal(65537, OpenSSL::PKey::RSA.new(512).e)
# 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_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_s_generate
key1 = OpenSSL::PKey::RSA.generate(512)
assert_equal 512, key1.n.num_bits
assert_equal 65537, key1.e

def test_generate
key = OpenSSL::PKey::RSA.generate(512, 17)
assert_equal 512, key.n.num_bits
assert_equal 17, key.e
assert_not_nil key.d
# 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
Expand Down

0 comments on commit 48a6c39

Please sign in to comment.