HTTPCLIENT-2151: Support for JSSE in-built endpoint identification

This commit is contained in:
Oleg Kalnichevski 2024-01-29 17:15:25 +01:00
parent e6e873d88b
commit 2e46b62ffd
6 changed files with 260 additions and 19 deletions

View File

@ -30,18 +30,24 @@ package org.apache.hc.client5.testing.sync;
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;
@ -54,6 +60,8 @@ import org.apache.hc.core5.io.CloseMode;
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;
@ -345,4 +353,111 @@ public class TestDefaultClientTlsStrategy {
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));
}
}
}

View File

@ -54,6 +54,7 @@ import org.apache.hc.core5.annotation.Contract;
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;
@ -79,6 +80,7 @@ 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(
@ -86,13 +88,16 @@ abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrate
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());
}
/**
@ -147,6 +152,10 @@ abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrate
applyParameters(sslEngine, sslParameters, H2TlsSupport.selectApplicationProtocols(versionPolicy));
if (hostnameVerificationPolicy == HostnameVerificationPolicy.BUILTIN || hostnameVerificationPolicy == HostnameVerificationPolicy.BOTH) {
sslParameters.setEndpointIdentificationAlgorithm(URIScheme.HTTPS.id);
}
initializeEngine(sslEngine);
if (LOG.isDebugEnabled()) {
@ -181,7 +190,8 @@ abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrate
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
@ -204,16 +214,23 @@ abstract class AbstractClientTlsStrategy implements TlsStrategy, TlsSocketStrate
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());

View File

@ -74,12 +74,8 @@ public class ClientTlsStrategyBuilder {
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;
/**
@ -125,6 +121,13 @@ public class ClientTlsStrategyBuilder {
return this;
}
/**
* Assigns {@link HostnameVerificationPolicy} value.
*/
public void setHostnameVerificationPolicy(final HostnameVerificationPolicy hostnameVerificationPolicy) {
this.hostnameVerificationPolicy = hostnameVerificationPolicy;
}
/**
* Assigns {@link HostnameVerifier} instance.
*/
@ -136,11 +139,10 @@ public class ClientTlsStrategyBuilder {
/**
* 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;
}
@ -153,7 +155,6 @@ public class ClientTlsStrategyBuilder {
return this;
}
@SuppressWarnings("deprecation")
public TlsStrategy build() {
final SSLContext sslContextCopy;
if (sslContext != null) {
@ -173,13 +174,18 @@ public class ClientTlsStrategyBuilder {
} 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);
}
}

View File

@ -54,6 +54,7 @@ public class ConscryptClientTlsStrategy extends AbstractClientTlsStrategy {
public static TlsStrategy getDefault() {
return new ConscryptClientTlsStrategy(
SSLContexts.createDefault(),
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
@ -63,6 +64,7 @@ public class ConscryptClientTlsStrategy extends AbstractClientTlsStrategy {
HttpsSupport.getSystemProtocols(),
HttpsSupport.getSystemCipherSuits(),
SSLBufferMode.STATIC,
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
@ -72,7 +74,20 @@ public class ConscryptClientTlsStrategy extends AbstractClientTlsStrategy {
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(
@ -81,6 +96,16 @@ public class ConscryptClientTlsStrategy extends AbstractClientTlsStrategy {
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());
}

View File

@ -54,6 +54,7 @@ public class DefaultClientTlsStrategy extends AbstractClientTlsStrategy {
public static DefaultClientTlsStrategy createDefault() {
return new DefaultClientTlsStrategy(
SSLContexts.createDefault(),
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
@ -66,6 +67,7 @@ public class DefaultClientTlsStrategy extends AbstractClientTlsStrategy {
HttpsSupport.getSystemProtocols(),
HttpsSupport.getSystemCipherSuits(),
SSLBufferMode.STATIC,
HostnameVerificationPolicy.BOTH,
HttpsSupport.getDefaultHostnameVerifier());
}
@ -102,17 +104,30 @@ public class DefaultClientTlsStrategy extends AbstractClientTlsStrategy {
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier,
final Factory<SSLEngine, TlsDetails> tlsDetailsFactory) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, hostnameVerifier);
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
this.tlsDetailsFactory = tlsDetailsFactory;
}
/**
* @since 5.4
*/
public DefaultClientTlsStrategy(
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 DefaultClientTlsStrategy(
final SSLContext sslContext,
final String[] supportedProtocols,
final String[] supportedCipherSuites,
final SSLBufferMode sslBufferManagement,
final HostnameVerifier hostnameVerifier) {
super(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, hostnameVerifier);
this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier);
}
public DefaultClientTlsStrategy(
@ -121,6 +136,16 @@ public class DefaultClientTlsStrategy extends AbstractClientTlsStrategy {
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerifier);
}
/**
* @since 5.4
*/
public DefaultClientTlsStrategy(
final SSLContext sslContext,
final HostnameVerificationPolicy hostnameVerificationPolicy,
final HostnameVerifier hostnameVerifier) {
this(sslContext, null, null, SSLBufferMode.STATIC, hostnameVerificationPolicy, hostnameVerifier);
}
public DefaultClientTlsStrategy(final SSLContext sslContext) {
this(sslContext, HttpsSupport.getDefaultHostnameVerifier());
}

View File

@ -0,0 +1,53 @@
/*
* ====================================================================
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*
*/
package org.apache.hc.client5.http.ssl;
/**
* Hostname verification policy.
*
* @see javax.net.ssl.HostnameVerifier
* @see DefaultHostnameVerifier
*
* @since 5.4
*/
public enum HostnameVerificationPolicy {
/**
* Hostname verification is delegated to the JSSE provider, usually executed during the TLS handshake.
*/
BUILTIN,
/**
* Hostname verification is executed by HttpClient post TLS handshake.
*/
CLIENT,
/**
* Hostname verification is executed by the JSSE provider and by HttpClient post TLS handshake.
*/
BOTH
}