skip to Main Content

TL;DR: After installing my CA in a Docker container from image eclipse-temurin:8-jre-alpine I still get javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure when accessing an URL with a certificate signed by the CA.

I’m currently trying to upgrade one of my applications from base image openjdk:8-jre-alpine (which was last updated three years ago and still runs Java 1.8.0_212) to eclipse-temurin:8-jre-alpine (with Java 1.8.0_332).

I’m using self-signed certificates for some other applications that this application communicates with. So in the current setup I copy my CA-certificate to /usr/local/share/ca-certificates and call update-ca-certificates.

In eclipse-temurin:8-jre-alpine I have to do some more steps: The package ca-certificates is not installed by default. Also the package java-cacerts is not installed anymore (which makes sense when ca-certificates is not, of course). So in my Dockerfile I install both and also link the cacerts file, that is created by java-cacerts, to the Java home directory:

apk add -U ca-certificates java-cacerts && ln -sf /etc/ssl/certs/java/cacerts $JAVA_HOME/lib/security/

Afterwards I copied my CA certificate to /usr/local/share/ca-certificates/ and called update-ca-certificates. When I look at /etc/ssl/certs/ I see the link to my certificate in there. And the file /etc/ssl/certs/java/cacerts is also updated (at least the modification date changes).

Accessing any application with a certificate signed by the CA works with wget now. But still fails from a Java application.

If I do the exact same thing with the image eclipse-temurin:11-jre-alpine it works in Java as well.

Any help is much appreciated!

How I Tested

  1. start a container from image eclipse-temurin:8-jre-alpine
docker run --rm -it --name temurin_8alpine_test --entrypoint /bin/sh eclipse-temurin:8-jre-alpine
  1. install ca-certificates and java-cacerts in container
apk add -U ca-certificates java-cacerts && ln -sf /etc/ssl/certs/java/cacerts $JAVA_HOME/lib/security/
  1. copy certificate to running container
docker cp /path/to/my/CA-certificate.pem temurin_8alpine_test:/usr/local/share/ca-certificates/
  1. update certificate stores in container
update-ca-certificates

this warns ca-certificates.crt does not contain exactly one certificate or CRL: skipping, which is ok (https://github.com/gliderlabs/docker-alpine/issues/30)

  1. test with wget
/ # wget https://my.internal.app
Connecting to my.internal.app (1.2.3.4:443)
saving to 'index.html'
index.html           100% |**********************************************************|  1087  0:00:00 ETA
'index.html' saved
  1. download, compile and upload SSLPoke
wget -q https://confluence.atlassian.com/download/attachments/117455/SSLPoke.java -O /tmp/SSLPoke.java
# make sure to use Java 8 compiler or use --target 8
javac /tmp/SSLPoke.java -d /tmp/
docker cp /tmp/SSLPoke.class temurin_8alpine_test:/

SSLPoke from https://matthewdavis111.com/java/poke-ssl-test-java-certs/

  1. test with SSLPoke in container
/ # java SSLPoke google.com 443
Successfully connected
/ # java SSLPoke my.internal.app 443
javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
        at sun.security.ssl.Alert.createSSLException(Alert.java:131)
        at sun.security.ssl.Alert.createSSLException(Alert.java:117)
        at sun.security.ssl.TransportContext.fatal(TransportContext.java:311)
        at sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
        at sun.security.ssl.TransportContext.dispatch(TransportContext.java:185)
        at sun.security.ssl.SSLTransport.decode(SSLTransport.java:152)
        at sun.security.ssl.SSLSocketImpl.decode(SSLSocketImpl.java:1397)
        at sun.security.ssl.SSLSocketImpl.readHandshakeRecord(SSLSocketImpl.java:1305)
        at sun.security.ssl.SSLSocketImpl.startHandshake(SSLSocketImpl.java:440)
        at sun.security.ssl.SSLSocketImpl.ensureNegotiated(SSLSocketImpl.java:818)
        at sun.security.ssl.SSLSocketImpl.access$200(SSLSocketImpl.java:73)
        at sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1180)
        at sun.security.ssl.SSLSocketImpl$AppOutputStream.write(SSLSocketImpl.java:1152)
        at SSLPoke.main(SSLPoke.java:23)
/ #

2

Answers


  1. Chosen as BEST ANSWER

    After some deeper digging and some help by @JockX I found the actual issue: Java8 Temurin does not support any of the SSL cipher suites my internal app requires.

    As the same goes for @JockX's example https://jockx.net I'll use that site for the example:

    All ciphers supported by the site use eliptic curves:

    user@nb [~]
    -> % nmap --script ssl-enum-ciphers -p 443 jockx.net
    Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-16 17:32 CEST
    Nmap scan report for jockx.net (172.67.206.34)
    Host is up (0.022s latency).
    Other addresses for jockx.net (not scanned): 104.21.37.83 2606:4700:3034::ac43:ce22 2606:4700:3034::6815:2553
    
    PORT    STATE SERVICE
    443/tcp open  https
    | ssl-enum-ciphers:
    |   TLSv1.2:
    |     ciphers:
    |       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256-draft (ecdh_x25519) - A
    |     compressors:
    |       NULL
    |     cipher preference: client
    |_  least strength: A
    
    Nmap done: 1 IP address (1 host up) scanned in 3.09 seconds
    user@nb [~]
    -> %
    

    When you look at the supported cipher suites of the Java installation in a container running with image eclipse-temurin:8-jre-alpine, then you get the following:

    / # java SSLCipherSuites
    Supported Cipheruites:
    * TLS_AES_256_GCM_SHA384
    * TLS_AES_128_GCM_SHA256
    * TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
    * TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
    * TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
    * TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
    * TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
    * TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
    * TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
    * TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
    * TLS_DHE_RSA_WITH_AES_256_CBC_SHA
    * TLS_DHE_DSS_WITH_AES_256_CBC_SHA
    * TLS_DHE_RSA_WITH_AES_128_CBC_SHA
    * TLS_DHE_DSS_WITH_AES_128_CBC_SHA
    * TLS_RSA_WITH_AES_256_GCM_SHA384
    * TLS_RSA_WITH_AES_128_GCM_SHA256
    * TLS_RSA_WITH_AES_256_CBC_SHA256
    * TLS_RSA_WITH_AES_128_CBC_SHA256
    * TLS_RSA_WITH_AES_256_CBC_SHA
    * TLS_RSA_WITH_AES_128_CBC_SHA
    * TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    / #
    

    I compiled the following short script with Java 8 and copied it to the container:

    import javax.net.ssl.SSLSocketFactory;
    
    public class SSLCipherSuites {
        public static void main(String[] args) {
            SSLSocketFactory sslsocketfactory = (SSLSocketFactory) SSLSocketFactory.getDefault();
            String[] cipherSuites = sslsocketfactory.getSupportedCipherSuites();
            System.out.println("Supported CipherSuites:");
            for (String cipherSuite : cipherSuites) {
                    System.out.println("* " + cipherSuite);
            }
        }
    }
    

    If I do the same in a container running with image eclipse-temurin:11-jre-alpine I get the following:

    / # java SSLCipherSuites
    Supported CipherSuites:
    * TLS_AES_256_GCM_SHA384
    * TLS_AES_128_GCM_SHA256
    * TLS_CHACHA20_POLY1305_SHA256
    * TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384
    * TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256
    * TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
    * TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384
    * TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256
    * TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
    * TLS_DHE_RSA_WITH_AES_256_GCM_SHA384
    * TLS_DHE_RSA_WITH_CHACHA20_POLY1305_SHA256
    * TLS_DHE_DSS_WITH_AES_256_GCM_SHA384
    * TLS_DHE_RSA_WITH_AES_128_GCM_SHA256
    * TLS_DHE_DSS_WITH_AES_128_GCM_SHA256
    * TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384
    * TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384
    * TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256
    * TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256
    * TLS_DHE_RSA_WITH_AES_256_CBC_SHA256
    * TLS_DHE_DSS_WITH_AES_256_CBC_SHA256
    * TLS_DHE_RSA_WITH_AES_128_CBC_SHA256
    * TLS_DHE_DSS_WITH_AES_128_CBC_SHA256
    * TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384
    * TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384
    * TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256
    * TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256
    * TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384
    * TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384
    * TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256
    * TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256
    * TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA
    * TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA
    * TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA
    * TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA
    * TLS_DHE_RSA_WITH_AES_256_CBC_SHA
    * TLS_DHE_DSS_WITH_AES_256_CBC_SHA
    * TLS_DHE_RSA_WITH_AES_128_CBC_SHA
    * TLS_DHE_DSS_WITH_AES_128_CBC_SHA
    * TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA
    * TLS_ECDH_RSA_WITH_AES_256_CBC_SHA
    * TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA
    * TLS_ECDH_RSA_WITH_AES_128_CBC_SHA
    * TLS_RSA_WITH_AES_256_GCM_SHA384
    * TLS_RSA_WITH_AES_128_GCM_SHA256
    * TLS_RSA_WITH_AES_256_CBC_SHA256
    * TLS_RSA_WITH_AES_128_CBC_SHA256
    * TLS_RSA_WITH_AES_256_CBC_SHA
    * TLS_RSA_WITH_AES_128_CBC_SHA
    * TLS_EMPTY_RENEGOTIATION_INFO_SCSV
    / #
    

    https://www.google.com supports a lot more ciphers and hence accessing it works from eclipse-temurin:8-jre-alpine

    user@nb [~]
    -> % nmap --script ssl-enum-ciphers -p 443 google.com
    Starting Nmap 7.92 ( https://nmap.org ) at 2022-05-16 17:47 CEST
    Nmap scan report for google.com (216.58.212.174)
    Host is up (0.024s latency).
    Other addresses for google.com (not scanned): 2a00:1450:4001:802::200e
    rDNS record for 216.58.212.174: ams15s22-in-f174.1e100.net
    
    PORT    STATE SERVICE
    443/tcp open  https
    | ssl-enum-ciphers:
    |   TLSv1.0:
    |     ciphers:
    |       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
    |     compressors:
    |       NULL
    |     cipher preference: server
    |     warnings:
    |       64-bit block cipher 3DES vulnerable to SWEET32 attack
    |   TLSv1.1:
    |     ciphers:
    |       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
    |     compressors:
    |       NULL
    |     cipher preference: server
    |     warnings:
    |       64-bit block cipher 3DES vulnerable to SWEET32 attack
    |   TLSv1.2:
    |     ciphers:
    |       TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
    |       TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
    |       TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
    |       TLS_RSA_WITH_3DES_EDE_CBC_SHA (rsa 2048) - C
    |       TLS_RSA_WITH_AES_128_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_AES_128_GCM_SHA256 (rsa 2048) - A
    |       TLS_RSA_WITH_AES_256_CBC_SHA (rsa 2048) - A
    |       TLS_RSA_WITH_AES_256_GCM_SHA384 (rsa 2048) - A
    |     compressors:
    |       NULL
    |     cipher preference: client
    |     warnings:
    |       64-bit block cipher 3DES vulnerable to SWEET32 attack
    |   TLSv1.3:
    |     ciphers:
    |       TLS_AKE_WITH_AES_128_GCM_SHA256 (ecdh_x25519) - A
    |       TLS_AKE_WITH_AES_256_GCM_SHA384 (ecdh_x25519) - A
    |       TLS_AKE_WITH_CHACHA20_POLY1305_SHA256 (ecdh_x25519) - A
    |     cipher preference: client
    |_  least strength: C
    
    Nmap done: 1 IP address (1 host up) scanned in 1.96 seconds
    user@nb [~]
    -> %
    

    Thanks for your support!


  2. Forget about ca-cert related packages, and directly update cacerts file of JRE powering the container. Use keytool, which is part of the image you are using:

    /opt/java/openjdk/bin/keytool -import -trustcacerts -keystore /opt/java/openjdk/lib/security/cacerts -storepass changeit -noprompt -alias mycert -file /my-ca-cert.crt
    

    Depending on the format of the ca-certificate file you are adding, you may need to convert it to the format supported by keytool. For example a PEM file with the lines like these:

    -----BEGIN CERTIFICATE-----
    
    -----END CERTIFICATE-----
    

    may be converted with openssl:

    openssl x509 -in my-cert.pem -text -out my-ca-cert.crt
    

    If to be done inside the container, the requrired package is openssl:

    apk add openssl
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search