From b8ca9da90d33d83ccbcd0166abd2bd398b88eb20 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Sun, 21 Jun 2026 23:12:55 +0900 Subject: [PATCH 1/2] Add HTTPS tests with iPAddress SAN Add a new test case to confirm the current behavior. Commit fa68e64beee8 fixed the handling of certificate identity check when connecting to an IP address, but the happy path is currently not covered by the test suite. --- test/net/fixtures/Makefile | 11 +++++--- test/net/fixtures/server.crt | 39 +++++++++++++++-------------- test/net/fixtures/server_ip_san.crt | 22 ++++++++++++++++ test/net/http/test_https.rb | 32 ++++++++++++++++++----- 4 files changed, 75 insertions(+), 29 deletions(-) create mode 100644 test/net/fixtures/server_ip_san.crt diff --git a/test/net/fixtures/Makefile b/test/net/fixtures/Makefile index 88c232e3..2b37c0bb 100644 --- a/test/net/fixtures/Makefile +++ b/test/net/fixtures/Makefile @@ -7,9 +7,12 @@ regen_certs: cacert.pem: server.key openssl req -new -x509 -days 3650 -key server.key -out cacert.pem -subj "/C=JP/ST=Shimane/L=Matz-e city/O=Ruby Core Team/CN=Ruby Test CA/emailAddress=security@ruby-lang.org" -server.csr: - openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=localhost" +server.crt: cacert.pem + openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=Server 1" -addext "subjectAltName = DNS:localhost" + openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -copy_extensions copy -out server.crt + rm server.csr -server.crt: server.csr cacert.pem - openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 00 -in server.csr -req -out server.crt +server_ip_san.crt: cacert.pem + openssl req -new -key server.key -out server.csr -subj "/C=JP/ST=Shimane/O=Ruby Core Team/OU=Ruby Test/CN=Server 2" -addext "subjectAltName = IP:127.0.0.1,IP:::1" + openssl x509 -days 3650 -CA cacert.pem -CAkey server.key -set_serial 01 -in server.csr -req -copy_extensions copy -out server_ip_san.crt rm server.csr diff --git a/test/net/fixtures/server.crt b/test/net/fixtures/server.crt index 5d292379..23d5efd5 100644 --- a/test/net/fixtures/server.crt +++ b/test/net/fixtures/server.crt @@ -1,21 +1,22 @@ -----BEGIN CERTIFICATE----- -MIIDYTCCAkkCAQAwDQYJKoZIhvcNAQELBQAwgYwxCzAJBgNVBAYTAkpQMRAwDgYD -VQQIDAdTaGltYW5lMRQwEgYDVQQHDAtNYXR6LWUgY2l0eTEXMBUGA1UECgwOUnVi -eSBDb3JlIFRlYW0xFTATBgNVBAMMDFJ1YnkgVGVzdCBDQTElMCMGCSqGSIb3DQEJ -ARYWc2VjdXJpdHlAcnVieS1sYW5nLm9yZzAeFw0yNDAxMDExMTQ3MjNaFw0zMzEy -MjkxMTQ3MjNaMGAxCzAJBgNVBAYTAkpQMRAwDgYDVQQIDAdTaGltYW5lMRcwFQYD -VQQKDA5SdWJ5IENvcmUgVGVhbTESMBAGA1UECwwJUnVieSBUZXN0MRIwEAYDVQQD -DAlsb2NhbGhvc3QwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCw+egZ -Q6eumJKq3hfKfED4dE/tL4FI5sjqont9ABVI+1GSqyi1bFBgsRjM0THllIdMbKmJ -tWwnKW8J+5OgNN8y6Xxv8JmM/Y5vQt2lis0fqXmG8UTz0VTWdlAXXmhUs6lSADvA -aIe4RVrCsZ97L3ZQTryY7JRVcbB4khUN3Gp0yg+801SXzoFTTa+UGIRLE66jH51a -a5VXu99hnv1OiH8tQrjdi8mH6uG/icq4XuIeNWMF32wHqIOOPvQcWV3M5D2vxJEj -702Ku6k9OQXkAo17qRSEonWW4HtLbtmS8He1JNPc/n3dVUm+fM6NoDXPoLP7j55G -9zKyqGtGAWXAj1MTAgMBAAEwDQYJKoZIhvcNAQELBQADggEBACtGNdj5TEtnJBYp -M+LhBeU3oNteldfycEm993gJp6ghWZFg23oX8fVmyEeJr/3Ca9bAgDqg0t9a0npN -oWKEY6wVKqcHgu3gSvThF5c9KhGbeDDmlTSVVNQmXWX0K2d4lS2cwZHH8mCm2mrY -PDqlEkSc7k4qSiqigdS8i80Yk+lDXWsm8CjsiC93qaRM7DnS0WPQR0c16S95oM6G -VklFKUSDAuFjw9aVWA/nahOucjn0w5fVW6lyIlkBslC1ChlaDgJmvhz+Ol3iMsE0 -kAmFNu2KKPVrpMWaBID49QwQTDyhetNLaVVFM88iUdA9JDoVMEuP1mm39JqyzHTu -uBrdP4Q= +MIIDnjCCAoagAwIBAgIBADANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCSlAx +EDAOBgNVBAgMB1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQK +DA5SdWJ5IENvcmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZI +hvcNAQkBFhZzZWN1cml0eUBydWJ5LWxhbmcub3JnMB4XDTI2MDYyMTEzMTQ0M1oX +DTM2MDYxODEzMTQ0M1owXzELMAkGA1UEBhMCSlAxEDAOBgNVBAgMB1NoaW1hbmUx +FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRIwEAYDVQQLDAlSdWJ5IFRlc3QxETAP +BgNVBAMMCFNlcnZlciAxMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +sPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqsotWxQYLEYzNEx5ZSH +TGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE89FU1nZQF15oVLOp +UgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNUl86BU02vlBiESxOu +ox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9sB6iDjj70HFldzOQ9 +r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P593VVJvnzOjaA1z6Cz ++4+eRvcysqhrRgFlwI9TEwIDAQABozcwNTAUBgNVHREEDTALgglsb2NhbGhvc3Qw +HQYDVR0OBBYEFIkZWV4O8Wn1y71H4TT84pjMaTCRMA0GCSqGSIb3DQEBCwUAA4IB +AQCUav+Av881U4D5HXYaU4JH3AYLP3pmaCAnaASbkCV5WjjIo8A4jLC5FrgibaQc +4vw4JdHaO8JK05qb61rtzQLPyk8fIrV8LAcisBMBRnawgo98EB6OVXjpgKSRm4YU +7SlYpnO+D9jMc1ECl8K1ILQyuT4+zgEFREZLNGjKuaJXDExD8JPhT7DumJ5XMGoI +2r2YWCfnsef1RbGtze30nAJqcf6XrHyn95VU70AV45yXuE/FOZDb8dX90n1K3Frm +ebqT0Q8smwLB+R9+BhWjhlW3oj8hLtsLBPz5U7Fp4rg2nMoOSvrrqgkRyhoxzJie +R+WNFodMHu0PxkmSGD37G76S -----END CERTIFICATE----- diff --git a/test/net/fixtures/server_ip_san.crt b/test/net/fixtures/server_ip_san.crt new file mode 100644 index 00000000..505a08cd --- /dev/null +++ b/test/net/fixtures/server_ip_san.crt @@ -0,0 +1,22 @@ +-----BEGIN CERTIFICATE----- +MIIDqzCCApOgAwIBAgIBATANBgkqhkiG9w0BAQsFADCBjDELMAkGA1UEBhMCSlAx +EDAOBgNVBAgMB1NoaW1hbmUxFDASBgNVBAcMC01hdHotZSBjaXR5MRcwFQYDVQQK +DA5SdWJ5IENvcmUgVGVhbTEVMBMGA1UEAwwMUnVieSBUZXN0IENBMSUwIwYJKoZI +hvcNAQkBFhZzZWN1cml0eUBydWJ5LWxhbmcub3JnMB4XDTI2MDYyMTEzMTQ0M1oX +DTM2MDYxODEzMTQ0M1owXzELMAkGA1UEBhMCSlAxEDAOBgNVBAgMB1NoaW1hbmUx +FzAVBgNVBAoMDlJ1YnkgQ29yZSBUZWFtMRIwEAYDVQQLDAlSdWJ5IFRlc3QxETAP +BgNVBAMMCFNlcnZlciAyMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA +sPnoGUOnrpiSqt4XynxA+HRP7S+BSObI6qJ7fQAVSPtRkqsotWxQYLEYzNEx5ZSH +TGypibVsJylvCfuToDTfMul8b/CZjP2Ob0LdpYrNH6l5hvFE89FU1nZQF15oVLOp +UgA7wGiHuEVawrGfey92UE68mOyUVXGweJIVDdxqdMoPvNNUl86BU02vlBiESxOu +ox+dWmuVV7vfYZ79Toh/LUK43YvJh+rhv4nKuF7iHjVjBd9sB6iDjj70HFldzOQ9 +r8SRI+9NirupPTkF5AKNe6kUhKJ1luB7S27ZkvB3tSTT3P593VVJvnzOjaA1z6Cz ++4+eRvcysqhrRgFlwI9TEwIDAQABo0QwQjAhBgNVHREEGjAYhwR/AAABhxAAAAAA +AAAAAAAAAAAAAAABMB0GA1UdDgQWBBSJGVleDvFp9cu9R+E0/OKYzGkwkTANBgkq +hkiG9w0BAQsFAAOCAQEAi8zbc22L5+8UGjXftk2Dfg814rsholLK4h8eXHptRh1k +cqRJ0DrXn1SbiavBAkRUBeh07CtSOaxq/LP9ZiJHAcZ99+ZDVG0BRmg4esh9LEiV +FuM7piCfdsDRBfzk25Ko8lQEq1l3aIUSzMpaQV8OvZC51JM8EBGxj3r6n5iZf0b9 +3yKVZme1l2mnMSvCeJ9VY1FCnIEwF1WTzBgOQLQOu4zRlR1O0xtn4+KIL79F7XSE +HB0Sni8krQA7tDprMQsKLoLmvPpFrkTHyR3D7/BTvsE3N0k6cPV9WJTg4wOyT4JC +7Gm1sdFNKo2MhdE/YXKqb5+rv2ILfezwtdcVlB23wQ== +-----END CERTIFICATE----- diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index f5b21b90..411c9d81 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -286,18 +286,19 @@ def test_ractor end if defined?(Ractor) && Ractor.method_defined?(:value) end -class TestNetHTTPSIdentityVerifyFailure < Test::Unit::TestCase +class TestNetHTTPSIPAddressCertificate < Test::Unit::TestCase include TestNetHTTPUtils def self.read_fixture(key) File.read(File.expand_path("../fixtures/#{key}", __dir__)) end + # Certificate subjectAltName contains IP addresses 127.0.0.1 but no DNS names HOST = 'localhost' HOST_IP = '127.0.0.1' CA_CERT = OpenSSL::X509::Certificate.new(read_fixture("cacert.pem")) SERVER_KEY = OpenSSL::PKey.read(read_fixture("server.key")) - SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server.crt")) + SERVER_CERT = OpenSSL::X509::Certificate.new(read_fixture("server_ip_san.crt")) TEST_STORE = OpenSSL::X509::Store.new.tap {|s| s.add_cert(CA_CERT) } CONFIG = { @@ -309,17 +310,36 @@ def self.read_fixture(key) 'ssl_private_key' => SERVER_KEY, } - def test_identity_verify_failure - # the certificate's subject has CN=localhost + def test_identity_verify_success http = Net::HTTP.new(HOST_IP, config("port")) http.use_ssl = true http.cert_store = TEST_STORE + http.request_get("/") {|res| + assert_equal($test_net_http_data, res.body) + } + end + + def test_identity_verify_failure + http = Net::HTTP.new("172.0.0.2", config("port")) + http.ipaddr = HOST_IP + http.use_ssl = true + http.cert_store = TEST_STORE @log_tester = lambda {|_| } ex = assert_raise(OpenSSL::SSL::SSLError){ http.request_get("/") {|res| } - sleep 0.5 } - re_msg = /certificate verify failed|hostname \"#{HOST_IP}\" does not match/ + re_msg = /certificate verify failed|hostname .* does not match/ assert_match(re_msg, ex.message) end + + def test_identity_verify_disabled + http = Net::HTTP.new("172.0.0.2", config("port")) + http.ipaddr = HOST_IP + http.use_ssl = true + http.cert_store = TEST_STORE + http.verify_hostname = false + http.request_get("/") {|res| + assert_equal($test_net_http_data, res.body) + } + end end From 40107ee4238da060b1bab83ad432bd033f04b5d4 Mon Sep 17 00:00:00 2001 From: Kazuki Yamaguchi Date: Mon, 22 Jun 2026 00:03:08 +0900 Subject: [PATCH 2/2] Expose OpenSSL::SSL::SSLContext object Add Net::HTTP#ssl_context to return the SSLContext used for HTTPS connections, and reuse the same object for future connections made by the same Net::HTTP instance. Also change existing SSL/TLS-related accessors to interact with the SSLContext directly, rather than temporarily storing values in instance variables and applying them later in Net::HTTP#start. Note that the SSLContext becomes frozen after the first connection is made using the Net::HTTP instance. Although I do not expect this to affect most users, it is a compatibility concern. [Feature #19641] --- lib/net/http.rb | 196 ++++++++++++++++++++++-------------- test/net/http/test_https.rb | 25 ++++- 2 files changed, 141 insertions(+), 80 deletions(-) diff --git a/lib/net/http.rb b/lib/net/http.rb index 7fd4c3ed..5041181f 100644 --- a/lib/net/http.rb +++ b/lib/net/http.rb @@ -1192,10 +1192,8 @@ def initialize(address, port = nil) # :nodoc: @use_ssl = false @ssl_context = nil @ssl_session = nil + @verify_hostname = true @sspi_enabled = false - SSL_IVNAMES.each do |ivname| - instance_variable_set ivname, nil - end end # Returns a string representation of +self+: @@ -1515,81 +1513,155 @@ def use_ssl=(flag) @use_ssl = flag end - SSL_ATTRIBUTES = [ - :ca_file, - :ca_path, - :cert, - :cert_store, - :ciphers, - :extra_chain_cert, - :key, - :ssl_timeout, - :ssl_version, - :min_version, - :max_version, - :verify_callback, - :verify_depth, - :verify_mode, - :verify_hostname, - ].freeze # :nodoc: - - SSL_IVNAMES = SSL_ATTRIBUTES.map { |a| "@#{a}".to_sym }.freeze # :nodoc: + def ssl_sni_enabled? + case @address + when Resolv::IPv4::Regex, Resolv::IPv6::Regex + return false + end + true + end + private :ssl_sni_enabled? + + # Returns the OpenSSL::SSL::SSLContext object used for SSL/TLS sessions. + # + # The object is frozen after a session is started for the first time and + # cannot be modified afterwards. + def ssl_context + @ssl_context ||= ( + ctx = OpenSSL::SSL::SSLContext.new + ctx.set_params + + # See the note on #verify_hostname + ctx.verify_hostname = verify_hostname && ssl_sni_enabled? + + unless ctx.session_cache_mode.nil? # a dummy method on JRuby + ctx.session_cache_mode = + OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | + OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE + end + if ctx.respond_to?(:session_new_cb) # not implemented under JRuby + ctx.session_new_cb = proc {|sock, sess| @ssl_session = sess } + end + + ctx + ) + end + + def_ssl_attr = proc do |attr| + class_eval <<~EOS, __FILE__, __LINE__ + 1 + def #{attr} + ssl_context.#{attr} + end + def #{attr}=(value) + ssl_context.#{attr} = value + end + EOS + end + + ## + # :attr_accessor: ca_file # Sets or returns the path to a CA certification file in PEM format. - attr_accessor :ca_file + def_ssl_attr.call(:ca_file) + ## + # :attr_accessor: ca_path # Sets or returns the path of to CA directory # containing certification files in PEM format. - attr_accessor :ca_path + def_ssl_attr.call(:ca_path) + ## + # :attr_accessor: cert # Sets or returns the OpenSSL::X509::Certificate object # to be used for client certification. - attr_accessor :cert + def_ssl_attr.call(:cert) + ## + # :attr_accessor: cert_store # Sets or returns the X509::Store to be used for verifying peer certificate. - attr_accessor :cert_store + def_ssl_attr.call(:cert_store) + ## + # :attr_accessor: ciphers # Sets or returns the available SSL ciphers. # See {OpenSSL::SSL::SSLContext#ciphers=}[OpenSSL::SSL::SSL::Context#ciphers=]. - attr_accessor :ciphers + def_ssl_attr.call(:ciphers) + ## + # :attr_accessor: extra_chain_cert # Sets or returns the extra X509 certificates to be added to the certificate chain. # See {OpenSSL::SSL::SSLContext#add_certificate}[OpenSSL::SSL::SSL::Context#add_certificate]. - attr_accessor :extra_chain_cert + def_ssl_attr.call(:extra_chain_cert) + ## + # :attr_accessor: key # Sets or returns the OpenSSL::PKey::RSA or OpenSSL::PKey::DSA object. - attr_accessor :key + def_ssl_attr.call(:key) + ## + # :attr_accessor: ssl_timeout # Sets or returns the SSL timeout seconds. - attr_accessor :ssl_timeout + def_ssl_attr.call(:ssl_timeout) + ## + # :attr_accessor: ssl_version # Sets or returns the SSL version. # See {OpenSSL::SSL::SSLContext#ssl_version=}[OpenSSL::SSL::SSL::Context#ssl_version=]. - attr_accessor :ssl_version + def_ssl_attr.call(:ssl_version) + ## + # :attr_accessor: min_version # Sets or returns the minimum SSL version. # See {OpenSSL::SSL::SSLContext#min_version=}[OpenSSL::SSL::SSL::Context#min_version=]. - attr_accessor :min_version + def_ssl_attr.call(:min_version) + ## + # :attr_accessor: max_version # Sets or returns the maximum SSL version. # See {OpenSSL::SSL::SSLContext#max_version=}[OpenSSL::SSL::SSL::Context#max_version=]. - attr_accessor :max_version + def_ssl_attr.call(:max_version) + ## + # :attr_accessor: verify_callback # Sets or returns the callback for the server certification verification. - attr_accessor :verify_callback + def_ssl_attr.call(:verify_callback) + ## + # :attr_accessor: verify_depth # Sets or returns the maximum depth for the certificate chain verification. - attr_accessor :verify_depth + def_ssl_attr.call(:verify_depth) + ## + # :attr_accessor: verify_mode # Sets or returns the flags for server the certification verification # at the beginning of the SSL/TLS session. # OpenSSL::SSL::VERIFY_NONE or OpenSSL::SSL::VERIFY_PEER are acceptable. - attr_accessor :verify_mode + def_ssl_attr.call(:verify_mode) + ## + # :attr_accessor: verify_hostname # Sets or returns whether to verify that the server certificate is valid - # for the hostname. + # for the hostname or the IP address of the server. # See {OpenSSL::SSL::SSLContext#verify_hostname=}[OpenSSL::SSL::SSL::Context#verify_hostname=]. - attr_accessor :verify_hostname + #-- + # This attribute is not forwarded directly to SSLContext because the + # semantics are slightly different. + # - SSLContext#verify_hostname must be used together with SNI, which + # is unavailable when connecting to an IP address. + # - Net::HTTP#verify_hostname is expected to verify the certificate + # identity regardless of whether SNI is used. + #++ + + # :stopdoc: + def verify_hostname + @verify_hostname + end + + def verify_hostname=(value) + @verify_hostname = value + ssl_context.verify_hostname = value && ssl_sni_enabled? + end + # :startdoc: # Returns the X509 certificate chain (an array of strings) # for the session's socket peer, @@ -1661,7 +1733,7 @@ def connect if use_ssl? # reference early to load OpenSSL before connecting, # as OpenSSL may take time to load. - @ssl_context = OpenSSL::SSL::SSLContext.new + ssl_context.setup end if proxy? then @@ -1708,53 +1780,21 @@ def connect # assuming nothing left in buffers after successful CONNECT response end - ssl_parameters = Hash.new - iv_list = instance_variables - SSL_IVNAMES.each_with_index do |ivname, i| - if iv_list.include?(ivname) - value = instance_variable_get(ivname) - unless value.nil? - ssl_parameters[SSL_ATTRIBUTES[i]] = value - end - end - end - @ssl_context.set_params(ssl_parameters) - unless @ssl_context.session_cache_mode.nil? # a dummy method on JRuby - @ssl_context.session_cache_mode = - OpenSSL::SSL::SSLContext::SESSION_CACHE_CLIENT | - OpenSSL::SSL::SSLContext::SESSION_CACHE_NO_INTERNAL_STORE - end - if @ssl_context.respond_to?(:session_new_cb) # not implemented under JRuby - @ssl_context.session_new_cb = proc {|sock, sess| @ssl_session = sess } - end - - # Still do the post_connection_check below even if connecting - # to IP address - verify_hostname = @ssl_context.verify_hostname - - # Server Name Indication (SNI) RFC 3546/6066 - case @address - when Resolv::IPv4::Regex, Resolv::IPv6::Regex - # don't set SNI, as IP addresses in SNI is not valid - # per RFC 6066, section 3. - - # Avoid openssl warning - @ssl_context.verify_hostname = false - else - ssl_host_address = @address - end - debug "starting SSL for #{conn_addr}:#{conn_port}..." - s = OpenSSL::SSL::SSLSocket.new(s, @ssl_context) + s = OpenSSL::SSL::SSLSocket.new(s, ssl_context) s.sync_close = true - s.hostname = ssl_host_address if s.respond_to?(:hostname=) && ssl_host_address + # Server Name Indication (SNI) RFC 3546/6066 + s.hostname = @address if ssl_sni_enabled? if @ssl_session and Process.clock_gettime(Process::CLOCK_REALTIME) < @ssl_session.time.to_f + @ssl_session.timeout s.session = @ssl_session end ssl_socket_connect(s, @open_timeout) - if (@ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE) && verify_hostname + # See the note on #verify_hostname + if !ssl_sni_enabled? && + ssl_context.verify_mode != OpenSSL::SSL::VERIFY_NONE && + verify_hostname s.post_connection_check(@address) end debug "SSL established, protocol: #{s.ssl_version}, cipher: #{s.cipher[0]}" diff --git a/test/net/http/test_https.rb b/test/net/http/test_https.rb index 411c9d81..eb5f2727 100644 --- a/test/net/http/test_https.rb +++ b/test/net/http/test_https.rb @@ -256,16 +256,37 @@ def test_min_version def test_max_version http = Net::HTTP.new(HOST, config("port")) http.use_ssl = true - http.max_version = :SSL2 + begin + http.max_version = :SSL2 + rescue OpenSSL::SSL::SSLError => e + # May fail early if SSLv2 is not supported at all by the OpenSSL release + return if /SSL_CTX_set_max_proto_version/ =~ e.message + raise + end http.cert_store = TEST_STORE @log_tester = lambda {|_| } ex = assert_raise(OpenSSL::SSL::SSLError){ http.request_get("/") {|res| } } - re_msg = /\ASSL_connect returned=1 errno=0 |SSL_CTX_set_max_proto_version|No appropriate protocol/ + re_msg = /\ASSL_connect returned=1 errno=0 |No appropriate protocol/ assert_match(re_msg, ex.message) end + def test_ssl_context + http = Net::HTTP.new(HOST, config("port")) + http.use_ssl = true + http.cert_store = TEST_STORE + assert_same(TEST_STORE, http.ssl_context.cert_store) + http.ssl_context.cert_store = OpenSSL::X509::Store.new # empty + assert_raise(OpenSSL::SSL::SSLError) { + http.request_get("/") {|res| } + } + assert_predicate(http.ssl_context, :frozen?) + assert_raise(FrozenError) { + http.cert_store = OpenSSL::X509::Store.new + } + end + def test_ractor assert_ractor(<<~RUBY, require: 'net/https') expected = #{$test_net_http_data.dump}.b