Java

Java

Java

Topics on Semeru (Java) on IBM Z

 View Only
  • 1.  Java 17.0.19.10: TLS Alert Changed from bad_certificate to handshake_failure

    Posted 07/07/26 08:57 AM

    Hello,

    We have a Spring Boot 3.5.6 application configured with SSL/TLS on the embedded Tomcat port:
    server:
      address: 0.0.0.0
      port: 9091
      ssl:
        enabled: true
        key-store: key.jks
        key-store-type: JKS
        key-store-password: xxxx
        key-alias: server
        trust-store: server.truststore
        trust-store-type: JKS
        trust-store-password: xxxx
        enabled-protocols: TLSv1.2
        protocol: TLSv1.2
        client-auth: need

    We have a negative test case where curl attempts to connect using an invalid client certificate.

    When the application is running on Java 17.0.18.8, curl reports:

    ssl/tls alert bad certificate

    which is the behavior we expect.

    However, when the application is running on Java 17.0.19.10, curl reports:

    SSL routines::ssl/tls alert handshake failure
    I enabled SSL handshake debugging on the server side using:
    -Djavax.net.debug=ssl:handshake

    but I could not identify any significant differences in the logs.

    Is this change in behavior expected between these Java versions? If so, could you help me understand what changed and why an invalid client certificate now results in a generic handshake failure alert instead of a bad certificate alert?

    Here are the curl logs:

    $ curl -V
    curl 8.17.0 (i370-ibm-openedition) libcurl/8.17.0 OpenSSL/3.5.4 zlib/1.3.1 libssh2/1.11.0 nghttp2/1.61.0 mit-krb5/1.21.3
    Release-Date: 2025-11-05
    Protocols: dict file ftp ftps gopher gophers http https imap imaps ipfs ipns mqtt pop3 pop3s rtsp scp sftp smb smbs smtp smtps telnet tftp ws wss
    Features: alt-svc GSS-API HSTS HTTP2 HTTPS-proxy IPv6 Kerberos Largefile libz NTLM SPNEGO SSL TLS-SRP UnixSockets

    * timeout on name lookup is not supported                                                   
    * Host xxx`:9091 was resolved.                                           
    * IPv6: 
    * IPv4: 
    *   Trying xxx:9091...                                                
    * ALPN: curl offers h2,http/1.1                                                             
    } [5 bytes data]                                                                            
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):                                           
    } [1576 bytes data]                                                                         
    * SSL Trust Anchors:                                                                        
    *   CAfile: ca.crt                                                   
    { [5 bytes data]                                                                            
    * TLSv1.3 (IN), TLS handshake, Server hello (2):                                            
    { [85 bytes data]                                                                           
    * TLSv1.2 (IN), TLS handshake, Certificate (11):                                            
    { [857 bytes data]                                                                          
    * TLSv1.2 (IN), TLS handshake, Server key exchange (12):                                    
    { [300 bytes data]                                                                          
    * TLSv1.2 (IN), TLS handshake, Request CERT (13):                                           
    { [138 bytes data]                                                                          
    * TLSv1.2 (IN), TLS handshake, Server finished (14):                                        
    { [4 bytes data]                                                                            
    * TLSv1.2 (OUT), TLS handshake, Certificate (11):                                           
    } [7 bytes data]                                                                            
    * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):                                   
    } [37 bytes data]                                                                           
    * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):                                 
    } [1 bytes data]                                                                            
    * TLSv1.2 (OUT), TLS handshake, Finished (20):                                              
    } [16 bytes data]                                                                           
    * TLSv1.2 (IN), TLS alert, bad certificate (554):                                           
    { [2 bytes data]                                                                            
    * TLS connect error: error:0A000412:SSL routines::ssl/tls alert bad certificate             
    * closing connection #0                                                                     
    curl: (35) TLS connect error: error:0A000412:SSL routines::ssl/tls alert bad certificate    

    * timeout on name lookup is not supported                                                    
    * Host xxx:9091 was resolved.                                            
    * IPv6: 
    * IPv4: 
    *   Trying xxx:9091...                                                 
    * ALPN: curl offers h2,http/1.1                                                              
    } [5 bytes data]                                                                             
    * TLSv1.3 (OUT), TLS handshake, Client hello (1):                                            
    } [1576 bytes data]                                                                          
    * SSL Trust Anchors:                                                                         
    *   CAfile: ca.crt                                                    
    { [5 bytes data]                                                                             
    * TLSv1.3 (IN), TLS handshake, Server hello (2):                                             
    { [85 bytes data]                                                                            
    * TLSv1.2 (IN), TLS handshake, Certificate (11):                                             
    { [857 bytes data]                                                                           
    * TLSv1.2 (IN), TLS handshake, Server key exchange (12):                                     
    { [300 bytes data]                                                                           
    * TLSv1.2 (IN), TLS handshake, Request CERT (13):                                            
    { [138 bytes data]                                                                           
    * TLSv1.2 (IN), TLS handshake, Server finished (14):                                         
    { [4 bytes data]                                                                             
    * TLSv1.2 (OUT), TLS handshake, Certificate (11):                                            
    } [7 bytes data]                                                                             
    * TLSv1.2 (OUT), TLS handshake, Client key exchange (16):                                    
    } [37 bytes data]                                                                            
    * TLSv1.2 (OUT), TLS change cipher, Change cipher spec (1):                                  
    } [1 bytes data]                                                                             
    * TLSv1.2 (OUT), TLS handshake, Finished (20):                                               
    } [16 bytes data]                                                                            
    * TLSv1.2 (IN), TLS alert, handshake failure (552):                                          
    { [2 bytes data]                                                                             
    * TLS connect error: error:0A000410:SSL routines::ssl/tls alert handshake failure            
    * closing connection #0                                                                      
    curl: (35) TLS connect error: error:0A000410:SSL routines::ssl/tls alert handshake failure   

    Thank you.



    ------------------------------
    Sergey Korzhevsky
    Senior Software Engineer in Test
    Rocket Software
    ------------------------------


  • 2.  RE: Java 17.0.19.10: TLS Alert Changed from bad_certificate to handshake_failure

    Posted 07/09/26 10:51 AM

    This is very likely a behavior change, not a bug - Java CPU releases routinely tighten the disabled/restricted algorithm lists in java.security (jdk.tls.disabledAlgorithms, jdk.certpath.disabledAlgorithms).

    In TLS 1.2 mutual auth, a bad client cert can get rejected in two different places:• Trust/chain validation (untrusted CA, expired, etc.) → bad_certificate• Signature algorithm negotiation, if the cert's key type/sig algorithm isn't in the server's CertificateRequest supported_signature_algorithms list at all → handshake_failure

    My guess: His negative-test cert uses an algorithm (SHA-1, small RSA key, older curve) that was still negotiable in 17.0.18.8 but got disabled by default in 17.0.19.10 - so it now fails earlier in the handshake (negotiation) instead of later (actual cert validation).

    To confirm:1. Check the test cert's sig algorithm/key: openssl x509 -in badcert.pem -text -noout2. Diff java.security between the two JDKs: grep -A5 "jdk.tls.disabledAlgorithms\|jdk.certpath.disabledAlgorithms" $JAVA_HOME/conf/security/java.security3. Re-run with -Djavax.net.debug=ssl verbose and compare the CertificateRequest's signature_algorithms list between versions4. Check the exception right before the alert in the debug log - CertificateException = real validation; NoSuchAlgorithmException/"no supported signature algorithm" = negotiation failure

    If confirmed, this isn't something to "fix" - either update the negative-test cert to use a still-supported algorithm, or explicitly re-enable the specific algorithm via jdk.tls.disabledAlgorithms override if bad_certificate semantics are required for the test. Let me know if having comments.



    ------------------------------
    ANGUO LI
    ------------------------------



  • 3.  RE: Java 17.0.19.10: TLS Alert Changed from bad_certificate to handshake_failure

    Posted 07/10/26 05:15 AM

    Hello Anguo,

       Thank you for the response, i gathered details, as you suggested.

    1. We are trying to connect with only CA cert, it has "Signature Algorithm: sha256WithRSAEncryption"
    2. Diff is the only in "jdk.security.caDistrustPolicies" - here "CHUNGHWA_TLS" is added. Also, i started Java 17.0.19 with the security from 17.0.18 and this didn't help
    3. Debug shows the following differences:

      17.0.18:

      javax.net.ssl|DEBUG|62|https-jsse-nio-0.0.0.0-9091-exec-2|2026-07-10 04:25:46.039 EDT|SSLEngineOutputRecord.java:530|WRITE: TLSv1.2 handshake, length = 1384
      javax.net.ssl|DEBUG|52|https-jsse-nio-0.0.0.0-9091-exec-1|2026-07-10 04:25:46.041 EDT|SSLEngineInputRecord.java:213|READ: TLSv1.2 handshake, length = 7
      javax.net.ssl|DEBUG|52|https-jsse-nio-0.0.0.0-9091-exec-1|2026-07-10 04:25:46.041 EDT|CertificateMessage.java:372|Consuming client Certificate handshake message (
      "Certificates": <empty list>
      )
      javax.net.ssl|ERROR|52|https-jsse-nio-0.0.0.0-9091-exec-1|2026-07-10 04:25:46.043 EDT|TransportContext.java:375|Fatal (BAD_CERTIFICATE): Empty client certificate chain (
      "throwable" : {
        javax.net.ssl.SSLHandshakeException: Empty client certificate chain
            at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
            at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:317)
            at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:390)
            at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:375)
            at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
            at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:481)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1277)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1264)
            at java.base/java.security.AccessController.doPrivileged(AccessController.java:793)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1209)
            at org.apache.tomcat.util.net.SecureNioChannel.tasks(SecureNioChannel.java:433)
            at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:504)
            at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:217)
            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1755)
            at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
            at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:946)
            at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:480)
            at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:57)
            at java.base/java.lang.Thread.run(Thread.java:854)}

      17.0.19:

      javax.net.ssl|DEBUG|52|https-jsse-nio-0.0.0.0-9091-exec-1|2026-07-10 04:09:29.159 EDT|SSLEngineOutputRecord.java:530|WRITE: TLSv1.2 handshake, length = 1384
      javax.net.ssl|DEBUG|62|https-jsse-nio-0.0.0.0-9091-exec-2|2026-07-10 04:09:29.161 EDT|SSLEngineInputRecord.java:213|READ: TLSv1.2 handshake, length = 7
      javax.net.ssl|DEBUG|62|https-jsse-nio-0.0.0.0-9091-exec-2|2026-07-10 04:09:29.161 EDT|CertificateMessage.java:372|Consuming client Certificate handshake message (
      "Certificates": <empty list>
      )
      javax.net.ssl|ERROR|62|https-jsse-nio-0.0.0.0-9091-exec-2|2026-07-10 04:09:29.162 EDT|TransportContext.java:375|Fatal (HANDSHAKE_FAILURE): Empty client certificate chain (
      "throwable" : {
        javax.net.ssl.SSLHandshakeException: (handshake_failure) Empty client certificate chain
            at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:131)
            at java.base/sun.security.ssl.Alert.createSSLException(Alert.java:117)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:370)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:326)
            at java.base/sun.security.ssl.TransportContext.fatal(TransportContext.java:317)
            at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.onCertificate(CertificateMessage.java:390)
            at java.base/sun.security.ssl.CertificateMessage$T12CertificateConsumer.consume(CertificateMessage.java:375)
            at java.base/sun.security.ssl.SSLHandshake.consume(SSLHandshake.java:396)
            at java.base/sun.security.ssl.HandshakeContext.dispatch(HandshakeContext.java:481)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1278)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask$DelegatedAction.run(SSLEngineImpl.java:1265)
            at java.base/java.security.AccessController.doPrivileged(AccessController.java:793)
            at java.base/sun.security.ssl.SSLEngineImpl$DelegatedTask.run(SSLEngineImpl.java:1210)
            at org.apache.tomcat.util.net.SecureNioChannel.tasks(SecureNioChannel.java:433)
            at org.apache.tomcat.util.net.SecureNioChannel.handshakeUnwrap(SecureNioChannel.java:504)
            at org.apache.tomcat.util.net.SecureNioChannel.handshake(SecureNioChannel.java:217)
            at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1755)
            at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
            at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:946)
            at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:480)
            at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:57)
            at java.base/java.lang.Thread.run(Thread.java:854)}


      Basically, exception message "(BAD_CERTIFICATE): Empty client certificate chain" changed to "(HANDSHAKE_FAILURE): Empty client certificate chain"


    ------------------------------
    Sergey Korzhevsky
    Senior Software Engineer in Test
    Rocket Software
    ------------------------------



  • 4.  RE: Java 17.0.19.10: TLS Alert Changed from bad_certificate to handshake_failure

    Posted 07/16/26 02:28 PM

    Hi Sergey, I deeply looked into the code from  Oracle's open source, this is exactly the data we needed, and your testing pinned it down. Swapping in the 17.0.18 java.security and seeing no change conclusively rules out a configuration/algorithm-list difference, and your certificate is SHA-256 (sha256WithRSAEncryption), so it was never an algorithm-disablement issue. This is an intentional code behavior change in JSSE - not a bug, and not something in java.security.

    What changed. The alert sent when a client presents an empty certificate chain under required mutual authentication was changed between 17.0.18 and 17.0.19:

    • 17.0.18: Fatal (BAD_CERTIFICATE): Empty client certificate chain
    • 17.0.19: Fatal (HANDSHAKE_FAILURE): Empty client certificate chain

    Everything else is identical - same trigger (empty client Certificate message), same code path, same exception text. Only the alert code (42 → 40) and its label changed.

    Where, in the JSSE source. This is in sun.security.ssl.CertificateMessage, in T12CertificateConsumer.onCertificate - the exact class and line in your stack trace, CertificateMessage.java:390:

    if (encodedCerts == null || encodedCerts.isEmpty()) {
        ...
        if (shc.sslConfig.clientAuthType != ClientAuthType.CLIENT_AUTH_REQUESTED) {
            throw shc.conContext.fatal(Alert.BAD_CERTIFICATE,   // 17.0.18
                "Empty client certificate chain");              // → Alert.HANDSHAKE_FAILURE in 17.0.19
        } else {
            return;   // optional client auth → continue
        }
    }

    The change is a single alert token on that line: Alert.BAD_CERTIFICATE  Alert.HANDSHAKE_FAILURE.

    Why. This aligns JSSE with RFC 5246 §7.4.6, which states that if the client sends no certificate and the server requires client authentication, the server "[responds] with a fatal handshake_failure alert." The bad_certificate alert is defined for a certificate that is corrupt or has an invalid signature - not for an absent certificate - so handshake_failure is the correct alert here. The condition in the code maps directly to the RFC: CLIENT_AUTH_REQUIRED → fatal alert; optional (REQUESTED) → continue without client auth.

    No security regression. In both versions the empty client certificate is rejected with a fatal alert and the handshake is aborted. Mutual TLS continues to work correctly and still refuses the connection - only the classification of the alert changed.  So this behavior change is expected and should be adopted. 



    ------------------------------
    ANGUO LI
    ------------------------------



  • 5.  RE: Java 17.0.19.10: TLS Alert Changed from bad_certificate to handshake_failure

    Posted 07/17/26 04:44 AM

    Hello Anguo,

       Thank you very much for taking the time to research this and explain it to me!



    ------------------------------
    Sergey Korzhevsky
    Senior Software Engineer in Test
    Rocket Software
    ------------------------------