diff options
-rw-r--r-- | ext/openssl/extconf.rb | 17 | ||||
-rw-r--r-- | ext/openssl/openssl_missing.c | 36 | ||||
-rw-r--r-- | ext/openssl/openssl_missing.h | 6 | ||||
-rw-r--r-- | ext/openssl/ossl_ssl.c | 93 | ||||
-rw-r--r-- | test/openssl/test_pair.rb | 49 | ||||
-rw-r--r-- | test/openssl/utils.rb | 2 |
6 files changed, 172 insertions, 31 deletions
diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 5c3ba84e1c..7758694b4b 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 3afba5c8b7..b62d58d444 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 e68649f207..55f3adacb4 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 a5f360bf07..4f215376c9 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 7f1a25d971..3042f96a90 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 a4340e785f..7123df2af2 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 |