Skip to content

Commit

Permalink
Configure Apache HttpClient to perform hostname verification
Browse files Browse the repository at this point in the history
  • Loading branch information
lherman-cs committed Sep 11, 2020
1 parent fefd48b commit 1238de8
Show file tree
Hide file tree
Showing 6 changed files with 455 additions and 60 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,176 @@
package com.amazonaws.kinesisvideo.http;

import com.amazonaws.kinesisvideo.common.logging.Log;
import org.apache.http.conn.ssl.DefaultHostnameVerifier;
import org.apache.http.conn.util.PublicSuffixMatcherLoader;

import javax.net.ssl.SSLEngine;
import javax.net.ssl.SSLException;
import javax.net.ssl.TrustManager;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedTrustManager;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;

/*
Adapted from (Apache 2.0 License):
https://github.com/apache/zookeeper/blob/master/zookeeper-server/src/main/java/org/apache/zookeeper/common/
ZKTrustManager.java
A custom TrustManager that supports hostname verification via org.apache.http.conn.ssl.DefaultHostnameVerifier.
*
* We attempt to perform verification using just the IP address first and if that fails will attempt to perform a
* reverse DNS lookup and verify using the hostname.
*/

public class HostnameVerifyingX509ExtendedTrustManager extends X509ExtendedTrustManager {

private static final DefaultHostnameVerifier DEFAULT_HOSTNAME_VERIFIER = new DefaultHostnameVerifier(
PublicSuffixMatcherLoader.getDefault());
private Log log = new Log(Log.SYSTEM_OUT);
private final boolean clientSideHostnameVerificationEnabled;

private final X509ExtendedTrustManager x509ExtendedTrustManager;

public HostnameVerifyingX509ExtendedTrustManager(final boolean clientSideHostnameVerificationEnabled) {
this.x509ExtendedTrustManager = getX509ExtendedTrustManager();
this.clientSideHostnameVerificationEnabled = clientSideHostnameVerificationEnabled;
}

private X509ExtendedTrustManager getX509ExtendedTrustManager() {
TrustManagerFactory factory;
try {
factory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
factory.init((KeyStore) null);
} catch (NoSuchAlgorithmException nsae) {
throw new RuntimeException("Unable to initialize default TrustManagerFactory", nsae);
} catch (KeyStoreException nse) {
throw new RuntimeException("Unable to initialize default TrustManagerFactory", nse);
}

for (TrustManager tm: factory.getTrustManagers()) {
if (tm instanceof X509ExtendedTrustManager) {
return (X509ExtendedTrustManager) tm;
}
}

throw new RuntimeException("No default X509TrustManager found");
}

@Override
public X509Certificate[] getAcceptedIssuers() {
return x509ExtendedTrustManager.getAcceptedIssuers();
}

@Override
public void checkClientTrusted(
final X509Certificate[] chain,
final String authType,
final Socket socket) throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType, socket);
if (clientSideHostnameVerificationEnabled) {
performHostVerification(socket.getInetAddress(), chain[0]);
}
}

@Override
public void checkServerTrusted(
final X509Certificate[] chain,
final String authType,
final Socket socket) throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType, socket);
performHostVerification(socket.getInetAddress(), chain[0]);
}

@Override
public void checkClientTrusted(
final X509Certificate[] chain,
final String authType,
final SSLEngine engine) throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType, engine);
if (clientSideHostnameVerificationEnabled) {
try {
performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
} catch (UnknownHostException e) {
throw new CertificateException("Failed to verify host", e);
}
}
}

@Override
public void checkServerTrusted(
final X509Certificate[] chain,
final String authType,
final SSLEngine engine) throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType, engine);
try {
performHostVerification(InetAddress.getByName(engine.getPeerHost()), chain[0]);
} catch (UnknownHostException e) {
throw new CertificateException("Failed to verify host", e);
}
}

@Override
public void checkClientTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
x509ExtendedTrustManager.checkClientTrusted(chain, authType);
}

@Override
public void checkServerTrusted(final X509Certificate[] chain, final String authType) throws CertificateException {
x509ExtendedTrustManager.checkServerTrusted(chain, authType);
}

/**
* Compares peer's hostname with the one stored in the provided client certificate. Performs verification
* with the help of provided HostnameVerifier.
*
* @param inetAddress Peer's inet address.
* @param certificate Peer's certificate
* @throws CertificateException Thrown if the provided certificate doesn't match the peer hostname.
*/
public void performHostVerification(
final InetAddress inetAddress,
final X509Certificate certificate
) throws CertificateException {
performHostVerification(inetAddress.getHostAddress(), inetAddress.getHostName(), certificate);
}

/**
* Compares peer's hostname with the one stored in the provided client certificate. Performs verification
* with the help of provided HostnameVerifier.
*
* @param hostAddress Peer's host address.
* @param hostName Peer's host name.
* @param certificate Peer's certificate
* @throws CertificateException Thrown if the provided certificate doesn't match the peer hostname.
*/
public void performHostVerification(
final String hostAddress,
final String hostName,
final X509Certificate certificate
) throws CertificateException {
try {
DEFAULT_HOSTNAME_VERIFIER.verify(hostAddress, certificate);
} catch (SSLException addressVerificationException) {
try {
log.debug(
"Failed to verify host address: {} attempting to verify host name with reverse dns lookup",
hostAddress,
addressVerificationException);
DEFAULT_HOSTNAME_VERIFIER.verify(hostName, certificate);
} catch (SSLException hostnameVerificationException) {
log.error("Failed to verify host address: {}", hostAddress, addressVerificationException);
log.error("Failed to verify hostname: {}", hostName, hostnameVerificationException);
throw new CertificateException("Failed to verify both host address and host name",
hostnameVerificationException);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,24 +4,26 @@

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.concurrent.FutureCallback;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.nio.client.CloseableHttpAsyncClient;
import org.apache.http.impl.nio.client.HttpAsyncClientBuilder;
import org.apache.http.nio.client.methods.HttpAsyncMethods;
import org.apache.http.nio.conn.ssl.SSLIOSessionStrategy;
import org.apache.http.nio.protocol.HttpAsyncRequestProducer;
import org.apache.http.nio.protocol.HttpAsyncResponseConsumer;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedTrustManager;

/**
* Http Async Client which uses Apache HttpAsyncClient internally to make
Expand Down Expand Up @@ -54,13 +56,13 @@ public void executeRequest() {
}

private CloseableHttpAsyncClient buildHttpAsyncClient() {
final SSLContextBuilder builder = new SSLContextBuilder();
try {
builder.loadTrustMaterial(new TrustAllStrategy());
final SSLIOSessionStrategy sslSessionStrategy = new SSLIOSessionStrategy(builder.build(),
new String[] {"TLSv1.2"},
null,
new NoopHostnameVerifier());
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new X509ExtendedTrustManager[] {
new HostnameVerifyingX509ExtendedTrustManager(true)}, new SecureRandom());

final SSLIOSessionStrategy sslSessionStrategy = new SSLIOSessionStrategy(sslContext);

return HttpAsyncClientBuilder.create()
.setSSLStrategy(sslSessionStrategy)
.setDefaultRequestConfig(RequestConfig.custom()
Expand All @@ -72,8 +74,6 @@ private CloseableHttpAsyncClient buildHttpAsyncClient() {
throw new RuntimeException("Exception while building Apache http client", e);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Exception while building Apache http client", e);
} catch (final KeyStoreException e) {
throw new RuntimeException("Exception while building Apache http client", e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@

import java.io.IOException;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Map;

import org.apache.http.HttpEntity;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.config.SocketConfig;
import org.apache.http.conn.ssl.NoopHostnameVerifier;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.ssl.SSLContextBuilder;

import javax.net.ssl.SSLContext;
import javax.net.ssl.X509ExtendedTrustManager;

/**
* Http Client which uses Apache HttpClient internally to make
Expand Down Expand Up @@ -53,13 +54,13 @@ public CloseableHttpResponse executeRequest() {
}

private CloseableHttpClient buildHttpClient() {
final SSLContextBuilder builder = new SSLContextBuilder();
try {
builder.loadTrustMaterial(new TrustAllStrategy());
final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(builder.build(),
new String[] {"TLSv1.2"}, // TLS protocol, use 1.2 only
null, // TLS ciphers, use default
new NoopHostnameVerifier());
final SSLContext sslContext = SSLContext.getInstance("TLSv1.2");
sslContext.init(null, new X509ExtendedTrustManager[] {
new HostnameVerifyingX509ExtendedTrustManager(true)}, new SecureRandom());

final SSLConnectionSocketFactory sslSocketFactory = new SSLConnectionSocketFactory(sslContext);

return HttpClients.custom()
.setSSLSocketFactory(sslSocketFactory)
.setDefaultRequestConfig(RequestConfig.custom()
Expand All @@ -73,8 +74,6 @@ private CloseableHttpClient buildHttpClient() {
throw new RuntimeException("Exception while building Apache http client", e);
} catch (final NoSuchAlgorithmException e) {
throw new RuntimeException("Exception while building Apache http client", e);
} catch (final KeyStoreException e) {
throw new RuntimeException("Exception while building Apache http client", e);
}
}

Expand Down

This file was deleted.

28 changes: 5 additions & 23 deletions src/main/java/com/amazonaws/kinesisvideo/socket/SocketFactory.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package com.amazonaws.kinesisvideo.socket;

import com.amazonaws.kinesisvideo.http.HostnameVerifyingX509ExtendedTrustManager;

import javax.net.ssl.KeyManager;
import javax.net.ssl.SSLContext;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
import javax.net.ssl.X509ExtendedTrustManager;

import java.net.InetAddress;
import java.net.Socket;
import java.net.URI;
import java.security.SecureRandom;
import java.security.cert.X509Certificate;

public class SocketFactory {
private static final int DEFAULT_HTTP_PORT = 80;
Expand All @@ -35,27 +35,9 @@ private Socket openSocket(final URI uri) throws Exception {

private Socket createSslSocket(final InetAddress address, final int port) throws Exception {
final SSLContext context = SSLContext.getInstance("TLSv1.2");
context.init(NO_KEY_MANAGERS, trustAllCertificates(), new SecureRandom());
context.init(NO_KEY_MANAGERS, new X509ExtendedTrustManager[] {
new HostnameVerifyingX509ExtendedTrustManager(true)}, new SecureRandom());
return context.getSocketFactory().createSocket(address, port);

}

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

public void checkClientTrusted(final X509Certificate[] certs, final String authType) {

}

public void checkServerTrusted(final X509Certificate[] certs, final String authType) {

}
}
};
}

private boolean isHttps(final URI uri) {
Expand Down
Loading

0 comments on commit 1238de8

Please sign in to comment.