[前][次][番号順一覧][スレッド一覧]

ruby-changes:65517

From: Kazuki <ko1@a...>
Date: Tue, 16 Mar 2021 20:38:38 +0900 (JST)
Subject: [ruby-changes:65517] 1e3590fe22 (master): [ruby/openssl] pkey: add PKey.generate_parameters and .generate_key

https://git.ruby-lang.org/ruby.git/commit/?id=1e3590fe22

From 1e3590fe221c9631259f6876bfaa896398ac7cfa Mon Sep 17 00:00:00 2001
From: Kazuki Yamaguchi <k@r...>
Date: Mon, 20 Mar 2017 23:18:26 +0900
Subject: [ruby/openssl] pkey: add PKey.generate_parameters and .generate_key

Add two methods to create a PKey using the generic EVP interface. This
is useful for the PKey types we don't have a dedicated class.

https://github.com/ruby/openssl/commit/d8e8e57de9
---
 ext/openssl/ossl_pkey.c   | 222 ++++++++++++++++++++++++++++++++++++++++++++++
 test/openssl/test_pkey.rb |  43 +++++++++
 2 files changed, 265 insertions(+)

diff --git a/ext/openssl/ossl_pkey.c b/ext/openssl/ossl_pkey.c
index 8d41623..1f3dd39 100644
--- a/ext/openssl/ossl_pkey.c
+++ b/ext/openssl/ossl_pkey.c
@@ -197,6 +197,226 @@ ossl_pkey_new_from_data(int argc, VALUE *argv, VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_pkey.c#L197
     return ossl_pkey_new(pkey);
 }
 
+static VALUE
+pkey_gen_apply_options_i(RB_BLOCK_CALL_FUNC_ARGLIST(i, ctx_v))
+{
+    VALUE key = rb_ary_entry(i, 0), value = rb_ary_entry(i, 1);
+    EVP_PKEY_CTX *ctx = (EVP_PKEY_CTX *)ctx_v;
+
+    if (SYMBOL_P(key))
+        key = rb_sym2str(key);
+    value = rb_String(value);
+
+    if (EVP_PKEY_CTX_ctrl_str(ctx, StringValueCStr(key), StringValueCStr(value)) <= 0)
+        ossl_raise(ePKeyError, "EVP_PKEY_CTX_ctrl_str(ctx, %+"PRIsVALUE", %+"PRIsVALUE")",
+                   key, value);
+    return Qnil;
+}
+
+static VALUE
+pkey_gen_apply_options0(VALUE args_v)
+{
+    VALUE *args = (VALUE *)args_v;
+
+    rb_block_call(args[1], rb_intern("each"), 0, NULL,
+                  pkey_gen_apply_options_i, args[0]);
+    return Qnil;
+}
+
+struct pkey_blocking_generate_arg {
+    EVP_PKEY_CTX *ctx;
+    EVP_PKEY *pkey;
+    int state;
+    int yield: 1;
+    int genparam: 1;
+    int stop: 1;
+};
+
+static VALUE
+pkey_gen_cb_yield(VALUE ctx_v)
+{
+    EVP_PKEY_CTX *ctx = (void *)ctx_v;
+    int i, info_num;
+    VALUE *argv;
+
+    info_num = EVP_PKEY_CTX_get_keygen_info(ctx, -1);
+    argv = ALLOCA_N(VALUE, info_num);
+    for (i = 0; i < info_num; i++)
+        argv[i] = INT2NUM(EVP_PKEY_CTX_get_keygen_info(ctx, i));
+
+    return rb_yield_values2(info_num, argv);
+}
+
+static int
+pkey_gen_cb(EVP_PKEY_CTX *ctx)
+{
+    struct pkey_blocking_generate_arg *arg = EVP_PKEY_CTX_get_app_data(ctx);
+
+    if (arg->yield) {
+        int state;
+        rb_protect(pkey_gen_cb_yield, (VALUE)ctx, &state);
+        if (state) {
+            arg->stop = 1;
+            arg->state = state;
+        }
+    }
+    return !arg->stop;
+}
+
+static void
+pkey_blocking_gen_stop(void *ptr)
+{
+    struct pkey_blocking_generate_arg *arg = ptr;
+    arg->stop = 1;
+}
+
+static void *
+pkey_blocking_gen(void *ptr)
+{
+    struct pkey_blocking_generate_arg *arg = ptr;
+
+    if (arg->genparam && EVP_PKEY_paramgen(arg->ctx, &arg->pkey) <= 0)
+        return NULL;
+    if (!arg->genparam && EVP_PKEY_keygen(arg->ctx, &arg->pkey) <= 0)
+        return NULL;
+    return arg->pkey;
+}
+
+static VALUE
+pkey_generate(int argc, VALUE *argv, VALUE self, int genparam)
+{
+    EVP_PKEY_CTX *ctx;
+    VALUE alg, options;
+    struct pkey_blocking_generate_arg gen_arg = { 0 };
+    int state;
+
+    rb_scan_args(argc, argv, "11", &alg, &options);
+    if (rb_obj_is_kind_of(alg, cPKey)) {
+        EVP_PKEY *base_pkey;
+
+        GetPKey(alg, base_pkey);
+        ctx = EVP_PKEY_CTX_new(base_pkey, NULL/* engine */);
+        if (!ctx)
+            ossl_raise(ePKeyError, "EVP_PKEY_CTX_new");
+    }
+    else {
+        const EVP_PKEY_ASN1_METHOD *ameth;
+        ENGINE *tmpeng;
+        int pkey_id;
+
+        StringValue(alg);
+        ameth = EVP_PKEY_asn1_find_str(&tmpeng, RSTRING_PTR(alg),
+                                       RSTRING_LENINT(alg));
+        if (!ameth)
+            ossl_raise(ePKeyError, "algorithm %"PRIsVALUE" not found", alg);
+        EVP_PKEY_asn1_get0_info(&pkey_id, NULL, NULL, NULL, NULL, ameth);
+#if !defined(OPENSSL_NO_ENGINE)
+        if (tmpeng)
+            ENGINE_finish(tmpeng);
+#endif
+
+        ctx = EVP_PKEY_CTX_new_id(pkey_id, NULL/* engine */);
+        if (!ctx)
+            ossl_raise(ePKeyError, "EVP_PKEY_CTX_new_id");
+    }
+
+    if (genparam && EVP_PKEY_paramgen_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_paramgen_init");
+    }
+    if (!genparam && EVP_PKEY_keygen_init(ctx) <= 0) {
+        EVP_PKEY_CTX_free(ctx);
+        ossl_raise(ePKeyError, "EVP_PKEY_keygen_init");
+    }
+
+    if (!NIL_P(options)) {
+        VALUE args[2];
+
+        args[0] = (VALUE)ctx;
+        args[1] = options;
+        rb_protect(pkey_gen_apply_options0, (VALUE)args, &state);
+        if (state) {
+            EVP_PKEY_CTX_free(ctx);
+            rb_jump_tag(state);
+        }
+    }
+
+    gen_arg.genparam = genparam;
+    gen_arg.ctx = ctx;
+    gen_arg.yield = rb_block_given_p();
+    EVP_PKEY_CTX_set_app_data(ctx, &gen_arg);
+    EVP_PKEY_CTX_set_cb(ctx, pkey_gen_cb);
+    if (gen_arg.yield)
+        pkey_blocking_gen(&gen_arg);
+    else
+        rb_thread_call_without_gvl(pkey_blocking_gen, &gen_arg,
+                                   pkey_blocking_gen_stop, &gen_arg);
+    EVP_PKEY_CTX_free(ctx);
+    if (!gen_arg.pkey) {
+        if (gen_arg.state) {
+            ossl_clear_error();
+            rb_jump_tag(gen_arg.state);
+        }
+        else {
+            ossl_raise(ePKeyError, genparam ? "EVP_PKEY_paramgen" : "EVP_PKEY_keygen");
+        }
+    }
+
+    return ossl_pkey_new(gen_arg.pkey);
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::PKey.generate_parameters(algo_name [, options]) -> pkey
+ *
+ * Generates new parameters for the algorithm. _algo_name_ is a String that
+ * represents the algorithm. The optional argument _options_ is a Hash that
+ * specifies the options specific to the algorithm. The order of the options
+ * can be important.
+ *
+ * A block can be passed optionally. The meaning of the arguments passed to
+ * the block varies depending on the implementation of the algorithm. The block
+ * may be called once or multiple times, or may not even be called.
+ *
+ * For the supported options, see the documentation for the 'openssl genpkey'
+ * utility command.
+ *
+ * == Example
+ *   pkey = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048)
+ *   p pkey.p.num_bits #=> 2048
+ */
+static VALUE
+ossl_pkey_s_generate_parameters(int argc, VALUE *argv, VALUE self)
+{
+    return pkey_generate(argc, argv, self, 1);
+}
+
+/*
+ * call-seq:
+ *    OpenSSL::PKey.generate_key(algo_name [, options]) -> pkey
+ *    OpenSSL::PKey.generate_key(pkey [, options]) -> pkey
+ *
+ * Generates a new key (pair).
+ *
+ * If a String is given as the first argument, it generates a new random key
+ * for the algorithm specified by the name just as ::generate_parameters does.
+ * If an OpenSSL::PKey::PKey is given instead, it generates a new random key
+ * for the same algorithm as the key, using the parameters the key contains.
+ *
+ * See ::generate_parameters for the details of _options_ and the given block.
+ *
+ * == Example
+ *   pkey_params = OpenSSL::PKey.generate_parameters("DSA", "dsa_paramgen_bits" => 2048)
+ *   pkey_params.priv_key #=> nil
+ *   pkey = OpenSSL::PKey.generate_key(pkey_params)
+ *   pkey.priv_key #=> #<OpenSSL::BN 6277...
+ */
+static VALUE
+ossl_pkey_s_generate_key(int argc, VALUE *argv, VALUE self)
+{
+    return pkey_generate(argc, argv, self, 0);
+}
+
 void
 ossl_pkey_check_public_key(const EVP_PKEY *pkey)
 {
@@ -707,6 +927,8 @@ Init_ossl_pkey(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_pkey.c#L927
     cPKey = rb_define_class_under(mPKey, "PKey", rb_cObject);
 
     rb_define_module_function(mPKey, "read", ossl_pkey_new_from_data, -1);
+    rb_define_module_function(mPKey, "generate_parameters", ossl_pkey_s_generate_parameters, -1);
+    rb_define_module_function(mPKey, "generate_key", ossl_pkey_s_generate_key, -1);
 
     rb_define_alloc_func(cPKey, ossl_pkey_alloc);
     rb_define_method(cPKey, "initialize", ossl_pkey_initialize, 0);
diff --git a/test/openssl/test_pkey.rb b/test/openssl/test_pkey.rb
index 0bdc979..a325a1e 100644
--- a/test/openssl/test_pkey.rb
+++ b/test/openssl/test_pkey.rb
@@ -25,4 +25,47 @@ class OpenSSL::TestPKey < OpenSSL::PKeyTestCase https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pkey.rb#L25
     assert_equal "X25519", x25519.oid
     assert_match %r{oid=X25519}, x25519.inspect
   end
+
+  def test_s_generate_parameters
+    # 512 is non-default; 1024 is used if 'dsa_paramgen_bits' is not specified
+    # with OpenSSL 1.1.0.
+    pkey = OpenSSL::PKey.generate_parameters("DSA", {
+      "dsa_paramgen_bits" => 512,
+      "dsa_paramgen_q_bits" => 256,
+    })
+    assert_instance_of OpenSSL::PKey::DSA, pkey
+    assert_equal 512, pkey.p.num_bits
+    assert_equal 256, pkey.q.num_bits
+    assert_equal nil, pkey.priv_key
+
+    # Invalid options are checked
+    assert_raise(OpenSSL::PKey::PKeyError) {
+      OpenSSL::PKey.generate_parameters("DSA", "invalid" => "option")
+    }
+
+    # Parameter generation callback is called
+    cb_called = []
+    assert_raise(RuntimeError) {
+      OpenSSL::PKey.generate_parameters("DSA") { |*args|
+        cb_called << args
+        raise "exit!" if cb_called.size == 3
+      }
+    }
+    assert_not_empty cb_called
+  end
+
+  def test_s_generate_key
+    assert_raise(OpenSSL::PKey::PKeyError) {
+      # DSA key pair cannot be generated without parameters
+      OpenSSL::PKey.generate_key("DSA")
+    }
+    pkey_params = OpenSSL::PKey.generate_parameters("DSA", {
+      "dsa_paramgen_bits" => 512,
+      "dsa_paramgen_q_bits" => 256,
+    })
+    pkey = OpenSSL::PKey.generate_key(pkey_params)
+    assert_instance_of OpenSSL::PKey::DSA, pkey
+    assert_equal 512, pkey.p.num_bits
+    assert_not_equal nil, pkey.priv_key
+  end
 end
-- 
cgit v1.1


--
ML: ruby-changes@q...
Info: http://www.atdot.net/~ko1/quickml/

[前][次][番号順一覧][スレッド一覧]