JDK-8271266 : TLS: X509KeyManagerImpl can prematurely select wrong alias from keystore
  • Type: Bug
  • Component: security-libs
  • Sub-Component: jdk.security
  • Affected Version: 11,16,18
  • Priority: P4
  • Status: Closed
  • Resolution: Duplicate
  • OS: generic
  • CPU: generic
  • Submitted: 2021-07-19
  • Updated: 2022-02-07
  • Resolved: 2022-02-07
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
JDK 19
19Resolved
Related Reports
Duplicate :  
Description
ADDITIONAL SYSTEM INFORMATION :
Generic; tested with OpenJDK 16.0.1 64-bit on Windows 10

A DESCRIPTION OF THE PROBLEM :
When multiple certificates are available in the keystore, a wrong one can be prematurely chosen depending on the order of SignatureScheme entries in peerRequestedCertSignSchemes. Incorrect extended key usage of the certificates is then not taken into account.

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Consider the following scenario:
* Server S1 allows or requires TLS client authentication
* Server S2 has a keystore with two entries:
  1) Alias "signing", type EC, with Extended Key Usage 1.3.6.1.5.5.7.3.3 (code signing)
  2) Alias "tls", type RSA, with Extended Key Usage 1.3.6.1.5.5.7.3.2 (TLS client authentication)
S1 issues a HTTPS request to S2 (TLS 1.3). S2 will attempt to produce a client certificate, but can choose the "signing" key, causing a handshake failure.

The reproducing instructions are in the README.md in the project root.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The "tls" key should be selected for the TLS client authentication. The "signing" key is intended for some other application-specific purpose.
ACTUAL -
The handshake flow eventually reaches sun.security.ssl.CertificateMessage.T13CertificateProducer.produce(ConnectionContext, HandshakeMessage), which calls CertificateMessage.T13CertificateProducer.onProduceCertificate(ClientHandshakeContext, HandshakeMessage) because we're in client mode. This then calls CertificateMessage.T13CertificateProducer.choosePossession(HandshakeContext, ClientHelloMessage).

Let's assume that hc.peerRequestedCertSignSchemes contains the following list:
#0 = "ED25519"
#1 = "ED448"
#2 = "ECDSA_SECP256R1_SHA256" <-- matches APPROXIMATELY with "signing" key
#3 = "ECDSA_SECP384R1_SHA384"
#4 = "ECDSA_SECP521R1_SHA512"
#5 = "RSA_PSS_RSAE_SHA256"    <-- matches PERFECTLY with "tls" key
#6 = "RSA_PSS_RSAE_SHA384"
#7 = "RSA_PSS_RSAE_SHA512"

The choosePossession() algorithm will try to create a possession for each of them in this sequence. #0 and #1 have no matching keys, so 509Authentication.valueOf(ss) will be null for them and so they will be skipped. #2 does have an approximately matching key though: the flow will go to X509Authentication.X509PossessionGenerator.createClientPossession(ClientHandshakeContext, String), then X509KeyManagerImpl.chooseClientAlias(String[], Principal[], Socket) and then X509KeyManagerImpl.chooseAlias(List<KeyType>, Principal[], CheckType, AlgorithmConstraints, List<SNIServerName>, String)

chooseAlias() will call List<EntryStatus> results = getAliases(...) and return the best available match from its results.

getAliases() loops over each keystore entry and checks how closely it matches what we're looking for. For #2 above, it discovers that the "signing" entry is a suitable EC key and eventually returns it with CheckResult.EXTENSION_MISMATCH, because it indeed does not have a compatible extended key usage.

However, the caller chooseAlias() then concludes that that's good enough. The "signing" key is therefore selected and used in the client authentication certificate. S1 sees that its Extended Key Usage is invalid for TLS client authentication and throws an exception.

The root cause is twofold:
1) chooseAlias() makes a decision based on the current SignatureScheme provided to it (from one of the peerRequestedCertSignSchemes) and discards the closeness information of the match in doing so (only the alias is returned)
2) Any alias return by chooseAlias() is immediately accepted as the final answer by choosePossession(), which then stops the search process, so a potentially even better match further down the line will not be considered.

---------- BEGIN SOURCE ----------
Whether this problem occurs or not depends on the relative order of the peerRequestedCertSignSchemes entries and the key type of the unsuitable keystore entry. In this example, if entries #2 and #5 were to be swapped, the RSA_PSS_RSAE_SHA256 SignatureScheme would be tried as #2, chooseAlias() would find a match with the "tls" keystore entry and return it to choosePossession(), which accepts it and produces the correct certificate. For this problem to occur there must also be multiple keystore entries, with different SignatureSchemes and the SignatureScheme matching the unsuitable entry must be tried before the correct one.

A quick fix would be to add an extra test in chooseAlias() at the very end, where it handles imperfect matches: in case of EXTENSION_MISMATCH, return null instead, causing another scheme to be tried that may be better. This would not change any public interfaces and a certificate with wrong extended key usage should be rejected anyway. Extended key usage is optional, but if it is present it must be correct.

A more elaborate solution would be to try all schemes with all keys. As soon as there's a perfect match, use it; otherwise keep going and gather everything, then pick the best one. However, X509KeyManager is a public interface, so modifying it to return X509KeyManagerImpl.EntryStatus instead of String would break compatibility.
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
Workarounds:
* keep only one entry in the keystore used for TLS, keep the other ones in a dedicated keystore
* for the other certificates, ensure that they are not trusted by the peer, so that getAliases() rejects them for TLS purposes



Comments
As the attached S2 server log showing, "Extended key usage does not permit use for TLS client authentication" is not found when running with JDK 18ea+24.
10-12-2021

[~tongwan] Have you tried the latest JDK 18? This looks related to JDK-8262186.
10-12-2021

The observations on Windows 10: JDK 11: Failed, the bottom of the log of S2 shows "Extended key usage does not permit use for TLS client authentication" JDK 16: Failed. JDK 18ea+1: Failed.
29-07-2021

This test case is set up to provoke the error when you call /start. Sometimes it says javax.ws.rs.ProcessingException: javax.net.ssl.SSLException: SSLException invoking https://localhost:9443/TLS_issue_demo/time: readHandshakeRecord and sometimes javax.ws.rs.ProcessingException: javax.net.ssl.SSLException: SSLException invoking https://localhost:9443/TLS_issue_demo/time: An established connection was aborted by the software in your host machine But the interesting part is in the console logs (which are also saved to TLS_issue_demo/S1/target/liberty/wlp/usr/servers/defaultServer/logs/messages.log and TLS_issue_demo/S2/target/liberty/wlp/usr/servers/defaultServer/logs/messages.log). The bottom of the log of S2 shows "Extended key usage does not permit use for TLS client authentication": [INFO] [ERROR ] CWPKI0022E: SSL HANDSHAKE FAILURE: A signer with SubjectDN CN=signing was sent from the target host. The signer might need to be added to local trust store C:/Users/(...)/TLS_issue_demo/S2/target/liberty/wlp/usr/servers/defaultServer/resources/security/TrustStore.p12, located in SSL configuration alias defaultSSLSettings. The extended error message from the SSL handshake exception is: Extended key usage does not permit use for TLS client authentication [INFO] [err] javax.net.ssl|ERROR|35|Default Executor-thread-15|2021-07-23 14:31:49.288 CEST|TransportContext.java:361|Fatal (CERTIFICATE_UNKNOWN): Extended key usage does not permit use for TLS client authentication ( [INFO] "throwable" : { [INFO] sun.security.validator.ValidatorException: Extended key usage does not permit use for TLS client authentication If you scroll up a bit in that log, you can see that S2 is being given the "signing" key for the client certificate instead of the "tls" key we wanted: [7/23/21, 14:31:49:286 CEST] 00000035 SystemErr R javax.net.ssl|DEBUG|35|Default Executor-thread-15|2021-07-23 14:31:49.286 CEST|CertificateMessage.java:1178|Consuming client Certificate handshake message ( "Certificate": { "certificate_request_context": "", "certificate_list": [ { "certificate" : { "version" : "v3", "serial number" : "60 F9 49 4D", "signature algorithm": "SHA256withRSA", "issuer" : "CN=trusted_root", "not before" : "2021-07-22 12:32:45.000 CEST", "not after" : "2022-07-22 12:32:45.000 CEST", "subject" : "CN=signing", The log of S1 contains the corresponding "Produced client certificate" part: [7/23/21, 14:31:48:934 CEST] 00000062 SystemErr R javax.net.ssl|DEBUG|62|Default Executor-thread-48|2021-07-23 14:31:48.934 CEST|CertificateMessage.java:1140|Produced client Certificate message ( "Certificate": { "certificate_request_context": "", "certificate_list": [ { "certificate" : { "version" : "v3", "serial number" : "60 F9 49 4D", "signature algorithm": "SHA256withRSA", "issuer" : "CN=trusted_root", "not before" : "2021-07-22 12:32:45.000 CEST", "not after" : "2022-07-22 12:32:45.000 CEST", "subject" : "CN=signing", The handshake failure is caused by S1 selecting the "signing" entry in its keystore instead of the "tls" entry.
26-07-2021

Requested a simple reproducer from the submitter.
21-07-2021