HttpClient: how to suppress 'trust all certificates' warning

The binding that I am developing for Growatt solar inverters, uses an HTTP client to send HTTPS commands to the Growatt cloud server endpoint, and it is currently configured to ‘trust all’ certificates. This leads to the warning messages below. So I am wondering what is the proper way to configure the client to eliminate that warning?

2024-01-08 00:10:00.451 [WARN ] [ty.util.ssl.SslContextFactory.config] - Trusting all certificates configured for Client@6b204ba7[provider=null,keyStore=null,trustStore=null]
2024-01-08 00:10:00.452 [WARN ] [ty.util.ssl.SslContextFactory.config] - No Client EndPointIdentificationAlgorithm configured for Client@6b204ba7[provider=null,keyStore=null,trustStore=null]

The only way this warning should be eliminated is if you don’t trust all certificates. Trusting all certificates is a security risk and end users should be able to decide whether that risk is worth accepting or not. Suppressing that warning eliminates the possibility that the end users can make that decision since they won’t even know.

Unfortunately, if they decide not accept all certificates, they will need to go through the effort to establish a CA, import that CA’s certificate into the trust store used by OH, and deploy a cert to the Growatt service.

The fact that all certificates are trusted should not be suppressed.

@rlkoshak maybe I should have been clearer. I don’t want to suppress the warning. I want to tell OH that I do trust the Growatt server certificates specifically. Rather than trusting all certificates. Obviously I understand your polemic, but that is not what I was asking for.

Out of curiosity how do you trust them in your binding. Do you independently verify the certificate against the CA that signs the Growatt’s certificate? If you go that far, maybe it makes sense to do the HTTPS with it directly instead of using the HttpClient.

Maybe the binding can add the CA to the overall trust store when it’s installed (I’m not sure that’s a good idea though)?

Maybe a PR can be made to the distro to add the CA certificate to all OH instances.

You can implement a TlsTrustManagerProvider, similar to AVMFritzTlsTrustManagerProvider which allows you to trust all certificates for a domain. A better solution would be to also create a sub-class X509ExtendedTrustManager that checks the certificate and return that instead of TrustAllTrustManager.

Another way (the most secure, but also most user-unfriendly) is to create a way to import the certificate chain and then insert that into the keystore.

2 Likes

I did this for the initial implementation of the vizio binding.It worked OK to grab the cert from the tv. The issue was that I could not find an easy way to determine if the cert retrieval failed because the TV could not be reached (off in power save mode) during binding initialization and re-try. I ended up re-factoring to use trust all certificates instead.

Thanks guys for all the advice. I looked at the AvmFritz-, BoschShc, and Vizio- approaches. Had to rule them all out for various reasons. Following is an update on this…

  • Obviously (IMHO) the trust all “solution” is too brutish…

  • The TlsTrustManagerProvider component solution unfortunately won’t work for me because I cannot use the OH common Http client; the binding does fancy stuff with cookies so I need to create a binding specific Http client instead.

  • The problem in my case is because the manufacturer’s remote cloud API is hosted on https://server-api.growatt.com whereas its certificate is named for https://server.growatt.com … so it gives a “certificate common name mismatch error” … AFAICT many other websites are guilty of such errors.

  • My current solution is shown below. It verifies the domain name suffixes but ignores the server certificate. This also avoids the OH warnings mentioned in my OP.

  • ‘Nice-to-Have’ would be to have some way to cross validate the certificate produced by https://server-api.growatt.com against its https://server.growatt.com cousin. But I am guessing that would be quite hard to do. Or??

    public GrowattCloud(GrowattBridgeConfiguration configuration, HttpClientFactory httpClientFactory)
            throws Exception {
        this.configuration = configuration;

        SslContextFactory.Client sslContextFactory = new SslContextFactory.Client.Client();
        sslContextFactory.setHostnameVerifier((@Nullable String host, @Nullable SSLSession session) -> {
            if (session != null) {
                String peerHost = session.getPeerHost();
                if (peerHost != null && host != null) {
                    return peerHost.endsWith(SERVER_DOMAIN) && host.endsWith(SERVER_DOMAIN);
                }
            }
            return false;
        });
        sslContextFactory.setValidatePeerCerts(false);

        this.httpClient = httpClientFactory.createHttpClient("growatt-cloud-api", sslContextFactory);
        this.httpClient.start();
    }

Are you sure that’s what going on? I went to the server-api.growatt.com address and the CN in the cert matches the domain, at least for the 405 error page it brings up.

So they do have a cert with the right CN. Maybe there are parts of the API using the wrong cert? If so they need to know this because it’s going to break everyone, not just openHAB. There’s no point in having the API if no one can use it properly and there’s no point in having the proper cert if they are not going to use it.

The risk is low but this pretty much bypasses all the protections that PKI is supposed to provide. The binding will be susceptible to adversary-in-the-middle attacks among others.

Impossible by design.

The point of the certificate is trust. It ensures that you are talking to who you think you are talking to. It does this by Growatt going to DigiCert (in this case) and proving to them that they are Growatt and that they run server-api.growatt.com. Once DigiCert is satisfied then they sign a certificate that says “yea verily Growatt is a legitimate person or organization and they own server-api.growatt.com”.

We as the clients know that DigiCert has a good process for validating and verifying the identity of people and organizations so if DigiCert signs it, we know we can trust it. But DigiCert is only signing and certifying the Common Name. Just because they signed a cert for server.growatt.com does not have any bearing on whether they own and control server-api.growatt.com.

So the only way you can trust the cert from server.growatt.com for server-api.growatt.com is to not really use the cert at all, which is the same as “trust all certificates” which is basically what you are doing here, just without the warning in the logs.

Umm. Evidently not. Thanks for checking that @rlkoshak. I obviously need to dig further.

IMO “trust all” (or any other bypass of the certificate chain) should only happen for local hosts.

It seems that my problem has in the meantime resolved itself. Now when I use the OH stock HttpClientFactory.createHttpClient() method (which creates its internal OH stock SslContextFactory) … it all works fine. So there is no longer any need for me to monkey around with customising the SslContextFactory and far less need to ‘trust all’. I don’t honestly know why it now works and was failing before; maybe Growatt changed something on their server, or maybe I was doing something stupid before. Anyway thanks to you all for having this conversation, since it did finally lead me to the correct and proper solution!

4 Likes

The mechanisms to do the import are all in the CryptoStore PR pending review.