It seems that I am destined to become a deep expert in the vagaries of TLS these days. My most recent challenge was in figuring out why Server Name Indication (SNI) extensions weren't simply working in my BC-FIPS implementation that I talked about in the last few posts.
Background on SNI
For a brief moment, let's talk a little about SNI. TLS is a lower layer session protocol on top of TCP that encrypts communication. HTTP and HTTPS are higher layer (Application) protocols on top of TLS. When you connect to an IP address over TCP, then initiate a TLS connection, the application layer hasn't yet seen the HTTP request, let alone the Host header. SNI serves, in TLS, the same function as the HTTP Host header. Effectively, this works in the same way that the HTTP Host header does.
In HTTP, the Host header allows one server to service multiple web sites or DNS endpoints, but unless SNI is used each endpoint must be served with the same certificate, either using a wildcare or multiple alternate names. SNI allows one host to service multiple sites with different certificates for each site.
Integrating SNI with Apache CXF and BCFIPS
"... Unfortunately, when using HttpsURLConnection SunJSSE uses some magic (reflection and/or internal API) to tell the socket about the "original hostname" used for the connection, and we cannot use that same magic as it is internal to the JVM.To allow the endpoint validation to work properly you need to make use of one of three workarounds:"
3. The third (and recommended) alternative is to set a customized SSLSocketFactory on the HttpsURLConnection, then intercept the socket creation call and manually set the SNI host_name on the created socket. We provide a utility class to make this simple, as shown in the example code below.
That's pretty simple. What URLConnectionUtil.openConnection does is wrap the socket factory provided by conn (see HttpsURLConnection.setSSLSocketFactory) with one that calls a method to set the server name extension in createSocket after calling the original createSocket method found in the connection.// main code block{ SSLContext sslContext = ...;URL serverURL = ...;URLConnectionUtil util = new URLConnectionUtil();HttpsURLConnection conn =(HttpsURLConnection)util.openConnection(serverURL);}
public HttpURLConnection createConnection(TLSClientParameters tlsClientParameters,
Proxy proxy, URL url) throws IOException {
HttpURLConnection connection =
(HttpURLConnection) (proxy != null ? url.openConnection(proxy) : url.openConnection());
if (HTTPS_URL_PROTOCOL_ID.equals(url.getProtocol())) {
if (tlsClientParameters == null) {
tlsClientParameters = new TLSClientParameters();
}
try {
decorateWithTLS(tlsClientParameters, connection);
} catch (Throwable ex) {
throw new IOException("Error while initializing secure socket", ex);
}
}
return connection;
}
URLConnectionUtil util = new URLConnectionUtil(
tlsClientParameters == null ? null : tlsClientParameters.getSSLSocketFactory()
);
HttpURLConnection connection =
(HttpURLConnection) (proxy != null ? util.openConnection(url, proxy) : util.openConnection(url));
- I don't care about other than JSSE implementations.
- My socketFactory is always set when I enter this method, and that's the one to use.
if (HTTPS_URL_PROTOCOL_ID.equals(url.getProtocol())) {
if (tlsClientParameters == null) {
tlsClientParameters = new TLSClientParameters();
}
HostnameVerifier verifier = SSLUtils.getHostnameVerifier(tlsClientParameters);
connection.setHostnameVerifier(verifier);
}
public class HTTPConduit extends URLConnectionHTTPConduit {
public static class Factory implements HTTPConduitFactory {
@Override
public org.apache.cxf.transport.http.HTTPConduit createConduit(HTTPTransportFactory f, Bus b,
EndpointInfo localInfo, EndpointReferenceType target) throws IOException {
HTTPConduit conduit = new HTTPConduit(b, localInfo, target);
// Perform any other conduit configuration here
return conduit;
}
}
public HTTPConduit(Bus b, EndpointInfo ei, EndpointReferenceType t) throws IOException {
super(b, ei, t);
// Override the default connectionFactory.
connectionFactory = new ConnectionFactory();
}
}