Skip to content

Commit 08afe67

Browse files
karesclaude
andcommitted
[fix] prepend leaf cert to extra_chain_cert (#181)
OpenSSL's ssl_add_cert_chain sends the leaf cert (cpk->x509) first, then iterates extra_certs. JRuby's KeyManagerImpl.getCertificateChain was returning only the extra_chain_cert list without the leaf, causing failures when the server couldn't find the client certificate. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 1b548a7 commit 08afe67

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

src/main/java/org/jruby/ext/openssl/SSLContext.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1137,7 +1137,12 @@ public java.security.cert.X509Certificate[] getCertificateChain(String alias) {
11371137
final List<java.security.cert.X509Certificate> chain;
11381138

11391139
if ( internalContext.extraChainCert != null ) {
1140-
chain = (List) internalContext.extraChainCert;
1140+
// C OpenSSL (ssl_add_cert_chain) sends the leaf cert first,
1141+
// then iterates extra_certs. JSSE's getCertificateChain must
1142+
// return the full chain starting with the leaf.
1143+
chain = new ArrayList<>(internalContext.extraChainCert.size() + 1);
1144+
if ( internalContext.cert != null ) chain.add(internalContext.cert);
1145+
chain.addAll(internalContext.extraChainCert);
11411146
}
11421147
else if ( internalContext.cert != null ) {
11431148
chain = new ArrayList<>(8);

src/test/ruby/ssl/test_ssl.rb

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -347,6 +347,49 @@ def test_verify_hostname_failure_error_code_via_callback
347347
end
348348
end
349349

350+
# GH-181: extra_chain_cert must include the leaf cert when sent over the wire.
351+
# C OpenSSL's ssl_add_cert_chain sends cpk->x509 first, then extra_certs.
352+
def test_extra_chain_cert_sends_leaf
353+
# Create CA -> Intermediate -> Leaf
354+
now = Time.now
355+
int_key = OpenSSL::PKey::RSA.new(2048)
356+
int_cert = issue_cert(
357+
OpenSSL::X509::Name.parse("/CN=Intermediate"), int_key, 10,
358+
[["basicConstraints","CA:TRUE",true],["keyUsage","cRLSign,keyCertSign",true]],
359+
@ca_cert, @ca_key, not_before: now, not_after: now + 3600)
360+
leaf_key = OpenSSL::PKey::RSA.new(2048)
361+
leaf_cert = issue_cert(
362+
OpenSSL::X509::Name.parse("/CN=Leaf"), leaf_key, 11,
363+
[["keyUsage","keyEncipherment,digitalSignature",true]],
364+
int_cert, int_key, not_before: now, not_after: now + 1800)
365+
366+
# Server trusts CA and verifies client certs
367+
ctx_proc = Proc.new do |ctx|
368+
ctx.verify_mode = OpenSSL::SSL::VERIFY_PEER
369+
ctx.cert_store = OpenSSL::X509::Store.new
370+
ctx.cert_store.add_cert(@ca_cert)
371+
end
372+
373+
server_proc = Proc.new do |sctx, ssl|
374+
# Server should see the leaf as peer_cert
375+
assert_equal "/CN=Leaf", ssl.peer_cert.subject.to_s
376+
readwrite_loop(sctx, ssl)
377+
end
378+
379+
start_server(OpenSSL::SSL::VERIFY_NONE, true,
380+
ctx_proc: ctx_proc, server_proc: server_proc) do |server, port|
381+
cctx = OpenSSL::SSL::SSLContext.new
382+
cctx.cert = leaf_cert
383+
cctx.key = leaf_key
384+
cctx.extra_chain_cert = [int_cert]
385+
cctx.verify_mode = OpenSSL::SSL::VERIFY_NONE
386+
server_connect(port, cctx) do |ssl|
387+
ssl.puts "hello"
388+
assert_equal "hello\n", ssl.gets
389+
end
390+
end
391+
end
392+
350393
def test_post_connect_check_with_anon_ciphers
351394
unless OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384
352395
return skip('OpenSSL::ExtConfig::TLS_DH_anon_WITH_AES_256_GCM_SHA384 not enabled')

0 commit comments

Comments
 (0)