Enabling host verification in Netty
Published
Updated
By default, Netty version 4 and lower are configured with Host verification disabled. While this is helpful during debugging or developing an application, it can become a security issue if brought in production.
There are a number of steps involved in certificate verification, one of these is to compare the hostname of the client or remote server to the certificate they are presenting. Should this hostname verification be disabled, any valid certificate presented by the same issuer could be accepted as valid, even though that certificate isn’t intended to be used by that hostname. Ultimately this could lead to an attacker using a custom made, or stolen certificate and could potentially lead to a man in the middle attack.
As a result of this, many static-analysis tools such as WhiteSource flag this as a security finding when using the netty-all
dependency. Additionally, Netty is a transitive dependency found in many other popular projects such as Spring, the AWS SDK, Apache Http Client, Gremlin, Spark, Cassandra, and many others.
Unfortunately, the Netty developers have decided not to alter this default configuration in Netty versions 4 and lower, but will enable host verification by default in the upcoming Netty 5 beta.
The following examples can be used as a reference to enable hostname verification within Netty, even if your application does not directly interface with it.
Enabling Hostname Verification using Netty’s SSLEngine
If your application has visibility or scope over Netty’s SSLEngine, the fix is as simple as getting the SSLParameters
and configuring the Endpoint Identification:
SSLEngine sslEngine = getSSLEngine(); // Get your sslEngine reference
// The below code will reconfigure Netty's SSLEngine to enable hostname verification
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
Unfortunately, the SSLEngine is not typically visible or configurable except upon initial connection initialization which makes the above configuration change not possible without some additional code.
Configuring Netty’s SslContext
Most libraries that implement Netty will give you access to the SslContext to be used within the underlying library usually via a Builder or setter. Knowing this, we can implement a kind of decorator pattern to Netty’s default SslContext and return one that has a modified SSLEngine as described in the above section.
Note: don’t mix up Netty’s SslContext
up with Java’s SSLContext
as they are different and not interchangable
SslContext context = new SslContext() {
// Alter the below context as needed, adding in any extra configurations needed for trust stores and such.
// I have provided a JDK default implementation that should work in most situations
SslContext sslContext = SslContextBuilder.forClient()
.sslProvider(SslProvider.JDK)
.keyManager(KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()))
.trustManager(TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()))
.build();
@Override
public boolean isClient() {
return sslContext.isClient();
}
@Override
public SSLSessionContext sessionContext() {
return sslContext.sessionContext();
}
@Override
public List<String> cipherSuites() {
return sslContext.cipherSuites();
}
@Override
public ApplicationProtocolNegotiator applicationProtocolNegotiator() {
return sslContext.applicationProtocolNegotiator();
}
@Override
public SSLEngine newEngine(ByteBufAllocator byteBufAllocator) {
SSLEngine sslEngine = sslContext.newEngine(byteBufAllocator);
// Hostname verification fix below:
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
return engine;
}
@Override
public SSLEngine newEngine(ByteBufAllocator byteBufAllocator, String s, int i) {
SSLEngine sslEngine = sslContext.newEngine(byteBufAllocator, s, i);
// Hostname verification fix below:
SSLParameters sslParameters = sslEngine.getSSLParameters();
sslParameters.setEndpointIdentificationAlgorithm("HTTPS");
sslEngine.setSSLParameters(sslParameters);
return engine;
}
};
Simply pass the reference to your context
to the library and it should now use the modified configuration. For example, in Spring using a WebClient it would be something like the following:
@Bean
public WebClient webClient() throws Exception {
HttpClient httpClient = HttpClient
.create()
.secure(t -> t.sslContext(context)); // Reference our context defined above
return WebClient.builder().clientConnector(new ReactorClientHttpConnector(httpClient)).build();
}
When configuring Tinkerpop / Gremlin, you could do something like:
Cluster.Builder builder = Cluster.build();
builder.addContactPoint("someurlhere");
builder.port(1234);
builder.sslContext(context); // Reference our context defined above