diff --git a/ext/openssl/extconf.rb b/ext/openssl/extconf.rb index 5bb045e89..6eb401cf5 100644 --- a/ext/openssl/extconf.rb +++ b/ext/openssl/extconf.rb @@ -135,6 +135,11 @@ def find_openssl_library # compile options have_func("RAND_egd()", "openssl/rand.h") +# added in OpenSSL 1.0.2, not in LibreSSL yet +have_func("SSL_CTX_set1_sigalgs_list(NULL, NULL)", ssl_h) +# added in OpenSSL 1.0.2, not in LibreSSL or AWS-LC yet +have_func("SSL_CTX_set1_client_sigalgs_list(NULL, NULL)", ssl_h) + # added in 1.1.0, currently not in LibreSSL have_func("EVP_PBE_scrypt(\"\", 0, (unsigned char *)\"\", 0, 0, 0, 0, 0, NULL, 0)", evp_h) diff --git a/ext/openssl/ossl_ssl.c b/ext/openssl/ossl_ssl.c index d18eb39d3..30fbb3bbd 100644 --- a/ext/openssl/ossl_ssl.c +++ b/ext/openssl/ossl_ssl.c @@ -999,11 +999,10 @@ static VALUE build_cipher_string(VALUE v) { VALUE str, elem; - int i; if (RB_TYPE_P(v, T_ARRAY)) { str = rb_str_new(0, 0); - for (i = 0; i < RARRAY_LEN(v); i++) { + for (long i = 0; i < RARRAY_LEN(v); i++) { elem = rb_ary_entry(v, i); if (RB_TYPE_P(elem, T_ARRAY)) elem = rb_ary_entry(elem, 0); elem = rb_String(elem); @@ -1024,9 +1023,14 @@ build_cipher_string(VALUE v) * ctx.ciphers = [name, ...] * ctx.ciphers = [[name, version, bits, alg_bits], ...] * - * Sets the list of available cipher suites for this context. Note in a server - * context some ciphers require the appropriate certificates. For example, an - * RSA cipher suite can only be chosen when an RSA certificate is available. + * Sets the list of available cipher suites for TLS 1.2 and below for this + * context. + * + * Note in a server context some ciphers require the appropriate certificates. + * For example, an RSA cipher suite can only be chosen when an RSA certificate + * is available. + * + * This method does not affect TLS 1.3 connections. See also #ciphersuites=. */ static VALUE ossl_sslctx_set_ciphers(VALUE self, VALUE v) @@ -1035,6 +1039,7 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v) VALUE str; rb_check_frozen(self); + // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; @@ -1051,9 +1056,8 @@ ossl_sslctx_set_ciphers(VALUE self, VALUE v) * call-seq: * ctx.ciphersuites = "cipher1:cipher2:..." * ctx.ciphersuites = [name, ...] - * ctx.ciphersuites = [[name, version, bits, alg_bits], ...] * - * Sets the list of available TLSv1.3 cipher suites for this context. + * Sets the list of available TLS 1.3 cipher suites for this context. */ static VALUE ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) @@ -1062,6 +1066,7 @@ ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) VALUE str; rb_check_frozen(self); + // Assigning nil is a no-op for compatibility if (NIL_P(v)) return v; @@ -1074,6 +1079,63 @@ ossl_sslctx_set_ciphersuites(VALUE self, VALUE v) return v; } +#ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST +/* + * call-seq: + * ctx.sigalgs = "sigalg1:sigalg2:..." + * + * Sets the list of "supported signature algorithms" for this context. + * + * For a TLS client, the list is used in the "signature_algorithms" extension + * in the ClientHello message. For a server, the list is used by OpenSSL to + * determine the set of shared signature algorithms. OpenSSL will pick the most + * appropriate one from it. + * + * See also #client_sigalgs= for the client authentication equivalent. + */ +static VALUE +ossl_sslctx_set_sigalgs(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + + if (!SSL_CTX_set1_sigalgs_list(ctx, StringValueCStr(v))) + ossl_raise(eSSLError, "SSL_CTX_set1_sigalgs_list"); + + return v; +} +#endif + +#ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST +/* + * call-seq: + * ctx.client_sigalgs = "sigalg1:sigalg2:..." + * + * Sets the list of "supported signature algorithms" for client authentication + * for this context. + * + * For a TLS server, the list is sent to the client as part of the + * CertificateRequest message. + * + * See also #sigalgs= for the server authentication equivalent. + */ +static VALUE +ossl_sslctx_set_client_sigalgs(VALUE self, VALUE v) +{ + SSL_CTX *ctx; + + rb_check_frozen(self); + GetSSLCTX(self, ctx); + + if (!SSL_CTX_set1_client_sigalgs_list(ctx, StringValueCStr(v))) + ossl_raise(eSSLError, "SSL_CTX_set1_client_sigalgs_list"); + + return v; +} +#endif + #ifndef OPENSSL_NO_DH /* * call-seq: @@ -2887,6 +2949,12 @@ Init_ossl_ssl(void) rb_define_method(cSSLContext, "ciphers", ossl_sslctx_get_ciphers, 0); rb_define_method(cSSLContext, "ciphers=", ossl_sslctx_set_ciphers, 1); rb_define_method(cSSLContext, "ciphersuites=", ossl_sslctx_set_ciphersuites, 1); +#ifdef HAVE_SSL_CTX_SET1_SIGALGS_LIST // Not in LibreSSL yet + rb_define_method(cSSLContext, "sigalgs=", ossl_sslctx_set_sigalgs, 1); +#endif +#ifdef HAVE_SSL_CTX_SET1_CLIENT_SIGALGS_LIST // Not in LibreSSL or AWS-LC yet + rb_define_method(cSSLContext, "client_sigalgs=", ossl_sslctx_set_client_sigalgs, 1); +#endif #ifndef OPENSSL_NO_DH rb_define_method(cSSLContext, "tmp_dh=", ossl_sslctx_set_tmp_dh, 1); #endif diff --git a/test/openssl/test_ssl.rb b/test/openssl/test_ssl.rb index 4642063f4..61c26b5dd 100644 --- a/test/openssl/test_ssl.rb +++ b/test/openssl/test_ssl.rb @@ -1968,6 +1968,84 @@ def test_ciphers_method_bogus_csuite ) { ssl_ctx.ciphers = 'BOGUS' } end + def test_sigalgs + omit "SSL_CTX_set1_sigalgs_list() not supported" if libressl? + + svr_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@svr, ecdsa_key, 10, svr_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + # Unset values set by start_server + ctx.cert = ctx.key = ctx.extra_chain_cert = nil + ctx.add_certificate(@svr_cert, @svr_key, [@ca_cert]) # RSA + ctx.add_certificate(ecdsa_cert, ecdsa_key, [@ca_cert]) # ECDSA + } + start_server(ctx_proc: ctx_proc) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.sigalgs = "rsa_pss_rsae_sha256" + server_connect(port, ctx1) { |ssl| + assert_kind_of(OpenSSL::PKey::RSA, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.sigalgs = "ed25519:ecdsa_secp256r1_sha256" + server_connect(port, ctx2) { |ssl| + assert_kind_of(OpenSSL::PKey::EC, ssl.peer_cert.public_key) + ssl.puts("abc"); ssl.gets + } + end + + # Frozen + ssl_ctx = OpenSSL::SSL::SSLContext.new + ssl_ctx.freeze + assert_raise(FrozenError) { ssl_ctx.sigalgs = "ECDSA+SHA256:RSA+SHA256" } + + # Bogus + ssl_ctx = OpenSSL::SSL::SSLContext.new + assert_raise(TypeError) { ssl_ctx.sigalgs = nil } + assert_raise(OpenSSL::SSL::SSLError) { ssl_ctx.sigalgs = "BOGUS" } + end + + def test_client_sigalgs + omit "SSL_CTX_set1_client_sigalgs_list() not supported" if libressl? || aws_lc? + + cli_exts = [ + ["keyUsage", "keyEncipherment,digitalSignature", true], + ["subjectAltName", "DNS:localhost", false], + ] + ecdsa_key = Fixtures.pkey("p256") + ecdsa_cert = issue_cert(@cli, ecdsa_key, 10, cli_exts, @ca_cert, @ca_key) + + ctx_proc = -> ctx { + store = OpenSSL::X509::Store.new + store.add_cert(@ca_cert) + store.purpose = OpenSSL::X509::PURPOSE_SSL_CLIENT + ctx.cert_store = store + ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER|OpenSSL::SSL::VERIFY_FAIL_IF_NO_PEER_CERT + ctx.client_sigalgs = "ECDSA+SHA256" + } + start_server(ctx_proc: ctx_proc, ignore_listener_error: true) do |port| + ctx1 = OpenSSL::SSL::SSLContext.new + ctx1.add_certificate(@cli_cert, @cli_key) # RSA + assert_handshake_error { + server_connect(port, ctx1) { |ssl| + ssl.puts("abc"); ssl.gets + } + } + + ctx2 = OpenSSL::SSL::SSLContext.new + ctx2.add_certificate(ecdsa_cert, ecdsa_key) # ECDSA + server_connect(port, ctx2) { |ssl| + ssl.puts("abc"); ssl.gets + } + end + end + def test_connect_works_when_setting_dh_callback_to_nil omit "AWS-LC does not support DHE ciphersuites" if aws_lc?