@@ -390,6 +390,64 @@ def test_extra_chain_cert_sends_leaf
390390 end
391391 end
392392
393+ # SSL_get_peer_cert_chain behavior differs between C OpenSSL and JSSE:
394+ # C OpenSSL server-side: excludes the peer's leaf cert (use peer_cert for that)
395+ # C OpenSSL client-side: includes the server's leaf cert
396+ # JSSE (both sides): always includes the leaf cert
397+ # This test documents the difference; both are valid TLS implementations.
398+ def test_peer_cert_chain_server_side
399+ now = Time . now
400+ int_key = OpenSSL ::PKey ::RSA . new ( 2048 )
401+ int_cert = issue_cert (
402+ OpenSSL ::X509 ::Name . parse ( "/CN=Intermediate" ) , int_key , 10 ,
403+ [ [ "basicConstraints" , "CA:TRUE" , true ] , [ "keyUsage" , "cRLSign,keyCertSign" , true ] ] ,
404+ @ca_cert , @ca_key , not_before : now , not_after : now + 3600 )
405+ leaf_key = OpenSSL ::PKey ::RSA . new ( 2048 )
406+ leaf_cert = issue_cert (
407+ OpenSSL ::X509 ::Name . parse ( "/CN=Leaf" ) , leaf_key , 11 ,
408+ [ [ "keyUsage" , "keyEncipherment,digitalSignature" , true ] ] ,
409+ int_cert , int_key , not_before : now , not_after : now + 1800 )
410+
411+ server_peer_cert = nil
412+ server_peer_chain = nil
413+
414+ ctx_proc = Proc . new do |ctx |
415+ ctx . verify_mode = OpenSSL ::SSL ::VERIFY_PEER
416+ ctx . cert_store = OpenSSL ::X509 ::Store . new
417+ ctx . cert_store . add_cert ( @ca_cert )
418+ end
419+
420+ server_proc = Proc . new do |sctx , ssl |
421+ server_peer_cert = ssl . peer_cert
422+ server_peer_chain = ssl . peer_cert_chain
423+ readwrite_loop ( sctx , ssl )
424+ end
425+
426+ start_server ( OpenSSL ::SSL ::VERIFY_NONE , true ,
427+ ctx_proc : ctx_proc , server_proc : server_proc ) do |server , port |
428+ cctx = OpenSSL ::SSL ::SSLContext . new
429+ cctx . cert = leaf_cert
430+ cctx . key = leaf_key
431+ cctx . extra_chain_cert = [ int_cert ]
432+ cctx . verify_mode = OpenSSL ::SSL ::VERIFY_NONE
433+ server_connect ( port , cctx ) do |ssl |
434+ ssl . puts "hello" ; ssl . gets
435+ end
436+ end
437+
438+ # Both: peer_cert is the leaf
439+ assert_equal "/CN=Leaf" , server_peer_cert . subject . to_s
440+
441+ chain_subjects = server_peer_chain . map { |c | c . subject . to_s }
442+ if defined? ( JRUBY_VERSION )
443+ # JSSE's getPeerCertificates always includes the leaf
444+ assert_equal [ "/CN=Leaf" , "/CN=Intermediate" ] , chain_subjects
445+ else
446+ # C OpenSSL's SSL_get_peer_cert_chain on server side excludes the leaf
447+ assert_equal [ "/CN=Intermediate" ] , chain_subjects
448+ end
449+ end
450+
393451 def test_post_connect_check_with_anon_ciphers
394452 unless OpenSSL ::ExtConfig ::TLS_DH_anon_WITH_AES_256_GCM_SHA384
395453 return skip ( 'OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384 not enabled' )
0 commit comments