@@ -274,6 +274,79 @@ def test_verify_result_with_verify_peer_self_signed
274274 end
275275 end
276276
277+ # verify_result should report V_ERR_HOSTNAME_MISMATCH when hostname
278+ # verification fails during connect (matches CRuby behavior).
279+ def test_verify_result_hostname_mismatch
280+ now = Time . now
281+ exts = [
282+ [ "keyUsage" , "keyEncipherment,digitalSignature" , true ] ,
283+ [ "subjectAltName" , "DNS:a.example.com" , false ] ,
284+ ]
285+ @svr_cert = issue_cert ( @svr , @svr_key , 4 , exts , @ca_cert , @ca_key ,
286+ not_before : now , not_after : now + 1800 )
287+
288+ start_server0 ( PORT , OpenSSL ::SSL ::VERIFY_NONE , true ) do |server , port |
289+ ctx = OpenSSL ::SSL ::SSLContext . new
290+ ctx . verify_hostname = true
291+ ctx . verify_mode = OpenSSL ::SSL ::VERIFY_PEER
292+ ctx . cert_store = OpenSSL ::X509 ::Store . new
293+ ctx . cert_store . add_cert ( @ca_cert )
294+
295+ sock = TCPSocket . new ( "127.0.0.1" , port )
296+ ssl = OpenSSL ::SSL ::SSLSocket . new ( sock , ctx )
297+ ssl . hostname = "b.example.com"
298+ assert_raise ( OpenSSL ::SSL ::SSLError ) { ssl . connect }
299+ assert_equal OpenSSL ::X509 ::V_ERR_HOSTNAME_MISMATCH , ssl . verify_result
300+ ensure
301+ ssl &.close rescue nil
302+ sock &.close rescue nil
303+ end
304+ end
305+
306+ # Ported from CRuby's test_verify_hostname_failure_error_code.
307+ # CRuby invokes verify_callback with V_ERR_HOSTNAME_MISMATCH because the
308+ # hostname check runs inside OpenSSL's verify callback during handshake.
309+ # JRuby checks hostname post-handshake (JSSE limitation), so the callback
310+ # doesn't see the error. Skipped on JRuby; runs on CRuby for parity check.
311+ def test_verify_hostname_failure_error_code_via_callback
312+ skip 'verify_callback not invoked for hostname mismatch (JSSE limitation)' if defined? ( JRUBY_VERSION )
313+
314+ now = Time . now
315+ exts = [
316+ [ "keyUsage" , "keyEncipherment,digitalSignature" , true ] ,
317+ [ "subjectAltName" , "DNS:a.example.com" , false ] ,
318+ ]
319+ @svr_cert = issue_cert ( @svr , @svr_key , 4 , exts , @ca_cert , @ca_key ,
320+ not_before : now , not_after : now + 1800 )
321+
322+ start_server0 ( PORT , OpenSSL ::SSL ::VERIFY_NONE , true ) do |server , port |
323+ verify_callback_ok = verify_callback_err = nil
324+
325+ ctx = OpenSSL ::SSL ::SSLContext . new
326+ ctx . verify_hostname = true
327+ ctx . cert_store = OpenSSL ::X509 ::Store . new
328+ ctx . cert_store . add_cert ( @ca_cert )
329+ ctx . verify_mode = OpenSSL ::SSL ::VERIFY_PEER
330+ ctx . verify_callback = -> ( preverify_ok , store_ctx ) {
331+ verify_callback_ok = preverify_ok
332+ verify_callback_err = store_ctx . error
333+ preverify_ok
334+ }
335+
336+ begin
337+ sock = TCPSocket . new ( "127.0.0.1" , port )
338+ ssl = OpenSSL ::SSL ::SSLSocket . new ( sock , ctx )
339+ ssl . hostname = "b.example.com"
340+ assert_raise ( OpenSSL ::SSL ::SSLError ) { ssl . connect }
341+ assert_equal false , verify_callback_ok
342+ assert_equal OpenSSL ::X509 ::V_ERR_HOSTNAME_MISMATCH , verify_callback_err
343+ ensure
344+ ssl &.close rescue nil
345+ sock &.close rescue nil
346+ end
347+ end
348+ end
349+
277350 def test_post_connect_check_with_anon_ciphers
278351 unless OpenSSL ::ExtConfig ::TLS_DH_anon_WITH_AES_256_GCM_SHA384
279352 return skip ( 'OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384 not enabled' )
0 commit comments