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

ruby-changes:43140

From: rhe <ko1@a...>
Date: Mon, 30 May 2016 18:30:44 +0900 (JST)
Subject: [ruby-changes:43140] rhe:r55214 (trunk): openssl: add SSLContext#ecdh_curves=

rhe	2016-05-30 18:30:38 +0900 (Mon, 30 May 2016)

  New Revision: 55214

  https://svn.ruby-lang.org/cgi-bin/viewvc.cgi?view=revision&revision=55214

  Log:
    openssl: add SSLContext#ecdh_curves=
    
    * ext/openssl/ossl_ssl.c (ossl_sslctx_s_alloc): Enable the automatic
      curve selection for ECDH by calling SSL_CTX_set_ecdh_auto(). With
      this a TLS server automatically selects a curve which both the client
      and the server support to use in ECDH. This changes the default
      behavior but users can still disable ECDH by excluding 'ECDH' cipher
      suites from the cipher list (with SSLContext#ciphers=). This commit
      also deprecate #tmp_ecdh_callback=. It was added in Ruby 2.3.0. It
      wraps SSL_CTX_set_tmp_ecdh_callback() which will be removed in OpenSSL
      1.1.0. Its callback receives two values 'is_export' and 'keylength'
      but both are completely useless for determining a curve to use in
      ECDH. The automatic curve selection was introduced to replace this.
    
      (ossl_sslctx_setup): Deprecate SSLContext#tmp_ecdh_callback=. Emit a
      warning if this is in use.
    
      (ossl_sslctx_set_ecdh_curves): Add SSLContext#ecdh_curves=. Wrap
      SSL_CTX_set1_curves_list(). If it is not available, this falls back
      to SSL_CTX_set_tmp_ecdh().
    
      (Init_ossl_ssl): Define SSLContext#ecdh_curves=.
    
    * ext/openssl/extconf.rb: Check the existence of EC_curve_nist2nid(),
      SSL_CTX_set1_curves_list(), SSL_CTX_set_ecdh_auto() and
      SSL_CTX_set_tmp_ecdh_callback().
    
    * ext/openssl/openssl_missing.[ch]: Implement EC_curve_nist2nid() if
      missing.
    
    * test/openssl/test_pair.rb (test_ecdh_callback): Use
      EnvUtil.suppress_warning to suppress deprecated warning.
    
      (test_ecdh_curves): Test that SSLContext#ecdh_curves= works.
    
    * test/openssl/utils.rb (start_server): Use SSLContext#ecdh_curves=.

  Modified files:
    trunk/ChangeLog
    trunk/ext/openssl/extconf.rb
    trunk/ext/openssl/openssl_missing.c
    trunk/ext/openssl/openssl_missing.h
    trunk/ext/openssl/ossl_ssl.c
    trunk/test/openssl/test_pair.rb
    trunk/test/openssl/utils.rb
Index: test/openssl/test_pair.rb
===================================================================
--- test/openssl/test_pair.rb	(revision 55213)
+++ test/openssl/test_pair.rb	(revision 55214)
@@ -372,41 +372,73 @@ module OpenSSL::TestPairM https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pair.rb#L372
   end
 
   def test_ecdh_callback
-    called = false
-    ctx2 = OpenSSL::SSL::SSLContext.new
-    ctx2.ciphers = "ECDH"
-    ctx2.tmp_ecdh_callback = ->(*args) {
-      called = true
-      OpenSSL::PKey::EC.new "prime256v1"
-    }
+    return unless OpenSSL::SSL::SSLContext.instance_methods.include?(:tmp_ecdh_callback)
+    EnvUtil.suppress_warning do # tmp_ecdh_callback is deprecated (2016-05)
+      begin
+        called = false
+        ctx2 = OpenSSL::SSL::SSLContext.new
+        ctx2.ciphers = "ECDH"
+        ctx2.tmp_ecdh_callback = ->(*args) {
+          called = true
+          OpenSSL::PKey::EC.new "prime256v1"
+        }
+
+        sock1, sock2 = tcp_pair
+
+        s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+        ctx1 = OpenSSL::SSL::SSLContext.new
+        ctx1.ciphers = "ECDH"
+
+        s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
+        th = Thread.new do
+          begin
+            rv = s1.connect_nonblock(exception: false)
+            case rv
+            when :wait_writable
+              IO.select(nil, [s1], nil, 5)
+            when :wait_readable
+              IO.select([s1], nil, nil, 5)
+            end
+          end until rv == s1
+        end
 
+        accepted = s2.accept
+        assert called, 'ecdh callback should be called'
+      rescue OpenSSL::SSL::SSLError => e
+        if e.message =~ /no cipher match/
+          skip "ECDH cipher not supported."
+        else
+          raise e
+        end
+      ensure
+        th.join if th
+        s1.close if s1
+        s2.close if s2
+        sock1.close if sock1
+        sock2.close if sock2
+      end
+    end
+  end
+
+  def test_ecdh_curves
     sock1, sock2 = tcp_pair
 
-    s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
     ctx1 = OpenSSL::SSL::SSLContext.new
     ctx1.ciphers = "ECDH"
-
+    ctx1.ecdh_curves = "P-384:P-224"
     s1 = OpenSSL::SSL::SSLSocket.new(sock1, ctx1)
-    th = Thread.new do
-      begin
-        rv = s1.connect_nonblock(exception: false)
-        case rv
-        when :wait_writable
-          IO.select(nil, [s1], nil, 5)
-        when :wait_readable
-          IO.select([s1], nil, nil, 5)
-        end
-      end until rv == s1
-    end
 
-    accepted = s2.accept
+    ctx2 = OpenSSL::SSL::SSLContext.new
+    ctx2.ciphers = "ECDH"
+    ctx2.ecdh_curves = "P-256:P-384"
+    s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
+
+    th = Thread.new { s1.accept }
+    s2.connect
 
-    assert called, 'ecdh callback should be called'
-  rescue OpenSSL::SSL::SSLError => e
-    if e.message =~ /no cipher match/
-      skip "ECDH cipher not supported."
-    else
-      raise e
+    assert s2.cipher[0].start_with?("AECDH"), "AECDH should be used"
+    if s2.respond_to?(:tmp_key)
+      assert_equal "secp384r1", s2.tmp_key.group.curve_name
     end
   ensure
     th.join if th
@@ -414,7 +446,6 @@ module OpenSSL::TestPairM https://github.com/ruby/ruby/blob/trunk/test/openssl/test_pair.rb#L446
     s2.close if s2
     sock1.close if sock1
     sock2.close if sock2
-    accepted.close if accepted.respond_to?(:close)
   end
 
   def test_connect_accept_nonblock_no_exception
Index: test/openssl/utils.rb
===================================================================
--- test/openssl/utils.rb	(revision 55213)
+++ test/openssl/utils.rb	(revision 55214)
@@ -281,7 +281,7 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOP https://github.com/ruby/ruby/blob/trunk/test/openssl/utils.rb#L281
         ctx.cert = @svr_cert
         ctx.key = @svr_key
         ctx.tmp_dh_callback = proc { OpenSSL::TestUtils::TEST_KEY_DH1024 }
-        ctx.tmp_ecdh_callback = proc { OpenSSL::TestUtils::TEST_KEY_EC_P256V1 }
+        ctx.ecdh_curves = "P-256"
         ctx.verify_mode = verify_mode
         ctx_proc.call(ctx) if ctx_proc
 
Index: ext/openssl/openssl_missing.c
===================================================================
--- ext/openssl/openssl_missing.c	(revision 55213)
+++ ext/openssl/openssl_missing.c	(revision 55214)
@@ -58,3 +58,40 @@ HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *i https://github.com/ruby/ruby/blob/trunk/ext/openssl/openssl_missing.c#L58
 }
 #endif /* HAVE_HMAC_CTX_COPY */
 #endif /* NO_HMAC */
+
+/* added in 1.0.2 */
+#if !defined(OPENSSL_NO_EC)
+#if !defined(HAVE_EC_CURVE_NIST2NID)
+static struct {
+    const char *name;
+    int nid;
+} nist_curves[] = {
+    {"B-163", NID_sect163r2},
+    {"B-233", NID_sect233r1},
+    {"B-283", NID_sect283r1},
+    {"B-409", NID_sect409r1},
+    {"B-571", NID_sect571r1},
+    {"K-163", NID_sect163k1},
+    {"K-233", NID_sect233k1},
+    {"K-283", NID_sect283k1},
+    {"K-409", NID_sect409k1},
+    {"K-571", NID_sect571k1},
+    {"P-192", NID_X9_62_prime192v1},
+    {"P-224", NID_secp224r1},
+    {"P-256", NID_X9_62_prime256v1},
+    {"P-384", NID_secp384r1},
+    {"P-521", NID_secp521r1}
+};
+
+int
+EC_curve_nist2nid(const char *name)
+{
+    size_t i;
+    for (i = 0; i < (sizeof(nist_curves) / sizeof(nist_curves[0])); i++) {
+	if (!strcmp(nist_curves[i].name, name))
+	    return nist_curves[i].nid;
+    }
+    return NID_undef;
+}
+#endif
+#endif
Index: ext/openssl/openssl_missing.h
===================================================================
--- ext/openssl/openssl_missing.h	(revision 55213)
+++ ext/openssl/openssl_missing.h	(revision 55214)
@@ -20,6 +20,12 @@ void HMAC_CTX_copy(HMAC_CTX *out, HMAC_C https://github.com/ruby/ruby/blob/trunk/ext/openssl/openssl_missing.h#L20
 #endif
 
 /* added in 1.0.2 */
+#if !defined(OPENSSL_NO_EC)
+#if !defined(HAVE_EC_CURVE_NIST2NID)
+int EC_curve_nist2nid(const char *);
+#endif
+#endif
+
 #if !defined(HAVE_X509_REVOKED_DUP)
 # define X509_REVOKED_dup(rev) (X509_REVOKED *)ASN1_dup((i2d_of_void *)i2d_X509_REVOKED, \
 	(d2i_of_void *)d2i_X509_REVOKED, (char *)(rev))
Index: ext/openssl/extconf.rb
===================================================================
--- ext/openssl/extconf.rb	(revision 55213)
+++ ext/openssl/extconf.rb	(revision 55214)
@@ -98,13 +98,17 @@ have_func("SSL_CTX_set_next_proto_select https://github.com/ruby/ruby/blob/trunk/ext/openssl/extconf.rb#L98
 have_macro("EVP_CTRL_GCM_GET_TAG", ['openssl/evp.h']) && $defs.push("-DHAVE_AUTHENTICATED_ENCRYPTION")
 
 # added in 1.0.2
+have_func("EC_curve_nist2nid")
 have_func("X509_REVOKED_dup")
 have_func("SSL_CTX_set_alpn_select_cb")
+OpenSSL.check_func_or_macro("SSL_CTX_set1_curves_list", "openssl/ssl.h")
+OpenSSL.check_func_or_macro("SSL_CTX_set_ecdh_auto", "openssl/ssl.h")
 OpenSSL.check_func_or_macro("SSL_get_server_tmp_key", "openssl/ssl.h")
 
 # added in 1.1.0
 have_func("X509_STORE_get_ex_data")
 have_func("X509_STORE_set_ex_data")
+OpenSSL.check_func_or_macro("SSL_CTX_set_tmp_ecdh_callback", "openssl/ssl.h") # removed
 
 Logging::message "=== Checking done. ===\n"
 
Index: ext/openssl/ossl_ssl.c
===================================================================
--- ext/openssl/ossl_ssl.c	(revision 55213)
+++ ext/openssl/ossl_ssl.c	(revision 55214)
@@ -162,6 +162,18 @@ ossl_sslctx_s_alloc(VALUE klass) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L162
     RTYPEDDATA_DATA(obj) = ctx;
     SSL_CTX_set_ex_data(ctx, ossl_ssl_ex_ptr_idx, (void*)obj);
 
+#if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+    /* We use SSL_CTX_set1_curves_list() to specify the curve used in ECDH. It
+     * allows to specify multiple curve names and OpenSSL will select
+     * automatically from them. In OpenSSL 1.0.2, the automatic selection has to
+     * be enabled explicitly. But OpenSSL 1.1.0 removed the knob and it is
+     * always enabled. To uniform the behavior, we enable the automatic
+     * selection also in 1.0.2. Users can still disable ECDH by removing ECDH
+     * cipher suites by SSLContext#ciphers=. */
+    if (!SSL_CTX_set_ecdh_auto(ctx, 1))
+	ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+#endif
+
     return obj;
 }
 
@@ -711,10 +723,24 @@ ossl_sslctx_setup(VALUE self) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L723
 #endif
 
 #if !defined(OPENSSL_NO_EC)
-    if (RTEST(ossl_sslctx_get_tmp_ecdh_cb(self))){
+    /* We added SSLContext#tmp_ecdh_callback= in Ruby 2.3.0,
+     * but SSL_CTX_set_tmp_ecdh_callback() was removed in OpenSSL 1.1.0. */
+    if (RTEST(ossl_sslctx_get_tmp_ecdh_cb(self))) {
+# if defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+	rb_warn("#tmp_ecdh_callback= is deprecated; use #ecdh_curves= instead");
 	SSL_CTX_set_tmp_ecdh_callback(ctx, ossl_tmp_ecdh_callback);
+#  if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+	/* tmp_ecdh_callback and ecdh_auto conflict; OpenSSL ignores
+	 * tmp_ecdh_callback. So disable ecdh_auto. */
+	if (!SSL_CTX_set_ecdh_auto(ctx, 0))
+	    ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+#  endif
+# else
+	ossl_raise(eSSLError, "OpenSSL does not support tmp_ecdh_callback; "
+		   "use #ecdh_curves= instead");
+# endif
     }
-#endif
+#endif /* OPENSSL_NO_EC */
 
     val = ossl_sslctx_get_cert_store(self);
     if(!NIL_P(val)){
@@ -953,6 +979,87 @@ ossl_sslctx_set_ciphers(VALUE self, VALU https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L979
     return v;
 }
 
+#if !defined(OPENSSL_NO_EC)
+/*
+ * call-seq:
+ *    ctx.ecdh_curves = curve_list -> curve_list
+ *
+ * Sets the list of "supported elliptic curves" for this context.
+ *
+ * For a TLS client, the list is directly used in the Supported Elliptic Curves
+ * Extension. For a server, the list is used by OpenSSL to determine the set of
+ * shared curves. OpenSSL will pick the most appropriate one from it.
+ *
+ * Note that this works differently with old OpenSSL (<= 1.0.1). Only one curve
+ * can be set, and this has no effect for TLS clients.
+ *
+ * === Example
+ *   ctx1 = OpenSSL::SSL::SSLContext.new
+ *   ctx1.ecdh_curves = "X25519:P-256:P-224"
+ *   svr = OpenSSL::SSL::SSLServer.new(tcp_svr, ctx1)
+ *   Thread.new { svr.accept }
+ *
+ *   ctx2 = OpenSSL::SSL::SSLContext.new
+ *   ctx2.ecdh_curves = "P-256"
+ *   cli = OpenSSL::SSL::SSLSocket.new(tcp_sock, ctx2)
+ *   cli.connect
+ *
+ *   p cli.tmp_key.group.curve_name
+ *   # => "prime256v1" (is an alias for NIST P-256)
+ */
+static VALUE
+ossl_sslctx_set_ecdh_curves(VALUE self, VALUE arg)
+{
+    SSL_CTX *ctx;
+
+    rb_check_frozen(self);
+    GetSSLCTX(self, ctx);
+    StringValueCStr(arg);
+
+#if defined(HAVE_SSL_CTX_SET1_CURVES_LIST)
+    if (!SSL_CTX_set1_curves_list(ctx, RSTRING_PTR(arg)))
+	ossl_raise(eSSLError, NULL);
+#else
+    /* OpenSSL does not have SSL_CTX_set1_curves_list()... Fallback to
+     * SSL_CTX_set_tmp_ecdh(). So only the first curve is used. */
+    {
+	VALUE curve, splitted;
+	EC_KEY *ec;
+	int nid;
+
+	splitted = rb_str_split(arg, ":");
+	if (!RARRAY_LEN(splitted))
+	    ossl_raise(eSSLError, "invalid input format");
+	curve = RARRAY_AREF(splitted, 0);
+	StringValueCStr(curve);
+
+	/* SSL_CTX_set1_curves_list() accepts NIST names */
+	nid = EC_curve_nist2nid(RSTRING_PTR(curve));
+	if (nid == NID_undef)
+	    nid = OBJ_txt2nid(RSTRING_PTR(curve));
+	if (nid == NID_undef)
+	    ossl_raise(eSSLError, "unknown curve name");
+
+	ec = EC_KEY_new_by_curve_name(nid);
+	if (!ec)
+	    ossl_raise(eSSLError, NULL);
+	EC_KEY_set_asn1_flag(ec, OPENSSL_EC_NAMED_CURVE);
+	SSL_CTX_set_tmp_ecdh(ctx, ec);
+# if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+	/* tmp_ecdh and ecdh_auto conflict. tmp_ecdh is ignored when ecdh_auto
+	 * is enabled. So disable ecdh_auto. */
+	if (!SSL_CTX_set_ecdh_auto(ctx, 0))
+	    ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+# endif
+    }
+#endif
+
+    return arg;
+}
+#else
+#define ossl_sslctx_set_ecdh_curves rb_f_notimplement
+#endif
+
 /*
  *  call-seq:
  *     ctx.session_add(session) -> true | false
@@ -2119,6 +2226,7 @@ Init_ossl_ssl(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2226
      */
     rb_attr(cSSLContext, rb_intern("client_cert_cb"), 1, 1, Qfalse);
 
+#if defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
     /*
      * A callback invoked when ECDH parameters are required.
      *
@@ -2126,10 +2234,11 @@ Init_ossl_ssl(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2234
      * flag indicating the use of an export cipher and the keylength
      * required.
      *
-     * The callback must return an OpenSSL::PKey::EC instance of the correct
-     * key length.
+     * The callback is deprecated. This does not work with recent versions of
+     * OpenSSL. Use OpenSSL::SSL::SSLContext#ecdh_curves= instead.
      */
     rb_attr(cSSLContext, rb_intern("tmp_ecdh_callback"), 1, 1, Qfalse);
+#endif
 
     /*
      * Sets the context in which a session can be reused.  This allows
@@ -2265,6 +2374,7 @@ Init_ossl_ssl(void) https://github.com/ruby/ruby/blob/trunk/ext/openssl/ossl_ssl.c#L2374
     rb_define_method(cSSLContext, "ssl_version=", ossl_sslctx_set_ssl_version, 1);
     rb_define_method(cSSLContext, "ciphers",     ossl_sslctx_get_ciphers, 0);
     rb_define_method(cSSLContext, "ciphers=",    ossl_sslctx_set_ciphers, 1);
+    rb_define_method(cSSLContext, "ecdh_curves=", ossl_sslctx_set_ecdh_curves, 1);
 
     rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0);
 
Index: ChangeLog
===================================================================
--- ChangeLog	(revision 55213)
+++ ChangeLog	(revision 55214)
@@ -1,3 +1,40 @@ https://github.com/ruby/ruby/blob/trunk/ChangeLog#L1
+Mon May 30 18:29:28 2016  Kazuki Yamaguchi  <k@r...>
+
+	* ext/openssl/ossl_ssl.c (ossl_sslctx_s_alloc): Enable the automatic
+	  curve selection for ECDH by calling SSL_CTX_set_ecdh_auto(). With
+	  this a TLS server automatically selects a curve which both the client
+	  and the server support to use in ECDH. This changes the default
+	  behavior but users can still disable ECDH by excluding 'ECDH' cipher
+	  suites from the cipher list (with SSLContext#ciphers=). This commit
+	  also deprecate #tmp_ecdh_callback=. It was added in Ruby 2.3.0. It
+	  wraps SSL_CTX_set_tmp_ecdh_callback() which will be removed in OpenSSL
+	  1.1.0. Its callback receives two values 'is_export' and 'keylength'
+	  but both are completely useless for determining a curve to use in
+	  ECDH. The automatic curve selection was introduced to replace this.
+
+	  (ossl_sslctx_setup): Deprecate SSLContext#tmp_ecdh_callback=. Emit a
+	  warning if this is in use.
+
+	  (ossl_sslctx_set_ecdh_curves): Add SSLContext#ecdh_curves=. Wrap
+	  SSL_CTX_set1_curves_list(). If it is not available, this falls back
+	  to SSL_CTX_set_tmp_ecdh().
+
+	  (Init_ossl_ssl): Define SSLContext#ecdh_curves=.
+
+	* ext/openssl/extconf.rb: Check the existence of EC_curve_nist2nid(),
+	  SSL_CTX_set1_curves_list(), SSL_CTX_set_ecdh_auto() and
+	  SSL_CTX_set_tmp_ecdh_callback().
+
+	* ext/openssl/openssl_missing.[ch]: Implement EC_curve_nist2nid() if
+	  missing.
+
+	* test/openssl/test_pair.rb (test_ecdh_callback): Use
+	  EnvUtil.suppress_warning to suppress deprecated warning.
+
+	  (test_ecdh_curves): Test that SSLContext#ecdh_curves= works.
+
+	* test/openssl/utils.rb (start_server): Use SSLContext#ecdh_curves=.
+
 Mon May 30 16:28:53 2016  Nobuyoshi Nakada  <nobu@r...>
 
 	* ext/socket/raddrinfo.c (host_str, port_str): use RSTRING_LEN

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

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