HttpClient supporting multiple TLS protocols

agentgonzo picture agentgonzo · Aug 14, 2013 · Viewed 23.2k times · Source

We're writing an app that must communicate with a few servers using HTTPS. It needs to communicate with AWS (using the AWS libraries) and also with some of our internal services that use TLS 1.2.

I started off by changing my HttpClient to use a TLS 1.2 SSLContext:

public static SchemeRegistry buildSchemeRegistry() throws Exception {
    final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
    sslContext.init(createKeyManager(), createTrustManager(), new SecureRandom());
    final SchemeRegistry schemeRegistry = new SchemeRegistry();
    schemeRegistry.register(new Scheme("https", 443, new SSLSocketFactory(sslContext)));
    return schemeRegistry;
}

and injecting this SchemeRegistry into the DefaultHttpClient object (via spring), but doing that I get errors from AWS and so I assume (I may be wrong) that AWS doesn't support TLS 1.2 (I don't get this message if I just use the normal DefaultHttpClient):

AmazonServiceException: Status Code: 403, AWS Service: AmazonSimpleDB, AWS Request ID: 5d91d65f-7158-91b6-431d-56e1c76a844c, AWS Error Code: InvalidClientTokenId, AWS Error Message: The AWS Access Key Id you provided does not exist in our records.

If I try to have two HttpClients defined in spring, one that uses TLS 1.2 and one that is the default, I get the following error, which I assume means that Spring doesn't like instantiating and autowiring two HttpClient objects:

SEVERE: Servlet /my-refsvc threw load() exception
java.lang.NullPointerException
at com.company.project.refsvc.base.HttpsClientFactory.<clinit>(BentoHttpsClientFactory.java:25)
...
org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1031)
at 
...
org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:223)

I haven't used HTTPS much in java so could you kind people give me some advice please? 1) How would I get Spring to allow two HttpClient objects and for one to be wired to the AWS stuff beans and the other to be wired to the other beans for accessing the TLS1.2 services 2) Or is it possible to change the one HttpClient object to be able to try TLS1.2 (via SSLContext, or the SchemeRegistry or something) and if that fails then try TLS1.1 or 1.0? 3) If both are possible, what would be the 'better' way of doing it?

Answer

Bruno picture Bruno · Aug 14, 2013

TLS has an in-built mechanism to negotiate which version of the protocol is to be used. From RFC 5246 (Appendix E):

TLS versions 1.0, 1.1, and 1.2, and SSL 3.0 are very similar, and use compatible ClientHello messages; thus, supporting all of them is relatively easy. Similarly, servers can easily handle clients trying to use future versions of TLS as long as the ClientHello format remains compatible, and the client supports the highest protocol version available in the server.

A TLS 1.2 client who wishes to negotiate with such older servers will send a normal TLS 1.2 ClientHello, containing { 3, 3 } (TLS 1.2) in ClientHello.client_version. If the server does not support this version, it will respond with a ServerHello containing an older version number. If the client agrees to use this version, the negotiation will proceed as appropriate for the negotiated protocol.

In addition, changing the version number in SSLContext.getInstance(...) only changes which protocols are enabled by default. Setting the actual protocol versions is done with SSLSocket.setEnabledProtocols(...) (see this question). I'm not sure about the rest of the libraries you're using, but it's possible that it sets the enabled protocols somewhere.

There are a few possibilities:

  • What you're doing in your createKeyManager() differs from the default behaviour. If the service is using client-certificate authentication, bad configuration there would certainly lead to a 403 error.

  • (Less likely, I guess, but hard to say without seeing your createKeyManager() and createTrustManager()). Perhaps the server you're using isn't compatible with TLS 1.2 and the version negotiation mechanism. There is this comment in sun.security.ssl.SSLContextImpl:

    SSL/TLS protocols specify the forward compatibility and version roll-back attack protections, however, a number of SSL/TLS server vendors did not implement these aspects properly, and some current SSL/TLS servers may refuse to talk to a TLS 1.1 or later client.