0

We are using HttpClient 4.4 to communicate with some external servers (server1.company.com, server2.company.com, server3.company.com). They recently added an additional environment (server4.company.com) which uses the same certificate as the other 3. The certificate in question has the first 3 servers listed as "Certificate Subject Alternative Names", but server4 is not mentioned.

Is it possible for me to tell keytool that the certificate is valid for additional SANs? Or is there any other way to tell HttpClient to 'trust' this one certificate for some extra domains? Are there any other options or must I go back to company.com and ask them to get a new certificate?

GaZ
  • 2,346
  • 23
  • 46
  • 1
    Telling them to fix the certificate is certainly the best option. Fiddling with the Trust Strategy puts you into problems (and possibly liability), if your code can be compromised. You should get a written statement, that they either take the risk or update the cert. – thst Mar 26 '15 at 11:24

2 Answers2

1

There are two aspects of certificate verifications (in general):

  1. Verifying that the certificate is genuine and issued by someone you trust (that's the PKI aspect).
  2. Verifying that it belongs to the host name you want to connect to (that's the host name verification).

(Perhaps this question, about libcurl might be of interest if you need analogies.)

According to what you are saying, that particular certificate is trusted and valid for other host names. Hence, it will pass the PKI verification (what the TrustStrategy implements).

What you need is to make build an exceptional case, only for that particular certificate, for the host name verification aspect.

I can't remember off the top of my head how it is used with Apache HTTP Client 4.4, but you should use your own verifier instead of the DefaultHostnameVerifier.

The methods to implement are verify(String hostYouAreAfter, SSLSession sessionYouActuallyGet) and verify(String hostYouAreAfter, X509Certificate certYouActuallyGet).

You can provide your own implementation along these lines:

public verify(String hostYouAreAfter, X509Certificate certYouActuallyGet) {
    if (certYouActuallyGet.equals(referenceCertificate)) {
         if ("server4.company.com".equalsIgnoreCase(hostYouAreAfter)) {
             // All good, don't fail and throw an exception.
         } else {
             super.verify(hostYouAreAfter, certYouActuallyGet);
         }
    } else {
         super.verify(hostYouAreAfter, certYouActuallyGet);
    }
}

You can do the same with verify(String,SSLSession) and get the X509Certificate from the SSLSession's peer chain (position 0). The logic is the same, but you need to return true/false instead of throwing exceptions.

Here, I'm assuming that you've loaded referenceCertificate from a place of reference where have that particular certificate. You could for example load it from a known keystore, or load it with a CertificateFactory from a reference PEM file configured in your application.


There are two key differences with a TrustStrategy where you'd implement isTrusted(final X509Certificate[] chain, final String authType) as return "nice guy".equalsIgnoreCase(issuerDN.getName());:

  • You're actually making this exceptional case only for that very certificate, not for any other certificate that would also happen to be issue with the name you're after.
  • It only affects the connections where you expect to connect to that particular host (not other hosts). You indeed have access to the first String parameter of HostnameVerifier.verify(...), which is the host name you intend to connect to. At least you have it to use for comparison with the certificate you get, which is something you don't get with a TrustStrategy.
Community
  • 1
  • 1
Bruno
  • 119,590
  • 31
  • 270
  • 376
  • thanks for taking the time to write this. This sounds more like the solution I was looking for. – GaZ Mar 26 '15 at 13:38
  • 1
    Note that, like many of these workarounds, the long-term cost of code maintenance might be higher than getting a valid certificate issued for that particular host (in particular if one day you change your mind or if you need to update that reference certificate). – Bruno Mar 26 '15 at 13:48
0

One can trust certain select certificates by using a custom TrustStrategy

SSLContext sslcontext = SSLContexts.custom()
  .loadTrustMaterial(new TrustStrategy() {
    @Override
    public boolean isTrusted(final X509Certificate[] chain, final String authType) 
        throws CertificateException {
      X509Certificate x509Certificate = chain[0];
      Principal issuerDN = x509Certificate.getIssuerDN();
      return "nice guy".equalsIgnoreCase(issuerDN.getName());
    }
  }).build();
CloseableHttpClient client = HttpClients.custom()
  .setSslcontext(sslcontext)
  .build();
Rafael Winterhalter
  • 42,759
  • 13
  • 108
  • 192
ok2c
  • 26,450
  • 5
  • 63
  • 71
  • (A) that should certainly be Subject DN, not Issuer DN. (B) That's actually insecure: you'd accept any cert with the right Subject DN (or Issuer DN in your example) without checking whether it's actually genuine. (Essentially, a bad said that calls itself "nice guy" would be accepted...) – Bruno Mar 26 '15 at 12:12
  • @Bruno agree that it's not secure, but the whole situation is insecure from the start. The only correct answer is to get new certificates. Oleg's answer is _slightly_ better than just trusting everything, which is my only working alternative at the moment. – GaZ Mar 26 '15 at 12:45
  • @Bruno. Please see the question and what was asked in the first place. I am not saying this makes sense or is the correct thing to do. I am saying this how it can be done should this be the last resort. – ok2c Mar 26 '15 at 12:50
  • @GaZ You could do this securely by adding an exception in the hostname verifier. Here, this will affect all the connections made by that HttpClient, provided the the attacker has the good idea to present a cert called "nice guy". Oleg, what should be done is adding an exceptional case for the host name when verifying that particular cert. According to the question, the cert itself is genuine (i.e. should be verified by the PKI/CertPath API). – Bruno Mar 26 '15 at 13:05
  • I've added an answer for additional details. I'm not even convinced `TrustStrategy` bypasses host name verification (which is what's required in this particular case). – Bruno Mar 26 '15 at 13:27
  • 1
    If the cert presented by the server is issued by the same CA, HostnameVerifier would be a better injection point. This however was not clear from the original question. The question was about 'trusting' a cert, which can be accomplished from a custom TrustStrategy only. – ok2c Mar 26 '15 at 13:43
  • 1
    I think from the question that we're talking of a single existing valid cert valid for 3 names, but not the 4th one which is the exceptional case. More generally, in terms of `TrustStrategy`, it would be good to convey the hostname the connection is trying to use to its `verify` method, so as to be able to have exceptional cases based on the host (like Firefox does, for example). That's a slightly different topic, though. – Bruno Mar 26 '15 at 13:47
  • Please raise a JIRA with a change request. I also realized that having access to the list of trusted CAs inside a TrustStrategy could be useful – ok2c Mar 26 '15 at 13:54