Skip to content

Commit

Permalink
HTTPCLIENT-2151: Support for JSSE in-built endpoint identification
Browse files Browse the repository at this point in the history
  • Loading branch information
ok2c committed Feb 5, 2024
1 parent e6e873d commit 2e46b62
Show file tree
Hide file tree
Showing 6 changed files with 260 additions and 19 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -30,18 +30,24 @@
import static org.hamcrest.MatcherAssert.assertThat;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyManagementException;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;

import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLHandshakeException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.SSLSocket;

import org.apache.hc.client5.http.ssl.DefaultClientTlsStrategy;
import org.apache.hc.client5.http.ssl.HostnameVerificationPolicy;
import org.apache.hc.client5.http.ssl.HttpsSupport;
import org.apache.hc.client5.http.ssl.NoopHostnameVerifier;
import org.apache.hc.client5.http.ssl.TlsSocketStrategy;
import org.apache.hc.client5.testing.SSLTestContexts;
Expand All @@ -54,6 +60,8 @@
import org.apache.hc.core5.ssl.SSLContexts;
import org.apache.hc.core5.ssl.TrustStrategy;
import org.hamcrest.CoreMatchers;
import org.hamcrest.MatcherAssert;
import org.hamcrest.Matchers;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
Expand Down Expand Up @@ -345,4 +353,111 @@ private void testWeakCipherDisabledByDefault(final String cipherSuite) throws Ex
context);
}
}

@Test
public void testHostnameVerificationClient() throws Exception {
// @formatter:off
this.server = ServerBootstrap.bootstrap()
.setSslContext(SSLTestContexts.createServerSSLContext())
.create();
// @formatter:on
this.server.start();

final HttpHost target1 = new HttpHost("https", "localhost", server.getLocalPort());

try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
SSLTestContexts.createClientSSLContext(),
HostnameVerificationPolicy.CLIENT,
HttpsSupport.getDefaultHostnameVerifier());
final HttpContext context = new BasicHttpContext();
final SSLSocket upgradedSocket = tlsStrategy.upgrade(
socket,
target1.getHostName(),
target1.getPort(),
null,
context);
final SSLSession session = upgradedSocket.getSession();
MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
}

final HttpHost target2 = new HttpHost("https", "some-other-host", server.getLocalPort());

try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
SSLTestContexts.createClientSSLContext(),
HostnameVerificationPolicy.CLIENT,
HttpsSupport.getDefaultHostnameVerifier());
final HttpContext context = new BasicHttpContext();
Assertions.assertThrows(SSLPeerUnverifiedException.class, () ->
tlsStrategy.upgrade(
socket,
target2.getHostName(),
target2.getPort(),
null,
context));
}

try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
SSLTestContexts.createClientSSLContext(),
HostnameVerificationPolicy.CLIENT,
NoopHostnameVerifier.INSTANCE);
final HttpContext context = new BasicHttpContext();
final SSLSocket upgradedSocket = tlsStrategy.upgrade(
socket,
target1.getHostName(),
target1.getPort(),
null,
context);
final SSLSession session = upgradedSocket.getSession();
MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
}
}

@Test
public void testHostnameVerificationBuiltIn() throws Exception {
// @formatter:off
this.server = ServerBootstrap.bootstrap()
.setSslContext(SSLTestContexts.createServerSSLContext())
.create();
// @formatter:on
this.server.start();

final HttpHost target1 = new HttpHost("https", "localhost", server.getLocalPort());

try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
SSLTestContexts.createClientSSLContext(),
HostnameVerificationPolicy.BUILTIN,
NoopHostnameVerifier.INSTANCE);
final HttpContext context = new BasicHttpContext();
final SSLSocket upgradedSocket = tlsStrategy.upgrade(
socket,
target1.getHostName(),
target1.getPort(),
null,
context);
final SSLSession session = upgradedSocket.getSession();
MatcherAssert.assertThat(Objects.toString(session.getPeerPrincipal()), Matchers.startsWith("CN=localhost"));
}

final HttpHost target2 = new HttpHost("https", "some-other-host", server.getLocalPort());

try (final Socket socket = new Socket(InetAddress.getLocalHost(), server.getLocalPort())) {
final TlsSocketStrategy tlsStrategy = new DefaultClientTlsStrategy(
SSLTestContexts.createClientSSLContext(),
HostnameVerificationPolicy.BUILTIN,
NoopHostnameVerifier.INSTANCE);
final HttpContext context = new BasicHttpContext();
Assertions.assertThrows(SSLHandshakeException.class, () ->
tlsStrategy.upgrade(
socket,
target2.getHostName(),
target2.getPort(),
null,
context));
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import org.apache.hc.core5.annotation.ThreadingBehavior;
import org.apache.hc.core5.concurrent.FutureCallback;
import org.apache.hc.core5.http.HttpHost;
import org.apache.hc.core5.http.URIScheme;
import org.apache.hc.core5.http.nio.ssl.TlsStrategy;
import org.apache.hc.core5.http.protocol.HttpContext;
import org.apache.hc.core5.http.ssl.TLS;
Expand All @@ -79,20 +80,24 @@ abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrate
private final String[] supportedProtocols;
private final String[] supportedCipherSuites;
private final SSLBufferMode sslBufferManagement;
private final HostnameVerificationPolicy hostnameVerificationPolicy;
private final HostnameVerifier hostnameVerifier;

AbstractClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
super();
this.sslContext = Args.notNull(sslContext, "SSL context");
this.supportedProtocols = supportedProtocols;
this.supportedCipherSuites = supportedCipherSuites;
this.sslBufferManagement = sslBufferManagement != null ? sslBufferManagement : SSLBufferMode.STATIC;
this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : HttpsSupport.getDefaultHostnameVerifier();
this.hostnameVerificationPolicy = hostnameVerificationPolicy != null ? hostnameVerificationPolicy : HostnameVerificationPolicy.BOTH;
this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier :
(this.hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN ? NoopHostnameVerifier.INSTANCE : HttpsSupport.getDefaultHostnameVerifier());
}

/**
Expand Down Expand Up @@ -147,6 +152,10 @@ public void upgrade(

applyParameters(sslEngine, sslParameters, H2TlsSupport.selectApplicationProtocols(versionPolicy));

if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
}

initializeEngine(sslEngine);

if (LOG.isDebugEnabled()) {
Expand Down Expand Up @@ -181,7 +190,8 @@ protected void initializeSocket(final SSLSocket socket) {
protected void verifySession(
final String hostname,
final SSLSession sslsession) throws SSLException {
verifySession(hostname, sslsession, hostnameVerifier);
verifySession(hostname, sslsession,
hostnameVerificationPolicy == HostnameVerificationPolicy.CLIENT || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH ? hostnameVerifier : null);
}

@Override
Expand All @@ -204,16 +214,23 @@ private void executeHandshake(
final String target,
final Object attachment) throws IOException {
final TlsConfig tlsConfig = attachment instanceof TlsConfig ? (TlsConfig) attachment : TlsConfig.DEFAULT;

final SSLParameters sslParameters = upgradedSocket.getSSLParameters();
if (supportedProtocols != null) {
upgradedSocket.setEnabledProtocols(supportedProtocols);
sslParameters.setProtocols(supportedProtocols);
} else {
upgradedSocket.setEnabledProtocols((TLS.excludeWeak(upgradedSocket.getEnabledProtocols())));
sslParameters.setProtocols((TLS.excludeWeak(upgradedSocket.getEnabledProtocols())));
}
if (supportedCipherSuites != null) {
upgradedSocket.setEnabledCipherSuites(supportedCipherSuites);
sslParameters.setCipherSuites(supportedCipherSuites);
} else {
upgradedSocket.setEnabledCipherSuites(TlsCiphers.excludeWeak(upgradedSocket.getEnabledCipherSuites()));
sslParameters.setCipherSuites(TlsCiphers.excludeWeak(upgradedSocket.getEnabledCipherSuites()));
}
if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
}
upgradedSocket.setSSLParameters(sslParameters);

final Timeout handshakeTimeout = tlsConfig.getHandshakeTimeout();
if (handshakeTimeout != null) {
upgradedSocket.setSoTimeout(handshakeTimeout.toMillisecondsIntBound());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,12 +74,8 @@ public static ClientTlsStrategyBuilder create() {
private String[] tlsVersions;
private String[] ciphers;
private SSLBufferMode sslBufferMode;
private HostnameVerificationPolicy hostnameVerificationPolicy;
private HostnameVerifier hostnameVerifier;
/**
* @deprecated To be removed.
*/
@Deprecated
private Factory<SSLEngine, TlsDetails> tlsDetailsFactory;
private boolean systemProperties;

/**
Expand Down Expand Up @@ -125,6 +121,13 @@ public ClientTlsStrategyBuilder setSslBufferMode(final SSLBufferMode sslBufferMo
return this;
}

/**
* Assigns {@link HostnameVerificationPolicy} value.
*/
public void setHostnameVerificationPolicy(final HostnameVerificationPolicy hostnameVerificationPolicy) {
this.hostnameVerificationPolicy = hostnameVerificationPolicy;
}

/**
* Assigns {@link HostnameVerifier} instance.
*/
Expand All @@ -136,11 +139,10 @@ public ClientTlsStrategyBuilder setHostnameVerifier(final HostnameVerifier hostn
/**
* Assigns {@link TlsDetails} {@link Factory} instance.
*
* @deprecated Do not use.
* @deprecated Do not use. This method has no effect.
*/
@Deprecated
public ClientTlsStrategyBuilder setTlsDetailsFactory(final Factory<SSLEngine, TlsDetails> tlsDetailsFactory) {
this.tlsDetailsFactory = tlsDetailsFactory;
return this;
}

Expand All @@ -153,7 +155,6 @@ public final ClientTlsStrategyBuilder useSystemProperties() {
return this;
}

@SuppressWarnings("deprecation")
public TlsStrategy build() {
final SSLContext sslContextCopy;
if (sslContext != null) {
Expand All @@ -173,13 +174,18 @@ public TlsStrategy build() {
} else {
ciphersCopy = systemProperties ? HttpsSupport.getSystemCipherSuits() : null;
}
final HostnameVerificationPolicy hostnameVerificationPolicyCopy = hostnameVerificationPolicy != null ? hostnameVerificationPolicy :
(hostnameVerifier == null ? HostnameVerificationPolicy.BUILTIN : HostnameVerificationPolicy.BOTH);
final HostnameVerifier hostnameVerifierCopy = hostnameVerifier != null ? hostnameVerifier :
(hostnameVerificationPolicyCopy == HostnameVerificationPolicy.CLIENT || hostnameVerificationPolicyCopy == HostnameVerificationPolicy.BOTH ?
HttpsSupport.getDefaultHostnameVerifier() : NoopHostnameVerifier.INSTANCE);
return new DefaultClientTlsStrategy(
sslContextCopy,
tlsVersionsCopy,
ciphersCopy,
sslBufferMode != null ? sslBufferMode : SSLBufferMode.STATIC,
hostnameVerifier != null ? hostnameVerifier : HttpsSupport.getDefaultHostnameVerifier(),
tlsDetailsFactory);
hostnameVerificationPolicyCopy,
hostnameVerifierCopy);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ public class ConscryptClientTlsStrategy extends AbstractClientTlsStrategy {
public static TlsStrategy getDefault() {
return new ConscryptClientTlsStrategy(
SSLContexts.createDefault(),
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}

Expand All @@ -63,6 +64,7 @@ public static TlsStrategy getSystemDefault() {
HttpsSupport.getSystemProtocols(),
HttpsSupport.getSystemCipherSuits(),
SSLBufferMode.STATIC,
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}

Expand All @@ -72,7 +74,20 @@ public ConscryptClientTlsStrategy(
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, hostnameVerifier);
this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
}

/**
* @since 5.4
*/
public ConscryptClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, hostnameVerificationPolicy, hostnameVerifier);
}

public ConscryptClientTlsStrategy(
Expand All @@ -81,6 +96,16 @@ public ConscryptClientTlsStrategy(
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerifier);
}

/**
* @since 5.4
*/
public ConscryptClientTlsStrategy(
final SSLContext sslContext,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerificationPolicy, hostnameVerifier);
}

public ConscryptClientTlsStrategy(final SSLContext sslContext) {
this(sslContext, HttpsSupport.getDefaultHostnameVerifier());
}
Expand Down
Loading

0 comments on commit 2e46b62

Please sign in to comment.