Blob Blame History Raw
From e8504c6248c4b0e5e961f57f004e1133c20c88a5 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Mon, 5 Apr 2021 00:30:01 +0900
Subject: [PATCH 1/5] pkey: fix interrupt handling in
 OpenSSL::PKey.generate_key

rb_thread_call_without_gvl() can be interrupted, but it may be able to
resume the operation. Call rb_thread_check_ints() to see if it raises
an exception or not.
---
 ext/openssl/ossl_pkey.c | 18 +++++++++++++-----
 1 file changed, 13 insertions(+), 5 deletions(-)

diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index 22e9f19982..d76f0600d1 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -239,7 +239,7 @@ struct pkey_blocking_generate_arg {
     int state;
     int yield: 1;
     int genparam: 1;
-    int stop: 1;
+    int interrupted: 1;
 };
 
 static VALUE
@@ -261,23 +261,31 @@ static int
 pkey_gen_cb(EVP_PKEY_CTX *ctx)
 {
     struct pkey_blocking_generate_arg *arg = EVP_PKEY_CTX_get_app_data(ctx);
+    int state;
 
     if (arg->yield) {
-        int state;
         rb_protect(pkey_gen_cb_yield, (VALUE)ctx, &state);
         if (state) {
-            arg->stop = 1;
             arg->state = state;
+            return 0;
+        }
+    }
+    if (arg->interrupted) {
+        arg->interrupted = 0;
+        state = (int)(VALUE)rb_thread_call_with_gvl(call_check_ints, NULL);
+        if (state) {
+            arg->state = state;
+            return 0;
         }
     }
-    return !arg->stop;
+    return 1;
 }
 
 static void
 pkey_blocking_gen_stop(void *ptr)
 {
     struct pkey_blocking_generate_arg *arg = ptr;
-    arg->stop = 1;
+    arg->interrupted = 1;
 }
 
 static void *
-- 
2.32.0


From f433d1b680e7ac5ef13fc15b0844267222438cf3 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Sun, 17 May 2020 20:48:23 +0900
Subject: [PATCH 2/5] 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 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 accepted by default
in OpenSSL 3.0. The test code is updated accordingly.
---
 ext/openssl/lib/openssl/pkey.rb |  57 ++++++++++
 ext/openssl/ossl_pkey_dh.c      | 186 ++++++--------------------------
 test/openssl/test_pkey_dh.rb    |  15 ++-
 3 files changed, 101 insertions(+), 157 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index be60ac2beb..5a3d0ed1ef 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -27,6 +27,63 @@ def compute_key(pub_bn)
       peer.set_key(pub_bn, nil)
       derive(peer)
     end
+
+    # :call-seq:
+    #    dh.generate_key! -> self
+    #
+    # Generates a private and public key unless a private key already exists.
+    # If this DH instance was generated from public \DH parameters (e.g. by
+    # encoding the result of DH#public_key), then this method needs to be
+    # called first in order to generate the per-session keys before performing
+    # the actual key exchange.
+    #
+    # See also OpenSSL::PKey.generate_key.
+    #
+    # Example:
+    #   dh = OpenSSL::PKey::DH.new(2048)
+    #   public_key = dh.public_key #contains no private/public key yet
+    #   public_key.generate_key!
+    #   puts public_key.private? # => true
+    def generate_key!
+      unless priv_key
+        tmp = OpenSSL::PKey.generate_key(self)
+        set_key(tmp.pub_key, tmp.priv_key)
+      end
+      self
+    end
+
+    class << self
+      # :call-seq:
+      #    DH.generate(size, generator = 2) -> dh
+      #
+      # Creates a new DH instance from scratch by generating random parameters
+      # and a key pair.
+      #
+      # See also OpenSSL::PKey.generate_parameters and
+      # OpenSSL::PKey.generate_key.
+      #
+      # +size+::
+      #   The desired key size in bits.
+      # +generator+::
+      #   The generator.
+      def generate(size, generator = 2, &blk)
+        dhparams = OpenSSL::PKey.generate_parameters("DH", {
+          "dh_paramgen_prime_len" => size,
+          "dh_paramgen_generator" => generator,
+        }, &blk)
+        OpenSSL::PKey.generate_key(dhparams)
+      end
+
+      # Handle DH.new(size, generator) form here; new(str) and new() forms
+      # are handled by #initialize
+      def new(*args, &blk) # :nodoc:
+        if args[0].is_a?(Integer)
+          generate(*args, &blk)
+        else
+          super
+        end
+      end
+    end
   end
 
   class DSA
diff --git a/ext/openssl/ossl_pkey_dh.c b/ext/openssl/ossl_pkey_dh.c
index 5bc1c49ca1..6b477b077c 100644
--- a/ext/openssl/ossl_pkey_dh.c
+++ b/ext/openssl/ossl_pkey_dh.c
@@ -32,147 +32,56 @@ VALUE eDHError;
 /*
  * Private
  */
-struct dh_blocking_gen_arg {
-    DH *dh;
-    int size;
-    int gen;
-    BN_GENCB *cb;
-    int result;
-};
-
-static void *
-dh_blocking_gen(void *arg)
-{
-    struct dh_blocking_gen_arg *gen = (struct dh_blocking_gen_arg *)arg;
-    gen->result = DH_generate_parameters_ex(gen->dh, gen->size, gen->gen, gen->cb);
-    return 0;
-}
-
-static DH *
-dh_generate(int size, int gen)
-{
-    struct ossl_generate_cb_arg cb_arg = { 0 };
-    struct dh_blocking_gen_arg gen_arg;
-    DH *dh = DH_new();
-    BN_GENCB *cb = BN_GENCB_new();
-
-    if (!dh || !cb) {
-	DH_free(dh);
-	BN_GENCB_free(cb);
-        ossl_raise(eDHError, "malloc failure");
-    }
-
-    if (rb_block_given_p())
-	cb_arg.yield = 1;
-    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
-    gen_arg.dh = dh;
-    gen_arg.size = size;
-    gen_arg.gen = gen;
-    gen_arg.cb = cb;
-    if (cb_arg.yield == 1) {
-	/* we cannot release GVL when callback proc is supplied */
-	dh_blocking_gen(&gen_arg);
-    } else {
-	/* there's a chance to unblock */
-	rb_thread_call_without_gvl(dh_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
-    }
-
-    BN_GENCB_free(cb);
-    if (!gen_arg.result) {
-	DH_free(dh);
-	if (cb_arg.state) {
-	    /* Clear OpenSSL error queue before re-raising. */
-	    ossl_clear_error();
-	    rb_jump_tag(cb_arg.state);
-	}
-        ossl_raise(eDHError, "DH_generate_parameters_ex");
-    }
-
-    if (!DH_generate_key(dh)) {
-        DH_free(dh);
-        ossl_raise(eDHError, "DH_generate_key");
-    }
-
-    return dh;
-}
-
-/*
- *  call-seq:
- *     DH.generate(size [, generator]) -> dh
- *
- * Creates a new DH instance from scratch by generating the private and public
- * components alike.
- *
- * === Parameters
- * * _size_ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
- * * _generator_ is a small number > 1, typically 2 or 5.
- *
- */
-static VALUE
-ossl_dh_s_generate(int argc, VALUE *argv, VALUE klass)
-{
-    EVP_PKEY *pkey;
-    DH *dh ;
-    int g = 2;
-    VALUE size, gen, obj;
-
-    if (rb_scan_args(argc, argv, "11", &size, &gen) == 2) {
-	g = NUM2INT(gen);
-    }
-    obj = rb_obj_alloc(klass);
-    GetPKey(obj, pkey);
-
-    dh = dh_generate(NUM2INT(size), g);
-    if (!EVP_PKEY_assign_DH(pkey, dh)) {
-        DH_free(dh);
-        ossl_raise(eDHError, "EVP_PKEY_assign_DH");
-    }
-    return obj;
-}
-
 /*
  * call-seq:
  *   DH.new -> dh
  *   DH.new(string) -> dh
  *   DH.new(size [, generator]) -> dh
  *
- * Either generates a DH instance from scratch or by reading already existing
- * DH parameters from _string_. Note that when reading a DH instance from
- * data that was encoded from a DH instance by using DH#to_pem or DH#to_der
- * the result will *not* contain a public/private key pair yet. This needs to
- * be generated using DH#generate_key! first.
+ * Creates a new instance of OpenSSL::PKey::DH.
+ *
+ * If called without arguments, an empty instance without any parameter or key
+ * components is created. Use #set_pqg to manually set the parameters afterwards
+ * (and optionally #set_key to set private and public key components).
+ *
+ * If a String is given, tries to parse it as a DER- or PEM- encoded parameters.
+ * See also OpenSSL::PKey.read which can parse keys of any kinds.
+ *
+ * The DH.new(size [, generator]) form is an alias of DH.generate.
  *
- * === Parameters
- * * _size_ is an integer representing the desired key size. Keys smaller than 1024 bits should be considered insecure.
- * * _generator_ is a small number > 1, typically 2 or 5.
- * * _string_ contains the DER or PEM encoded key.
+ * +string+::
+ *   A String that contains the DER or PEM encoded key.
+ * +size+::
+ *   See DH.generate.
+ * +generator+::
+ *   See DH.generate.
  *
- * === Examples
- *  DH.new # -> dh
- *  DH.new(1024) # -> dh
- *  DH.new(1024, 5) # -> dh
- *  #Reading DH parameters
- *  dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet
- *  dh.generate_key! # -> dh with public and private key
+ * Examples:
+ *   # Creating an instance from scratch
+ *   dh = DH.new
+ *   dh.set_pqg(bn_p, nil, bn_g)
+ *
+ *   # Generating a parameters and a key pair
+ *   dh = DH.new(2048) # An alias of DH.generate(2048)
+ *
+ *   # Reading DH parameters
+ *   dh = DH.new(File.read('parameters.pem')) # -> dh, but no public/private key yet
+ *   dh.generate_key! # -> dh with public and private key
  */
 static VALUE
 ossl_dh_initialize(int argc, VALUE *argv, VALUE self)
 {
     EVP_PKEY *pkey;
     DH *dh;
-    int g = 2;
     BIO *in;
-    VALUE arg, gen;
+    VALUE arg;
 
     GetPKey(self, pkey);
-    if(rb_scan_args(argc, argv, "02", &arg, &gen) == 0) {
-      dh = DH_new();
-    }
-    else if (RB_INTEGER_TYPE_P(arg)) {
-	if (!NIL_P(gen)) {
-	    g = NUM2INT(gen);
-	}
-        dh = dh_generate(NUM2INT(arg), g);
+    /* The DH.new(size, generator) form is handled by lib/openssl/pkey.rb */
+    if (rb_scan_args(argc, argv, "01", &arg) == 0) {
+        dh = DH_new();
+        if (!dh)
+            ossl_raise(eDHError, "DH_new");
     }
     else {
 	arg = ossl_to_der_if_possible(arg);
@@ -449,33 +358,6 @@ ossl_dh_check_params(VALUE self)
     return codes == 0 ? Qtrue : Qfalse;
 }
 
-/*
- *  call-seq:
- *     dh.generate_key! -> self
- *
- * Generates a private and public key unless a private key already exists.
- * If this DH instance was generated from public DH parameters (e.g. by
- * encoding the result of DH#public_key), then this method needs to be
- * called first in order to generate the per-session keys before performing
- * the actual key exchange.
- *
- * === Example
- *   dh = OpenSSL::PKey::DH.new(2048)
- *   public_key = dh.public_key #contains no private/public key yet
- *   public_key.generate_key!
- *   puts public_key.private? # => true
- */
-static VALUE
-ossl_dh_generate_key(VALUE self)
-{
-    DH *dh;
-
-    GetDH(self, dh);
-    if (!DH_generate_key(dh))
-	ossl_raise(eDHError, "Failed to generate key");
-    return self;
-}
-
 /*
  * Document-method: OpenSSL::PKey::DH#set_pqg
  * call-seq:
@@ -540,7 +422,6 @@ Init_ossl_dh(void)
      *  puts symm_key1 == symm_key2 # => true
      */
     cDH = rb_define_class_under(mPKey, "DH", cPKey);
-    rb_define_singleton_method(cDH, "generate", ossl_dh_s_generate, -1);
     rb_define_method(cDH, "initialize", ossl_dh_initialize, -1);
     rb_define_method(cDH, "initialize_copy", ossl_dh_initialize_copy, 1);
     rb_define_method(cDH, "public?", ossl_dh_is_public, 0);
@@ -552,7 +433,6 @@ Init_ossl_dh(void)
     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);
-    rb_define_method(cDH, "generate_key!", ossl_dh_generate_key, 0);
 
     DEF_OSSL_PKEY_BN(cDH, dh, p);
     DEF_OSSL_PKEY_BN(cDH, dh, q);
diff --git a/test/openssl/test_pkey_dh.rb b/test/openssl/test_pkey_dh.rb
index 9efc3ba68d..279ce1984c 100644
--- a/test/openssl/test_pkey_dh.rb
+++ b/test/openssl/test_pkey_dh.rb
@@ -4,12 +4,19 @@
 if defined?(OpenSSL) && defined?(OpenSSL::PKey::DH)
 
 class OpenSSL::TestPKeyDH < OpenSSL::PKeyTestCase
-  NEW_KEYLEN = 256
+  NEW_KEYLEN = 2048
 
-  def test_new
+  def test_new_empty
+    dh = OpenSSL::PKey::DH.new
+    assert_equal nil, dh.p
+    assert_equal nil, dh.priv_key
+  end
+
+  def test_new_generate
+    # This test is slow
     dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
     assert_key(dh)
-  end
+  end if ENV["OSSL_TEST_ALL"]
 
   def test_new_break
     assert_nil(OpenSSL::PKey::DH.new(NEW_KEYLEN) { break })
@@ -80,7 +87,7 @@ def test_key_exchange
   end
 
   def test_dup
-    dh = OpenSSL::PKey::DH.new(NEW_KEYLEN)
+    dh = Fixtures.pkey("dh1024")
     dh2 = dh.dup
     assert_equal dh.to_der, dh2.to_der # params
     assert_equal_params dh, dh2 # keys
-- 
2.32.0


From ba1d1d68ac2b489691eb3fe2052e77b3e57a372b Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Sun, 17 May 2020 20:48:23 +0900
Subject: [PATCH 3/5] pkey/rsa: use high level EVP interface to generate
 parameters and keys

Implement PKey::RSA.new(size, exponent) and PKey::RSA.generate using
OpenSSL::PKey.generate_key instead of the low level RSA functions.
---
 ext/openssl/lib/openssl/pkey.rb |  30 ++++++++
 ext/openssl/ossl_pkey_rsa.c     | 132 ++++----------------------------
 2 files changed, 46 insertions(+), 116 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index 5a3d0ed1ef..3bef06e3b3 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -128,5 +128,35 @@ def to_bn(conversion_form = group.point_conversion_form)
 
   class RSA
     include OpenSSL::Marshal
+
+    class << self
+      # :call-seq:
+      #    RSA.generate(size, exponent = 65537) -> RSA
+      #
+      # Generates an \RSA keypair.
+      #
+      # See also OpenSSL::PKey.generate_key.
+      #
+      # +size+::
+      #   The desired key size in bits.
+      # +exponent+::
+      #   An odd Integer, normally 3, 17, or 65537.
+      def generate(size, exp = 0x10001, &blk)
+        OpenSSL::PKey.generate_key("RSA", {
+          "rsa_keygen_bits" => size,
+          "rsa_keygen_pubexp" => exp,
+        }, &blk)
+      end
+
+      # Handle RSA.new(size, exponent) form here; new(str) and new() forms
+      # are handled by #initialize
+      def new(*args, &blk) # :nodoc:
+        if args[0].is_a?(Integer)
+          generate(*args, &blk)
+        else
+          super
+        end
+      end
+    end
   end
 end
diff --git a/ext/openssl/ossl_pkey_rsa.c b/ext/openssl/ossl_pkey_rsa.c
index 3c298a2aea..43f82cb29e 100644
--- a/ext/openssl/ossl_pkey_rsa.c
+++ b/ext/openssl/ossl_pkey_rsa.c
@@ -47,125 +47,28 @@ VALUE eRSAError;
 /*
  * Private
  */
-struct rsa_blocking_gen_arg {
-    RSA *rsa;
-    BIGNUM *e;
-    int size;
-    BN_GENCB *cb;
-    int result;
-};
-
-static void *
-rsa_blocking_gen(void *arg)
-{
-    struct rsa_blocking_gen_arg *gen = (struct rsa_blocking_gen_arg *)arg;
-    gen->result = RSA_generate_key_ex(gen->rsa, gen->size, gen->e, gen->cb);
-    return 0;
-}
-
-static RSA *
-rsa_generate(int size, unsigned long exp)
-{
-    int i;
-    struct ossl_generate_cb_arg cb_arg = { 0 };
-    struct rsa_blocking_gen_arg gen_arg;
-    RSA *rsa = RSA_new();
-    BIGNUM *e = BN_new();
-    BN_GENCB *cb = BN_GENCB_new();
-
-    if (!rsa || !e || !cb) {
-	RSA_free(rsa);
-	BN_free(e);
-	BN_GENCB_free(cb);
-        ossl_raise(eRSAError, "malloc failure");
-    }
-    for (i = 0; i < (int)sizeof(exp) * 8; ++i) {
-	if (exp & (1UL << i)) {
-	    if (BN_set_bit(e, i) == 0) {
-		BN_free(e);
-		RSA_free(rsa);
-		BN_GENCB_free(cb);
-                ossl_raise(eRSAError, "BN_set_bit");
-	    }
-	}
-    }
-
-    if (rb_block_given_p())
-	cb_arg.yield = 1;
-    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
-    gen_arg.rsa = rsa;
-    gen_arg.e = e;
-    gen_arg.size = size;
-    gen_arg.cb = cb;
-    if (cb_arg.yield == 1) {
-	/* we cannot release GVL when callback proc is supplied */
-	rsa_blocking_gen(&gen_arg);
-    } else {
-	/* there's a chance to unblock */
-	rb_thread_call_without_gvl(rsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
-    }
-
-    BN_GENCB_free(cb);
-    BN_free(e);
-    if (!gen_arg.result) {
-	RSA_free(rsa);
-	if (cb_arg.state) {
-	    /* must clear OpenSSL error stack */
-	    ossl_clear_error();
-	    rb_jump_tag(cb_arg.state);
-	}
-        ossl_raise(eRSAError, "RSA_generate_key_ex");
-    }
-
-    return rsa;
-}
-
 /*
  * call-seq:
- *   RSA.generate(size)           => RSA instance
- *   RSA.generate(size, exponent) => RSA instance
+ *   RSA.new -> rsa
+ *   RSA.new(encoded_key [, passphrase]) -> rsa
+ *   RSA.new(encoded_key) { passphrase } -> rsa
+ *   RSA.new(size [, exponent]) -> rsa
  *
- * Generates an RSA keypair.  _size_ is an integer representing the desired key
- * size.  Keys smaller than 1024 should be considered insecure.  _exponent_ is
- * an odd number normally 3, 17, or 65537.
- */
-static VALUE
-ossl_rsa_s_generate(int argc, VALUE *argv, VALUE klass)
-{
-/* why does this method exist?  why can't initialize take an optional exponent? */
-    EVP_PKEY *pkey;
-    RSA *rsa;
-    VALUE size, exp;
-    VALUE obj;
-
-    rb_scan_args(argc, argv, "11", &size, &exp);
-    obj = rb_obj_alloc(klass);
-    GetPKey(obj, pkey);
-
-    rsa = rsa_generate(NUM2INT(size), NIL_P(exp) ? RSA_F4 : NUM2ULONG(exp));
-    if (!EVP_PKEY_assign_RSA(pkey, rsa)) {
-        RSA_free(rsa);
-        ossl_raise(eRSAError, "EVP_PKEY_assign_RSA");
-    }
-    return obj;
-}
-
-/*
- * call-seq:
- *   RSA.new(size [, exponent])        => RSA instance
- *   RSA.new(encoded_key)              => RSA instance
- *   RSA.new(encoded_key, pass_phrase) => RSA instance
+ * Generates or loads an \RSA keypair.
  *
- * Generates or loads an RSA keypair.  If an integer _key_size_ is given it
- * represents the desired key size.  Keys less than 1024 bits should be
- * considered insecure.
+ * If called without arguments, creates a new instance with no key components
+ * set. They can be set individually by #set_key, #set_factors, and
+ * #set_crt_params.
  *
- * A key can instead be loaded from an _encoded_key_ which must be PEM or DER
- * encoded.  A _pass_phrase_ can be used to decrypt the key.  If none is given
- * OpenSSL will prompt for the pass phrase.
+ * If called with a String, tries to parse as DER or PEM encoding of an \RSA key.
+ * Note that, if _passphrase_ is not specified but the key is encrypted with a
+ * passphrase, \OpenSSL will prompt for it.
+ * See also OpenSSL::PKey.read which can parse keys of any kinds.
  *
- * = Examples
+ * If called with a number, generates a new key pair. This form works as an
+ * alias of RSA.generate.
  *
+ * Examples:
  *   OpenSSL::PKey::RSA.new 2048
  *   OpenSSL::PKey::RSA.new File.read 'rsa.pem'
  *   OpenSSL::PKey::RSA.new File.read('rsa.pem'), 'my pass phrase'
@@ -179,15 +82,13 @@ ossl_rsa_initialize(int argc, VALUE *argv, VALUE self)
     VALUE arg, pass;
 
     GetPKey(self, pkey);
+    /* The RSA.new(size, generator) form is handled by lib/openssl/pkey.rb */
     rb_scan_args(argc, argv, "02", &arg, &pass);
     if (argc == 0) {
 	rsa = RSA_new();
         if (!rsa)
             ossl_raise(eRSAError, "RSA_new");
     }
-    else if (RB_INTEGER_TYPE_P(arg)) {
-	rsa = rsa_generate(NUM2INT(arg), NIL_P(pass) ? RSA_F4 : NUM2ULONG(pass));
-    }
     else {
 	pass = ossl_pem_passwd_value(pass);
 	arg = ossl_to_der_if_possible(arg);
@@ -832,7 +733,6 @@ Init_ossl_rsa(void)
      */
     cRSA = rb_define_class_under(mPKey, "RSA", cPKey);
 
-    rb_define_singleton_method(cRSA, "generate", ossl_rsa_s_generate, -1);
     rb_define_method(cRSA, "initialize", ossl_rsa_initialize, -1);
     rb_define_method(cRSA, "initialize_copy", ossl_rsa_initialize_copy, 1);
 
-- 
2.32.0


From a6c4a8116c09243c39cc8d1e7ececcd8be0cfaf2 Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Sun, 17 May 2020 22:14:03 +0900
Subject: [PATCH 4/5] pkey/dsa: use high level EVP interface to generate
 parameters and keys

Implement PKey::DSA.new(size) and PKey::DSA.generate using
OpenSSL::PKey.generate_parameters and .generate_key instead of the low
level DSA functions.
---
 ext/openssl/lib/openssl/pkey.rb |  30 +++++++
 ext/openssl/ossl_pkey_dsa.c     | 140 ++++++--------------------------
 test/openssl/test_pkey_dsa.rb   |  23 ++----
 3 files changed, 64 insertions(+), 129 deletions(-)

diff --git a/ext/openssl/lib/openssl/pkey.rb b/ext/openssl/lib/openssl/pkey.rb
index 3bef06e3b3..53ee52f98b 100644
--- a/ext/openssl/lib/openssl/pkey.rb
+++ b/ext/openssl/lib/openssl/pkey.rb
@@ -88,6 +88,36 @@ def new(*args, &blk) # :nodoc:
 
   class DSA
     include OpenSSL::Marshal
+
+    class << self
+      # :call-seq:
+      #    DSA.generate(size) -> dsa
+      #
+      # Creates a new DSA instance by generating a private/public key pair
+      # from scratch.
+      #
+      # See also OpenSSL::PKey.generate_parameters and
+      # OpenSSL::PKey.generate_key.
+      #
+      # +size+::
+      #   The desired key size in bits.
+      def generate(size, &blk)
+        dsaparams = OpenSSL::PKey.generate_parameters("DSA", {
+          "dsa_paramgen_bits" => size,
+        }, &blk)
+        OpenSSL::PKey.generate_key(dsaparams)
+      end
+
+      # Handle DSA.new(size) form here; new(str) and new() forms
+      # are handled by #initialize
+      def new(*args, &blk) # :nodoc:
+        if args[0].is_a?(Integer)
+          generate(*args, &blk)
+        else
+          super
+        end
+      end
+    end
   end
 
   if defined?(EC)
diff --git a/ext/openssl/ossl_pkey_dsa.c b/ext/openssl/ossl_pkey_dsa.c
index 0e68f7f27f..1c5a8a737e 100644
--- a/ext/openssl/ossl_pkey_dsa.c
+++ b/ext/openssl/ossl_pkey_dsa.c
@@ -46,126 +46,39 @@ VALUE eDSAError;
 /*
  * Private
  */
-struct dsa_blocking_gen_arg {
-    DSA *dsa;
-    int size;
-    int *counter;
-    unsigned long *h;
-    BN_GENCB *cb;
-    int result;
-};
-
-static void *
-dsa_blocking_gen(void *arg)
-{
-    struct dsa_blocking_gen_arg *gen = (struct dsa_blocking_gen_arg *)arg;
-    gen->result = DSA_generate_parameters_ex(gen->dsa, gen->size, NULL, 0,
-					     gen->counter, gen->h, gen->cb);
-    return 0;
-}
-
-static DSA *
-dsa_generate(int size)
-{
-    struct ossl_generate_cb_arg cb_arg = { 0 };
-    struct dsa_blocking_gen_arg gen_arg;
-    DSA *dsa = DSA_new();
-    BN_GENCB *cb = BN_GENCB_new();
-    int counter;
-    unsigned long h;
-
-    if (!dsa || !cb) {
-        DSA_free(dsa);
-        BN_GENCB_free(cb);
-        ossl_raise(eDSAError, "malloc failure");
-    }
-
-    if (rb_block_given_p())
-	cb_arg.yield = 1;
-    BN_GENCB_set(cb, ossl_generate_cb_2, &cb_arg);
-    gen_arg.dsa = dsa;
-    gen_arg.size = size;
-    gen_arg.counter = &counter;
-    gen_arg.h = &h;
-    gen_arg.cb = cb;
-    if (cb_arg.yield == 1) {
-	/* we cannot release GVL when callback proc is supplied */
-	dsa_blocking_gen(&gen_arg);
-    } else {
-	/* there's a chance to unblock */
-	rb_thread_call_without_gvl(dsa_blocking_gen, &gen_arg, ossl_generate_cb_stop, &cb_arg);
-    }
-
-    BN_GENCB_free(cb);
-    if (!gen_arg.result) {
-	DSA_free(dsa);
-	if (cb_arg.state) {
-	    /* Clear OpenSSL error queue before re-raising. By the way, the
-	     * documentation of DSA_generate_parameters_ex() says the error code
-	     * can be obtained by ERR_get_error(), but the default
-	     * implementation, dsa_builtin_paramgen() doesn't put any error... */
-	    ossl_clear_error();
-	    rb_jump_tag(cb_arg.state);
-	}
-        ossl_raise(eDSAError, "DSA_generate_parameters_ex");
-    }
-
-    if (!DSA_generate_key(dsa)) {
-        DSA_free(dsa);
-        ossl_raise(eDSAError, "DSA_generate_key");
-    }
-
-    return dsa;
-}
-
-/*
- *  call-seq:
- *    DSA.generate(size) -> dsa
- *
- * Creates a new DSA instance by generating a private/public key pair
- * from scratch.
- *
- * === Parameters
- * * _size_ is an integer representing the desired key size.
- *
- */
-static VALUE
-ossl_dsa_s_generate(VALUE klass, VALUE size)
-{
-    EVP_PKEY *pkey;
-    DSA *dsa;
-    VALUE obj;
-
-    obj = rb_obj_alloc(klass);
-    GetPKey(obj, pkey);
-
-    dsa = dsa_generate(NUM2INT(size));
-    if (!EVP_PKEY_assign_DSA(pkey, dsa)) {
-        DSA_free(dsa);
-        ossl_raise(eDSAError, "EVP_PKEY_assign_DSA");
-    }
-    return obj;
-}
-
 /*
  *  call-seq:
  *    DSA.new -> dsa
- *    DSA.new(size) -> dsa
  *    DSA.new(string [, pass]) -> dsa
+ *    DSA.new(size) -> dsa
  *
  * Creates a new DSA instance by reading an existing key from _string_.
  *
- * === Parameters
- * * _size_ is an integer representing the desired key size.
- * * _string_ contains a DER or PEM encoded key.
- * * _pass_ is a string that contains an optional password.
+ * If called without arguments, creates a new instance with no key components
+ * set. They can be set individually by #set_pqg and #set_key.
  *
- * === Examples
- *  DSA.new -> dsa
- *  DSA.new(1024) -> dsa
- *  DSA.new(File.read('dsa.pem')) -> dsa
- *  DSA.new(File.read('dsa.pem'), 'mypassword') -> dsa
+ * If called with a String, tries to parse as DER or PEM encoding of a \DSA key.
+ * See also OpenSSL::PKey.read which can parse keys of any kinds.
+ *
+ * If called with a number, generates random parameters and a key pair. This
+ * form works as an alias of DSA.generate.
+ *
+ * +string+::
+ *   A String that contains a DER or PEM encoded key.
+ * +pass+::
+ *   A String that contains an optional password.
+ * +size+::
+ *   See DSA.generate.
  *
+ * Examples:
+ *   p OpenSSL::PKey::DSA.new(1024)
+ *   #=> #<OpenSSL::PKey::DSA:0x000055a8d6025bf0 oid=DSA>
+ *
+ *   p OpenSSL::PKey::DSA.new(File.read('dsa.pem'))
+ *   #=> #<OpenSSL::PKey::DSA:0x000055555d6b8110 oid=DSA>
+ *
+ *   p OpenSSL::PKey::DSA.new(File.read('dsa.pem'), 'mypassword')
+ *   #=> #<OpenSSL::PKey::DSA:0x0000556f973c40b8 oid=DSA>
  */
 static VALUE
 ossl_dsa_initialize(int argc, VALUE *argv, VALUE self)
@@ -176,15 +89,13 @@ ossl_dsa_initialize(int argc, VALUE *argv, VALUE self)
     VALUE arg, pass;
 
     GetPKey(self, pkey);
+    /* The DSA.new(size, generator) form is handled by lib/openssl/pkey.rb */
     rb_scan_args(argc, argv, "02", &arg, &pass);
     if (argc == 0) {
         dsa = DSA_new();
         if (!dsa)
             ossl_raise(eDSAError, "DSA_new");
     }
-    else if (argc == 1 && RB_INTEGER_TYPE_P(arg)) {
-        dsa = dsa_generate(NUM2INT(arg));
-    }
     else {
 	pass = ossl_pem_passwd_value(pass);
 	arg = ossl_to_der_if_possible(arg);
@@ -553,7 +464,6 @@ Init_ossl_dsa(void)
      */
     cDSA = rb_define_class_under(mPKey, "DSA", cPKey);
 
-    rb_define_singleton_method(cDSA, "generate", ossl_dsa_s_generate, 1);
     rb_define_method(cDSA, "initialize", ossl_dsa_initialize, -1);
     rb_define_method(cDSA, "initialize_copy", ossl_dsa_initialize_copy, 1);
 
diff --git a/test/openssl/test_pkey_dsa.rb b/test/openssl/test_pkey_dsa.rb
index 4bf8a7b374..85bb6ec0ae 100644
--- a/test/openssl/test_pkey_dsa.rb
+++ b/test/openssl/test_pkey_dsa.rb
@@ -5,31 +5,26 @@
 
 class OpenSSL::TestPKeyDSA < OpenSSL::PKeyTestCase
   def test_private
-    key = OpenSSL::PKey::DSA.new(256)
-    assert(key.private?)
+    key = Fixtures.pkey("dsa1024")
+    assert_equal true, key.private?
     key2 = OpenSSL::PKey::DSA.new(key.to_der)
-    assert(key2.private?)
+    assert_equal true, key2.private?
     key3 = key.public_key
-    assert(!key3.private?)
+    assert_equal false, key3.private?
     key4 = OpenSSL::PKey::DSA.new(key3.to_der)
-    assert(!key4.private?)
+    assert_equal false, key4.private?
   end
 
   def test_new
-    key = OpenSSL::PKey::DSA.new 256
+    key = OpenSSL::PKey::DSA.new(2048)
     pem  = key.public_key.to_pem
     OpenSSL::PKey::DSA.new pem
-    if $0 == __FILE__
-      assert_nothing_raised {
-        key = OpenSSL::PKey::DSA.new 2048
-      }
-    end
   end
 
   def test_new_break
-    assert_nil(OpenSSL::PKey::DSA.new(512) { break })
+    assert_nil(OpenSSL::PKey::DSA.new(2048) { break })
     assert_raise(RuntimeError) do
-      OpenSSL::PKey::DSA.new(512) { raise }
+      OpenSSL::PKey::DSA.new(2048) { raise }
     end
   end
 
@@ -184,7 +179,7 @@ def test_read_DSAPublicKey_pem
   end
 
   def test_dup
-    key = OpenSSL::PKey::DSA.new(256)
+    key = Fixtures.pkey("dsa1024")
     key2 = key.dup
     assert_equal key.params, key2.params
     key2.set_pqg(key2.p + 1, key2.q, key2.g)
-- 
2.32.0


From ba5a3a5c3eabf969f5cd2232b022e440af803b5b Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@rhe.jp>
Date: Mon, 5 Apr 2021 00:39:04 +0900
Subject: [PATCH 5/5] pkey: remove unused ossl_generate_cb_2() helper function

The previous series of commits re-implemented key generation with the
low level API with the EVP API. The BN_GENCB-based callback function is
no longer used.
---
 ext/openssl/extconf.rb        |  3 --
 ext/openssl/openssl_missing.h | 12 ------
 ext/openssl/ossl_pkey.c       | 73 +++++++----------------------------
 ext/openssl/ossl_pkey.h       |  8 ----
 4 files changed, 15 insertions(+), 81 deletions(-)

diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index 693e55cd97..b3c6647faf 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -136,9 +136,6 @@ def find_openssl_library
   $defs.push("-DHAVE_OPAQUE_OPENSSL")
 end
 have_func("CRYPTO_lock") || $defs.push("-DHAVE_OPENSSL_110_THREADING_API")
-have_func("BN_GENCB_new")
-have_func("BN_GENCB_free")
-have_func("BN_GENCB_get_arg")
 have_func("EVP_MD_CTX_new")
 have_func("EVP_MD_CTX_free")
 have_func("EVP_MD_CTX_pkey_ctx")
diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h
index 7d218f86f5..e575415f49 100644
--- a/ext/openssl/openssl_missing.h
+++ b/ext/openssl/openssl_missing.h
@@ -34,18 +34,6 @@ int ossl_EC_curve_nist2nid(const char *);
 #endif
 
 /* added in 1.1.0 */
-#if !defined(HAVE_BN_GENCB_NEW)
-#  define BN_GENCB_new() ((BN_GENCB *)OPENSSL_malloc(sizeof(BN_GENCB)))
-#endif
-
-#if !defined(HAVE_BN_GENCB_FREE)
-#  define BN_GENCB_free(cb) OPENSSL_free(cb)
-#endif
-
-#if !defined(HAVE_BN_GENCB_GET_ARG)
-#  define BN_GENCB_get_arg(cb) (cb)->arg
-#endif
-
 #if !defined(HAVE_EVP_MD_CTX_NEW)
 #  define EVP_MD_CTX_new EVP_MD_CTX_create
 #endif
diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index d76f0600d1..f9282b9417 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -17,64 +17,6 @@ VALUE cPKey;
 VALUE ePKeyError;
 static ID id_private_q;
 
-/*
- * callback for generating keys
- */
-static VALUE
-call_check_ints0(VALUE arg)
-{
-    rb_thread_check_ints();
-    return Qnil;
-}
-
-static void *
-call_check_ints(void *arg)
-{
-    int state;
-    rb_protect(call_check_ints0, Qnil, &state);
-    return (void *)(VALUE)state;
-}
-
-int
-ossl_generate_cb_2(int p, int n, BN_GENCB *cb)
-{
-    VALUE ary;
-    struct ossl_generate_cb_arg *arg;
-    int state;
-
-    arg = (struct ossl_generate_cb_arg *)BN_GENCB_get_arg(cb);
-    if (arg->yield) {
-	ary = rb_ary_new2(2);
-	rb_ary_store(ary, 0, INT2NUM(p));
-	rb_ary_store(ary, 1, INT2NUM(n));
-
-	/*
-	* can be break by raising exception or 'break'
-	*/
-	rb_protect(rb_yield, ary, &state);
-	if (state) {
-	    arg->state = state;
-	    return 0;
-	}
-    }
-    if (arg->interrupted) {
-	arg->interrupted = 0;
-	state = (int)(VALUE)rb_thread_call_with_gvl(call_check_ints, NULL);
-	if (state) {
-	    arg->state = state;
-	    return 0;
-	}
-    }
-    return 1;
-}
-
-void
-ossl_generate_cb_stop(void *ptr)
-{
-    struct ossl_generate_cb_arg *arg = (struct ossl_generate_cb_arg *)ptr;
-    arg->interrupted = 1;
-}
-
 static void
 ossl_evp_pkey_free(void *ptr)
 {
@@ -257,6 +199,21 @@ pkey_gen_cb_yield(VALUE ctx_v)
     return rb_yield_values2(info_num, argv);
 }
 
+static VALUE
+call_check_ints0(VALUE arg)
+{
+    rb_thread_check_ints();
+    return Qnil;
+}
+
+static void *
+call_check_ints(void *arg)
+{
+    int state;
+    rb_protect(call_check_ints0, Qnil, &state);
+    return (void *)(VALUE)state;
+}
+
 static int
 pkey_gen_cb(EVP_PKEY_CTX *ctx)
 {
diff --git a/ext/openssl/ossl_pkey.h b/ext/openssl/ossl_pkey.h
index 7dbaed47bc..629c16ae1f 100644
--- a/ext/openssl/ossl_pkey.h
+++ b/ext/openssl/ossl_pkey.h
@@ -35,14 +35,6 @@ extern const rb_data_type_t ossl_evp_pkey_type;
     } \
 } while (0)
 
-struct ossl_generate_cb_arg {
-    int yield;
-    int interrupted;
-    int state;
-};
-int ossl_generate_cb_2(int p, int n, BN_GENCB *cb);
-void ossl_generate_cb_stop(void *ptr);
-
 VALUE ossl_pkey_new(EVP_PKEY *);
 void ossl_pkey_check_public_key(const EVP_PKEY *);
 EVP_PKEY *ossl_pkey_read_generic(BIO *, VALUE);
-- 
2.32.0