aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKazuki Yamaguchi <k@rhe.jp>2016-04-26 16:29:58 +0900
committerKazuki Yamaguchi <k@rhe.jp>2016-04-27 23:07:42 +0900
commit7e17fed37fb0da9e610156efe2b51ea182a21aa5 (patch)
tree44806f96c5caba66df8f889c07b6064a155f4a47
parent77de40a74e687f54647eaf84708aa163a3b5488c (diff)
downloadruby-7e17fed37fb0da9e610156efe2b51ea182a21aa5.tar.gz
ext/openssl: add SSLContext#set_ecdh_curves
And deprecate #tmp_ecdh_callback. Since SSL_CTX_set_tmp_ecdh_callback() was removed in OpenSSL 1.1.0, we can't provide SSLContext#tmp_ecdh_callback anymore. Instead, we should use SSL_CTX_set1_curves_list() to set the curves and SSL_CTX_set_ecdh_auto() to make OpenSSL select automatically from the list.
-rw-r--r--ext/openssl/extconf.rb17
-rw-r--r--ext/openssl/openssl_missing.c36
-rw-r--r--ext/openssl/openssl_missing.h6
-rw-r--r--ext/openssl/ossl_ssl.c93
-rw-r--r--test/openssl/test_pair.rb49
-rw-r--r--test/openssl/utils.rb2
6 files changed, 172 insertions, 31 deletions
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb
index 5c3ba84..7758694 100644
--- a/ext/openssl/extconf.rb
+++ b/ext/openssl/extconf.rb
@@ -60,6 +60,11 @@ unless OpenSSL.check_func("SSL_library_init()", "openssl/ssl.h")
end
Logging::message "=== Checking for OpenSSL features... ===\n"
+def have_func_like(name, header)
+ have_func(name, [header]) ||
+ have_macro(name, [header]) && $defs.push("-DHAVE_#{name.upcase}")
+end
+
# compile options
have_func("SSLv2_method")
have_func("SSLv3_method")
@@ -71,13 +76,13 @@ have_func("RAND_egd")
engines = %w{builtin_engines openbsd_dev_crypto dynamic 4758cca aep atalla chil
cswift nuron sureware ubsec padlock capi gmp gost cryptodev aesni}
engines.each { |name|
- have_func("ENGINE_load_#{name}", ["openssl/engine.h"])
+ have_func_like("ENGINE_load_#{name}", "openssl/engine.h")
}
# added in 0.9.8X
have_func("EVP_CIPHER_CTX_new")
have_func("EVP_CIPHER_CTX_free")
-have_func("SSL_CTX_clear_options", ["openssl/ssl.h"])
+have_func_like("SSL_CTX_clear_options", "openssl/ssl.h")
# added in 1.0.0
have_func("EVP_CIPHER_CTX_copy")
@@ -87,7 +92,7 @@ have_func("PKCS5_PBKDF2_HMAC")
have_func("X509_NAME_hash_old")
have_func("X509_STORE_CTX_get0_current_crl")
have_func("X509_STORE_set_verify_cb")
-have_func("SSL_set_tlsext_host_name", ["openssl/ssl.h"])
+have_func_like("SSL_set_tlsext_host_name", "openssl/ssl.h")
have_struct_member("CRYPTO_THREADID", "ptr", "openssl/crypto.h")
# added in 1.0.1
@@ -96,10 +101,13 @@ have_macro("EVP_CTRL_GCM_GET_TAG", ['openssl/evp.h']) && $defs.push("-DHAVE_AUTH
# added in 1.0.2
have_func("CRYPTO_memcmp")
+have_func("EC_curve_nist2nid")
have_func("X509_REVOKED_dup")
have_func("X509_STORE_CTX_get0_store")
have_func("SSL_CTX_set_alpn_select_cb")
-have_func("SSL_get_server_tmp_key", ["openssl/ssl.h"])
+have_func_like("SSL_CTX_set1_curves_list", "openssl/ssl.h")
+have_func_like("SSL_CTX_set_ecdh_auto", "openssl/ssl.h")
+have_func_like("SSL_get_server_tmp_key", "openssl/ssl.h")
# added in 1.1.0
have_func("CRYPTO_lock") || $defs.push("-DHAVE_OPENSSL_110_THREADING_API")
@@ -130,6 +138,7 @@ have_func("X509_STORE_up_ref")
have_func("SSL_CTX_get_ciphers")
have_func("SSL_CTX_get_security_level")
have_func_like("SSL_CTX_set_min_proto_version", "openssl/ssl.h")
+have_func_like("SSL_CTX_set_tmp_ecdh_callback", "openssl/ssl.h") # removed
have_func("SSL_SESSION_up_ref")
have_func("EVP_PKEY_up_ref")
have_func("ENGINE_cleanup") # removed
diff --git a/ext/openssl/openssl_missing.c b/ext/openssl/openssl_missing.c
index 3afba5c..b62d58d 100644
--- a/ext/openssl/openssl_missing.c
+++ b/ext/openssl/openssl_missing.c
@@ -99,6 +99,42 @@ CRYPTO_memcmp(const volatile void * volatile in_a,
}
#endif
+#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
+
/*** added in 1.1.0 ***/
#if !defined(HAVE_HMAC_CTX_NEW)
HMAC_CTX *
diff --git a/ext/openssl/openssl_missing.h b/ext/openssl/openssl_missing.h
index e68649f..55f3ada 100644
--- a/ext/openssl/openssl_missing.h
+++ b/ext/openssl/openssl_missing.h
@@ -55,6 +55,12 @@ int HMAC_CTX_copy(HMAC_CTX *out, HMAC_CTX *in);
int CRYPTO_memcmp(const volatile void * volatile in_a, const volatile void * volatile in_b, size_t len);
#endif
+#if !defined(OPENSSL_NO_EC)
+#if !defined(HAVE_EC_CURVE_NIST2NID)
+int EC_curve_nist2nid(const char *str);
+#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))
diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c
index a5f360b..4f21537 100644
--- a/ext/openssl/ossl_ssl.c
+++ b/ext/openssl/ossl_ssl.c
@@ -271,7 +271,11 @@ ossl_tmp_dh_callback(SSL *ssl, int is_export, int keylength)
}
#endif /* OPENSSL_NO_DH */
-#if !defined(OPENSSL_NO_EC)
+#if !defined(OPENSSL_NO_EC) && defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
+/*
+ * SSL_CTX_set_tmp_ecdh_callback() is removed in OpenSSL 1.1.0. User should use
+ * SSLContext#set_ecdh_curves instead.
+ */
static VALUE
ossl_call_tmp_ecdh_callback(VALUE args)
{
@@ -719,10 +723,29 @@ ossl_sslctx_setup(VALUE self)
#endif
#if !defined(OPENSSL_NO_EC)
+ /*
+ * OpenSSL will use ECDH:
+ * 0.9.8-1.0.1: if a curve (or tmp_ecdh callback) is explicitly set
+ * 1.0.2: if SSL_CTX_set_ecdh_auto() is called
+ * 1.1.0: always
+ */
+#if defined(HAVE_SSL_CTX_SET_ECDH_AUTO)
+ /* 1.0.2 / LibreSSL 2.3 case: enable it */
+ if (!SSL_CTX_set_ecdh_auto(ctx, 1))
+ ossl_raise(eSSLError, "SSL_CTX_set_ecdh_auto");
+#else
+ /* -1.0.1 case: enable if SSLContext#set_ecdh_curves is called */
+ /* 1.1.0 case: enable always; nothing to do */
+#endif
+ /* for compatibility */
if (RTEST(ossl_sslctx_get_tmp_ecdh_cb(self))){
+ rb_warn("tmp_ecdh_callback is deprecated and will not work with recent "
+ "OpenSSL; use SSLContext#set_ecdh_curves() instead.");
+#if defined(HAVE_SSL_CTX_SET_TMP_ECDH_CALLBACK)
SSL_CTX_set_tmp_ecdh_callback(ctx, ossl_tmp_ecdh_callback);
- }
#endif
+ }
+#endif /* OPENSSL_NO_EC */
val = ossl_sslctx_get_cert_store(self);
if(!NIL_P(val)){
@@ -1018,6 +1041,64 @@ ossl_sslctx_set_security_level(VALUE self, VALUE v)
return v;
}
+#if !defined(OPENSSL_NO_EC)
+/*
+ * call-seq:
+ * ctx.set_ecdh_curves("curve1:curve2:curve3") -> self
+ *
+ * Sets the list of supported elliptic curves for this context. The curves are
+ * passed as a string, colon separated list of named curves in preference order.
+ * For example "P-521:P-384:P-256".
+ *
+ * If you are using an newer OpenSSL (1.0.2-):
+ * For a TLS client the curves are used in the supported elliptic curves
+ * extension. For a TLS server the curves are used to determine the set of
+ * shared curves.
+ * If you are using an older OpenSSL (-1.0.1):
+ * You can set only one curve. For a TLS client this does nothing. The
+ * supported curves list can't be changed. For a TLS server the specified
+ * curve is directly used.
+ */
+static VALUE
+ossl_sslctx_set_ecdh_curves(VALUE self, VALUE value)
+{
+ SSL_CTX *ctx;
+ const char *cstr = StringValueCStr(value);
+
+ rb_check_frozen(self);
+ GetSSLCTX(self, ctx);
+ if (!ctx)
+ ossl_raise(eSSLError, "SSL_CTX is not initialized");
+
+#if defined(HAVE_SSL_CTX_SET1_CURVES_LIST)
+ /* OpenSSL 1.0.2 or newer */
+ if (!SSL_CTX_set1_curves_list(ctx, cstr))
+ ossl_raise(eSSLError, NULL);
+#else
+ /* OpenSSL 1.0.1 or older, LibreSSL */
+ if (strstr(cstr, ":")) {
+ ossl_raise(rb_eArgError, "only one curve can be specified");
+ }
+ else {
+ EC_KEY *ecdh;
+ int nid = EC_curve_nist2nid(cstr);
+ if (nid == NID_undef)
+ nid = OBJ_sn2nid(cstr);
+ if (nid == NID_undef)
+ nid = OBJ_ln2nid(cstr);
+ if (nid == NID_undef)
+ ossl_raise(eSSLError, "unknown curve name");
+ ecdh = EC_KEY_new_by_curve_name(nid);
+ if (!ecdh)
+ ossl_raise(eSSLError, NULL);
+ SSL_CTX_set_tmp_ecdh(ctx, ecdh);
+ }
+#endif
+
+ return self;
+}
+#endif
+
/*
* call-seq:
* ctx.session_add(session) -> true | false
@@ -2140,6 +2221,7 @@ Init_ossl_ssl(void)
*/
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.
*
@@ -2149,8 +2231,12 @@ Init_ossl_ssl(void)
*
* The callback must return an OpenSSL::PKey::EC instance of the correct
* key length.
+ *
+ * This callback is deprecated and won't work with newer OpenSSL (>= 1.1.0).
+ * Use OpenSSL::SSL::SSLContext#set_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
@@ -2288,6 +2374,9 @@ Init_ossl_ssl(void)
rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1);
rb_define_method(cSSLContext, "security_level", ossl_sslctx_get_security_level, 0);
rb_define_method(cSSLContext, "security_level=", ossl_sslctx_set_security_level, 1);
+#if !defined(OPENSSL_NO_EC)
+ rb_define_method(cSSLContext, "set_ecdh_curves", ossl_sslctx_set_ecdh_curves, 1);
+#endif
rb_define_method(cSSLContext, "setup", ossl_sslctx_setup, 0);
diff --git a/test/openssl/test_pair.rb b/test/openssl/test_pair.rb
index 7f1a25d..3042f96 100644
--- a/test/openssl/test_pair.rb
+++ b/test/openssl/test_pair.rb
@@ -377,37 +377,39 @@ module OpenSSL::TestPairM
accepted.close if accepted.respond_to?(:close)
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"
- }
+ def test_ecdh_curves
+ # FIXME: LibreSSL currently does not implement SSL_CTX_set1_curves but
+ # it may be implemented in future
+ if OpenSSL::OPENSSL_VERSION_NUMBER >= 0x10002000 &&
+ !OpenSSL::OPENSSL_VERSION.include?("LibreSSL")
+ server_curves = "P-256:P-224"
+ client_curves = "P-384:P-521:P-224"
+ else
+ server_curves = "P-224"
+ client_curves = "P-224"
+ end
sock1, sock2 = tcp_pair
- s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
ctx1 = OpenSSL::SSL::SSLContext.new
ctx1.ciphers = "ECDH"
-
+ ctx1.set_ecdh_curves(client_curves)
+ ctx1.security_level = 0
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.set_ecdh_curves(server_curves)
+ ctx2.security_level = 0
+ s2 = OpenSSL::SSL::SSLSocket.new(sock2, ctx2)
- assert called, 'ecdh callback should be called'
+ th = Thread.new { s2.accept }
+ s1.connect
+
+ assert s1.cipher[0].start_with?("AECDH"), "AECDH should be used"
+ if s1.respond_to?(:tmp_key)
+ assert_equal "secp224r1", s1.tmp_key.group.curve_name
+ end
rescue OpenSSL::SSL::SSLError => e
if e.message =~ /no cipher match/
skip "ECDH cipher not supported."
@@ -420,7 +422,6 @@ module OpenSSL::TestPairM
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
diff --git a/test/openssl/utils.rb b/test/openssl/utils.rb
index a4340e7..7123df2 100644
--- a/test/openssl/utils.rb
+++ b/test/openssl/utils.rb
@@ -279,7 +279,7 @@ AQjjxMXhwULlmuR/K+WwlaZPiLIBYalLAZQ7ZbOPeVkJ8ePao0eLAgEC
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.set_ecdh_curves("P-256") if defined?(OpenSSL::PKey::EC)
ctx.verify_mode = verify_mode
ctx_proc.call(ctx) if ctx_proc