Java connection to HTTPS with self signed cert

Hi all,

I’ve started working on a new Binding for Lennox S30 smart thermostat, device itself has a build in web responder which provides and accepts json over HTTPS on standard 443. This works well in my POC with Python and PowerShell but I cannot make it work with Java / Jetty HTTP client. At this point I’m not even using OpenHab Client Factory but just trying to connect, authenticate and create classes structure.

Basic code is very simple:

SslContextFactory.Client sslContextFactory = new SslContextFactory.Client(true);
httpClient = new HttpClient(sslContextFactory);
httpClient.start();
Request request = httpClient.newRequest("https://local_url_connect");
request.method(HttpMethod.POST);
ContentResponse response = request.send();

Initiating a request always produced the same result:

java.util.concurrent.ExecutionException: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
        at org.eclipse.jetty.client.util.FutureResponseListener.getResult(FutureResponseListener.java:118)
        at org.eclipse.jetty.client.util.FutureResponseListener.get(FutureResponseListener.java:101)
        at org.eclipse.jetty.client.HttpRequest.send(HttpRequest.java:730)
        at main.java.org.openhab.binding.icomforts30.internal.api.testjetty.<init>(testjetty.java:152)
        at main.java.org.openhab.binding.icomforts30.internal.api.start.main(start.java:6)
Caused by: javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure
        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:356)
        at java.base/sun.security.ssl.Alert$AlertConsumer.consume(Alert.java:293)
        at java.base/sun.security.ssl.TransportContext.dispatch(TransportContext.java:202)
        at java.base/sun.security.ssl.SSLTransport.decode(SSLTransport.java:171)
        at java.base/sun.security.ssl.SSLEngineImpl.decode(SSLEngineImpl.java:688)
        at java.base/sun.security.ssl.SSLEngineImpl.readRecord(SSLEngineImpl.java:643)
        at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:461)
        at java.base/sun.security.ssl.SSLEngineImpl.unwrap(SSLEngineImpl.java:440)
        at java.base/javax.net.ssl.SSLEngine.unwrap(SSLEngine.java:637)
        at org.eclipse.jetty.io.ssl.SslConnection.unwrap(SslConnection.java:429)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.fill(SslConnection.java:718)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.process(HttpReceiverOverHTTP.java:164)
        at org.eclipse.jetty.client.http.HttpReceiverOverHTTP.receive(HttpReceiverOverHTTP.java:79)
        at org.eclipse.jetty.client.http.HttpChannelOverHTTP.receive(HttpChannelOverHTTP.java:131)
        at org.eclipse.jetty.client.http.HttpConnectionOverHTTP.onFillable(HttpConnectionOverHTTP.java:172)
        at org.eclipse.jetty.io.AbstractConnection$ReadCallback.succeeded(AbstractConnection.java:311)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
        at org.eclipse.jetty.io.ssl.SslConnection$DecryptedEndPoint.onFillable(SslConnection.java:555)
        at org.eclipse.jetty.io.ssl.SslConnection.onFillable(SslConnection.java:410)
        at org.eclipse.jetty.io.ssl.SslConnection$2.succeeded(SslConnection.java:164)
        at org.eclipse.jetty.io.FillInterest.fillable(FillInterest.java:105)
        at org.eclipse.jetty.io.ChannelEndPoint$1.run(ChannelEndPoint.java:104)
        at org.eclipse.jetty.util.thread.QueuedThreadPool.runJob(QueuedThreadPool.java:883)
        at org.eclipse.jetty.util.thread.QueuedThreadPool$Runner.run(QueuedThreadPool.java:1034)
        at java.base/java.lang.Thread.run(Thread.java:834)

Suggestions from Internet are to simply use “trustAllCert = true” passed to SSLContextFactory (code above), create TrustManager which will ignore cert or load keystore with the self signed cert but result is the same. Code above works perfectly fine with valid cert (like https://www.facebook.com).

Would appreciate any suggestions from experienced Java programmers!

Did you try to put your self signed cert into the cacerts keystore of your java distribution ?
E.g.: /opt/jdk/zulu11.50.19-ca-jdk11.0.12-linux_aarch32hf/lib/security/cacerts

No, haven’t tried that. Tried to create a keystore with only that cert and that didn’t work. I can try to put it into main store just for testing but that won’t be a solution for binding, it should be self contained. Cert could be in the binding or HTTPClient should just accept it. (Note that it is not “my” cert, the cert is from Lennox box but self signed and with the wrong host.)

Adding cert to “cacerts” didn’t work either.

Hi,

Dealing with self-signed certificates is a bit tricky at the moment.

Currently most of the bindings implement a trust all TlsTrustManagerProvider for a specific hostname (e.g. avmfritz binding). That will work whenever you have a fixed hostname or you know the CN (common name) used in the certificate.

I recently proposed a PEMTrustManager implementation. It creates a TrustManager instance by downloading the PEM certificate from the given server. This is useful if you have to deal with self-signed certificates which may differ on each server or if you do not have a fixed hostname. It pins the certificate on first connection with the server (“trust on first use”) by using a trust all connection and retrieves the servers certificate chain, stores it internally and uses it for further validation.

Thank you, I’m currently looking on Bosh implementation, will look on “avmfritz”, would be great if we can have some easy solution.

Hi Christoph,

Method implemented in Fritz binding doesn’t work for me, I see my class called and CN set to “Lennox” as per cert, anyway I’ve got “javax.net.ssl.SSLHandshakeException: Received fatal alert: handshake_failure”. I see that PEMTrustManager is implemented in 3.3.0-snapshot, want to see if that will work. Can you provide some examples on how to use it with SelfSigned download cert?

Thanks,
K.

Hi Konstantin,

You can find an implementation example in my PR for the Hue binding improvements. Here is a link to the class:

Currently the usage is not that easy as I supposed it to be because we have to pass the URL (optionally port) to the TLSTrustManagerProvider. Which in most cases is configured by the user and only available after initialization if the handler. The PR contains a working solution for that which means it registeres the service during handler initialization - but I do not expect that to be the final approach as it requires some knowledge about how OSGi services are working. For your own purpose you can give it a try.

Have Fun, C

1 Like

Hi Christoph,

Thank you for examples, spent a lot of time playing with them, I think I’m one step close however I found a few issues with PEMTrustManager implementation:

  1. First thing Java is a bit picky about how PEM input is formatted and expects “\r\n” after opening tag and end tag, otherwise it just throws exception on Cert creation:

return BEGIN_CERT + “\r\n” + Base64.getEncoder().encodeToString(bytes) + “\r\n” + END_CERT;

  1. Second thing that constructor for PEMTrustManager never returns anything and instead always throw an exception, could be real one like if cert missing “\r\n” or bogus one even if cert passes creation stage. I’m not an expert here and not sure how it exactly supposed to work but I expect constructor must return a TrustManager.

I’ve reused your code in my own implementation of “TlsTrustManagerProvider” similar to what used in Hue but because of PEMTrustManager crashing it always tries to use TrustAllTrustManager which in my case just fails the connection to the device / thermostat.

Thanks,
K.

Hi Konstantin,

Sry for my late reply. I will look.into this in the next couple not days. My initial approach added those line endings but during review if my PR we decided to remove the again.

On which OS are you developing?

For the meantime you might want to copy my code / class from OHC into you local bundle and apply your fix in it. Just call it LennoxPEMTrustManager or similar and replace the original usages.

Regarding throwing exceptions in a constructor: imo this is a valid use-case.

Best,
Christoph

Thanks for responding Chris, no worries about late reply, there’s no such thing in open source, that actually gave me time to read a lot about SSL/TLS implementation in Java.

On which OS are you developing?
Windows but according to what I googled Certificate code must be surrounded by tags and end of lines. Fix worked for me fine.

Regarding throwing exceptions in a constructor
Yes, this is fine and handled in the calling function by using generic TrustManager, the problem however is that exception is thrown regardless of the result of the constructor and there’s now path were PEMTrustManager is created.

Your code is like this:

public PEMTrustManager(String pemCert) throws CertificateException {
        if (!pemCert.isBlank() && pemCert.startsWith(BEGIN_CERT)) {
            try (InputStream certInputStream = new ByteArrayInputStream(pemCert.getBytes(StandardCharsets.UTF_8))) {
                trustedCert = (X509Certificate) CertificateFactory.getInstance("X.509")
                        .generateCertificate(certInputStream);
            } catch (IOException e) {
                throw new CertificateException(e);
            }
        }
        throw new CertificateParsingException("Certificate is either empty or cannot be parsed correctly");
    }

If certificate created successfully we exit from “if” and throw exception CertificateParsingException(“Certificate is either empty or cannot be parsed correctly”) instead of creating the PEMTrustManager, this will be handled by using generic one. So basically regardless of the result one or another exception will be thrown. According to the documentation some additional steps must be taken like creating stor in memory and initialising TrustManager with that stor. I’m currently playing with the code, will post if I’m successful.

Thanks,
K.

What a pitty. Now I see my mess. Thanks for the hint and your spent time.

I submitted a fix:

2 Likes

Thank you Chris! I’ve managed to find a problem, it was easy, turned out that required Cipher suite was not enabled and Disable is prevail over Enable. But along the way I’ve learned tons of things about SSL and Java. I’ll try to use your implementation in my binding.

K.