Skip to content

Commit 9640a93

Browse files
karesclaude
andcommitted
[test] peer_cert_chain server-side behavior difference
OpenSSL's SSL_get_peer_cert_chain excludes the peer's leaf cert on the server side (available via peer_cert). JSSE's getPeerCertificates always includes it. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 08afe67 commit 9640a93

1 file changed

Lines changed: 58 additions & 0 deletions

File tree

src/test/ruby/ssl/test_ssl.rb

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)