Skip to content

Reject certificates containing blocked eMudhra intermediate CA public keys - #481

Merged
yyuu merged 8 commits into
nahi:masterfrom
yyuu:block-compromised-emudhra-keys
Oct 29, 2026
Merged

yyuu merged 8 commits into
nahi:masterfrom
yyuu:block-compromised-emudhra-keys

Conversation

@yyuu

@yyuu yyuu commented Oct 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Compromised eMudhra intermediate CA keys can remain trusted through cross-signed certificates even after related roots are removed. This adds a default safeguard to prevent HTTP data from being sent over connections presenting those keys.

Explicit verification overrides remain under the caller's control. The README, SSLConfig API documentation, and changelog explain the compatibility impact, including rejection of unused server-supplied certificates containing blocked keys.

On JRuby, this supplements JVM certificate validation and does not replace JVM security updates. Intermediates present only in local trust stores or obtained through JVM issuer retrieval may fall outside the check.

Testing

  • Regression tests cover rejection before HTTP transmission, cross-signed certificates, verification overrides, and hostname verification.
  • Local full suite on Ruby 3.4 / OpenSSL 3.5: 242 tests, 853 assertions, no failures or errors in both normal and frozen string modes (2 existing omissions). These runs used released rubyntlm 0.6.8 instead of the Gemfile's Git source; the JRuby full suite was not run.
  • git diff --check passed. The Ruby HEAD CI failure is tracked separately in #483.

References

Comment thread lib/httpclient/jruby_ssl_socket.rb Outdated
def ssl_connect(hostname)
@ssl_socket.startHandshake
ssl_session = @ssl_socket.getSession
@config.check_peer_certificates(ssl_session.getPeerCertificates)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Include locally trusted intermediates in the denylist check

This checks the peer-supplied certificates, but not the locally selected trust anchor.

On JRuby, if a configured trust store contains a blocked intermediate and the server sends only an otherwise valid leaf signed by that intermediate, JSSE can validate the leaf against the local intermediate. The verification callback has the same gap: SystemTrustManager#checkServerTrusted passes the original input chain to the callback after validation. Neither check sees the blocked intermediate, so the connection can proceed to HTTP even with peer verification enabled.

Please apply the denylist to the actual validated certification path, including its trust anchor, and add a regression test where the blocked intermediate exists only in the local trust store and the server sends only the leaf. The test should assert rejection before any HTTP request is sent.

This finding is based on source/API analysis, supported by a separate JDK trust-manager check. I have not reproduced it end-to-end with JRuby/HTTPClient.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, httpclient treats MRI and JRuby completely differently while handling TLS/SSL sockets. While MRI implementation manages CA certificates utilizing cacert.pem bundled as a part of library, JRuby implementation is relying on CA certificates managed by the JVM runtime. I'd think that for the latter case, the general responsibility to maintain the certificate handling should be on the runtime, and not on a library application like httpclient. This PR is focusing on the former case which httpclient should be responsible for on TLS handshake to make it secure enough.

Although the behavior on JRuby is surely NOT identical with MRI, I've added some extra side notes in README to explicitly mentioning it. Hope it works for clarification.

@nahi

nahi commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner

My understanding is that the intended use case for this PR is: To prevent a successful validation using a compromised certificate—even after the client has removed it from the trust store due to a key leak—if that certificate is sent by the server. It does not anticipate a scenario where a compromised certificate is used due to an incorrect client-side configuration.

Given this premise, I believe @takkanm 's point is not valid, I think.

However, there is one caveat. peer_cert_chain contains all certificates sent from the server; it is not a list of certificates used to construct the verification path. In other words, if a server sends a CA certificate that has been compromised, the connection will fail even if that certificate is not actually used.

While this is a possible change, it would not be advisable to implement it without documentation.

@yyuu

yyuu commented Oct 23, 2026

Copy link
Copy Markdown
Collaborator Author

That's a fair point. I should have been more cautious about changing the behavior w/o proper/explicit documentation of it. I'll figure out a way...

@yyuu yyuu changed the title Reject compromised eMudhra intermediate CA keys Reject certificates containing blocked eMudhra intermediate CA public keys Oct 24, 2026
@yyuu

yyuu commented Oct 24, 2026

Copy link
Copy Markdown
Collaborator Author

I happened to notice CI is broken with head even on the master branch. I'm planning to fix it separately from this PR as #483

@yyuu
yyuu requested a review from takkanm October 29, 2026 08:36
@yyuu
yyuu merged commit dcf7100 into nahi:master Oct 29, 2026
10 checks passed
@yyuu
yyuu deleted the block-compromised-emudhra-keys branch October 29, 2026 09:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants