3

While testing my client-server distributed system, I was surprised at first to learn that the default JSSE implementation of TLS doesn't do hostname verification. I tried the accepted answer in this question, but my use case is a bit different. I use RabbitMQ's connection factory, which abstracts the SSLSocket's construction. I just provide the connection factory with an SSLContext. I did find a lot about HTTPS and even some other protocols, but not something general that can always be used, even with custom protocols.

There's not really much to be found about creating a domain-verifying SSLContext though, except for using the X509ExtendedTrustManager. While debugging, I can see that

TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init((KeyStore) null);
tmf.getTrustManagers();

returns one TrustManager, a X509TrustManagerImpl, which according to this page extends X509ExtendedTrustManager. This one does not, however, reject a faulty certificate (and by 'faulty' I mean that the certificate does not match the server's hostname).

So I then resorted to writing my own X509ExtendedTrustManager, which delegates to the trust managers from my TrustManagerFactory:

final TrustManager[] trustManagers = tmf.getTrustManagers();
X509ExtendedTrustManager x509ExtendedTrustManager = new X509ExtendedTrustManager() {
    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
        checkClientTrusted(x509Certificates, s);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s, Socket socket) throws CertificateException {
        checkServerTrusted(x509Certificates, s);
    }

    @Override
    public void checkClientTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
        checkClientTrusted(x509Certificates, s);
    }

    @Override
    public void checkServerTrusted(X509Certificate[] x509Certificates, String s, SSLEngine sslEngine) throws CertificateException {
        checkServerTrusted(x509Certificates, s);
    }

    public void checkClientTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        for (TrustManager trustManager : trustManagers)
            ((X509TrustManager)trustManager).checkClientTrusted(x509Certificates, s);
    }

    public void checkServerTrusted(X509Certificate[] x509Certificates, String s) throws CertificateException {
        for (TrustManager trustManager : trustManagers)
            ((X509TrustManager)trustManager).checkServerTrusted(x509Certificates, s);

        for (X509Certificate x509Certificate : x509Certificates) {
            Collection<List<?>> alternativeNameCollection = x509Certificate.getSubjectAlternativeNames();
            if (alternativeNameCollection != null) {
                for (List alternativeNames : alternativeNameCollection) {
                    if (alternativeNames.get(1).equals(host))
                        return;
                }
            }
        }

        throw new CertificateException("Certificate hostname and requested hostname don't match");
    }

    public X509Certificate[] getAcceptedIssuers() {
        return new X509Certificate[0];
    }
};

This does finally work. But I cannot believe there wouldn't be a cleaner way to do this. Basically what I'm looking for is something standard, because I think what I'm doing is pretty standard too. I read about HostnameVerifier but is there a way to use it with SSLContext without using HTTPS? Is there a hostname-verifying implementation of X509ExtendedTrustManager somewhere? I'm sure I'm reinventing things that have already been written.

EDIT: this is a good, working example. Still custom code though.

EDIT 2: The problem still remains with RabbitMQ, because RabbitMQ resolves the DNS and passes the IP address to the verifier, which of course always fails. So the X509ExtendedTrustManager still seems the way to go.

Community
  • 1
  • 1
delucasvb
  • 5,393
  • 4
  • 25
  • 35
  • 1
    I'm tempted to suggest this could be a duplicate of [this question](http://stackoverflow.com/q/17972658/372643) (i.e. you do actually get something out of the box in the JSSE, starting with Java 7). I'm not sure how your RabbitMQ library implements hostname verification, but if it doesn't, it's a bug there. – Bruno Apr 12 '16 at 11:28
  • I agree that the questions are very similar, they are both about hostname verification. The difference IMO is that that question and its answers are all about HTTPS. I'm looking for a general solution, basically to add a hostname verification feature to any protocol and I just find it dangerous and unmaintainable to write code like this myself (which, for now, is how I've solved it). I would rather rely on a library that has been implemented by a team with more knowledge on this subject. – delucasvb Apr 12 '16 at 12:45
  • @delucasvb see https://tersesystems.com/2014/03/23/fixing-hostname-verification/ for more on this – Will Sargent Apr 01 '17 at 02:28
  • I use Netty for network comm. It does hostname verification by default, including for raw TLS sockets. – Aleksandr Dubinsky Jul 15 '21 at 06:27

1 Answers1

-1

Hostname verification isn't part of SSL. It is part of HTTPS. You're investigating the wrong layer and the wrong APIs. You should be looking into HttpURLConnection and HostnameVerifier.

user207421
  • 305,947
  • 44
  • 307
  • 483
  • I'm using other protocols than HTTPS, some of which are custom. – delucasvb Apr 11 '16 at 13:08
  • That doesn't change my answer in any way. – user207421 Apr 12 '16 at 09:28
  • I edited my question. I tried working with a hostname verifier, but this doesn't always work because it doesn't always receive a hostname. Some libraries or frameworks (like RabbitMQ) will send it an IP address. – delucasvb Apr 12 '16 at 10:09
  • That doesn't change my answer in any way either. – user207421 Oct 18 '17 at 09:11
  • @EJP https://docs.oracle.com/javase/7/docs/api/javax/net/ssl/X509ExtendedTrustManager.html You are right that hostname verification is done at HTTPS not at SSL/TLS layer but the above class does exist for doing both certificate chain checks and hostname verification with one API call. The doc says "This class allows for the checking to be done during a single call to this class." – CoderSpinoza Jun 19 '18 at 08:55
  • 1
    Hostname verification is not "part of HTTPS." It has nothing to with HTTP. It's an optional step that any TLS client can do, but required for HTTP-over-TLS. – Aleksandr Dubinsky Jul 15 '21 at 06:35
  • There are quite a view protocols like SMTP, IMAP, ... which are not HTTP but use SSL/TLS. Based of what you wrote those protocols should not have/perform host name verification - which is nonsense. See [RFC6125](https://www.rfc-editor.org/rfc/rfc6125) – Robert Sep 28 '22 at 12:44