0

I am using a Java Websocket Server (TooTallNate). A Javascript App is connecting securely via a LetsEncrypt certificate. It is renewed automatically via certbot and is serving an Apache on the same machine too. On all tested browsers everything is working fine, for both https and wss.

I wanted to submit my app as a packaged FireTV app. I tested it in the "Web App Tester" app. As soon as the JS tries to connect to the WSS, it raises an SSL error, which reads in adb-logcat

I/X509util: Failed to validate the certificate chain, error: java.security.cert.CertPathValidatorException: Trust anchor for certification path not found.

but sometimes just

E/chromium(13208): [ERROR:ssl_client_socket_impl.cc(947)] handshake failed; returned -1, SSL error code 1, net_error -202

The relevant Java code filling the SSLContext from TooTallNate is:

private static SSLContext getContext() {
    SSLContext context;
    String password = "CHANGEIT";
    String pathname = "pem";
    try {
        context = SSLContext.getInstance("TLS");

        byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
        byte[] keyBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "privkey.pem")),"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");

        X509Certificate cert = generateCertificateFromDER(certBytes);
        RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);

        KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(null);
        keystore.setCertificateEntry("cert-alias", cert);
        keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert});

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keystore, password.toCharArray());

        KeyManager[] km = kmf.getKeyManagers();

        context.init(km, null, null);
    }
    catch (Exception e) {
        context = null;
    }
    return context;
}
Max Power
  • 169
  • 10

1 Answers1

0

It took some reading to get it to work. There are few information on this special problem, so I decided to answer myself:

It was truly a problem of the missing chain in the SSLContext. Couldn't believe that, because it worked on every other chromium based browser. In my LetsEncrypt folder I have four files: privkey.pem (not at thing), cert.pem, chain.pem and fullchain.pem, where fullchain.pem is just the concatenation of chain.pem and cert.pem. cert.pem is our own certificate and chain.pem is the Digital Signature Trust DST Root CA X3 certificate which you can see yourself using:

cat chain.pem | openssl x509 -text

I compared the certificates served over https and wss using:

openssl s_client -connect myurl:443 | openssl x509 -text

and

openssl s_client -connect myurl:8080 | openssl x509 -text

The latter showed two error at the beginning of the output

verify error:num=20:unable to get local issuer certificate

while the https response from apache showed

depth=2 O = Digital Signature Trust Co., CN = DST Root CA X3
verify return:1
depth=1 C = US, O = Let's Encrypt, CN = Let's Encrypt Authority X3
verify return:1
depth=0 CN = myurl
verify return:1

Finally the working solution is a lot easier than my solutions I had meanwhile. Its not necessary to download any further chain certificates. Just load the chain.pem you already have in your LetsEncrypt folder like the cert.pem and add both as the chain to the keystore in the setKeyEntry function:

private static SSLContext getContext() {
    SSLContext context;
    String password = "CHANGEIT";
    String pathname = "pem";
    try {
        context = SSLContext.getInstance("TLS");

        byte[] certBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "cert.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
        byte[] chainBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "chain.pem")),"-----BEGIN CERTIFICATE-----", "-----END CERTIFICATE-----");
        byte[] keyBytes = parseDERFromPEM(getBytes(new File(pathname + File.separator + "privkey.pem")),"-----BEGIN PRIVATE KEY-----", "-----END PRIVATE KEY-----");

        X509Certificate cert = generateCertificateFromDER(certBytes);
        X509Certificate chain = generateCertificateFromDER(chainBytes);
        RSAPrivateKey key = generatePrivateKeyFromDER(keyBytes);

        KeyStore keystore = KeyStore.getInstance("JKS");
        keystore.load(null);
        keystore.setCertificateEntry("cert-alias", cert);
        keystore.setKeyEntry("key-alias", key, password.toCharArray(), new Certificate[]{cert, chain});

        KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
        kmf.init(keystore, password.toCharArray());

        KeyManager[] km = kmf.getKeyManagers();

        context.init(km, null, null);
    }
    catch (Exception e) {
        context = null;
    }
    return context;
}
Dharman
  • 30,962
  • 25
  • 85
  • 135
Max Power
  • 169
  • 10