diff --git a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java index 01d80a275..d5c76b566 100644 --- a/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java +++ b/httpclient5-testing/src/test/java/org/apache/hc/client5/testing/sync/TestDefaultClientTlsStrategy.java @@ -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; @@ -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; @@ -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)); + } + } + } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java index b52e63649..85ec626da 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/AbstractClientTlsStrategy.java @@ -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; @@ -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 @@ 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()) { @@ -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 @@ -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()); diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ClientTlsStrategyBuilder.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ClientTlsStrategyBuilder.java index 3f4e35af0..2f1f4b414 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ClientTlsStrategyBuilder.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ClientTlsStrategyBuilder.java @@ -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 tlsDetailsFactory; private boolean systemProperties; /** @@ -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. */ @@ -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 tlsDetailsFactory) { - this.tlsDetailsFactory = tlsDetailsFactory; return this; } @@ -153,7 +155,6 @@ public final ClientTlsStrategyBuilder useSystemProperties() { return this; } - @SuppressWarnings("deprecation") public TlsStrategy build() { final SSLContext sslContextCopy; if (sslContext != null) { @@ -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); } } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ConscryptClientTlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ConscryptClientTlsStrategy.java index 465a8f258..eb2c5bdae 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ConscryptClientTlsStrategy.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/ConscryptClientTlsStrategy.java @@ -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 static TlsStrategy getSystemDefault() { HttpsSupport.getSystemProtocols(), HttpsSupport.getSystemCipherSuits(), SSLBufferMode.STATIC, + HostnameVerificationPolicy.BOTH, HttpsSupport.getDefaultHostnameVerifier()); } @@ -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( @@ -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()); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultClientTlsStrategy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultClientTlsStrategy.java index 861b4c5f9..7a74db5c4 100644 --- a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultClientTlsStrategy.java +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/DefaultClientTlsStrategy.java @@ -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 static DefaultClientTlsStrategy createSystemDefault() { HttpsSupport.getSystemProtocols(), HttpsSupport.getSystemCipherSuits(), SSLBufferMode.STATIC, + HostnameVerificationPolicy.BOTH, HttpsSupport.getDefaultHostnameVerifier()); } @@ -102,17 +104,30 @@ public DefaultClientTlsStrategy( final SSLBufferMode sslBufferManagement, final HostnameVerifier hostnameVerifier, final Factory 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, 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) { + this(sslContext, supportedProtocols, supportedCipherSuites, sslBufferManagement, HostnameVerificationPolicy.CLIENT, hostnameVerifier); } public DefaultClientTlsStrategy( @@ -121,6 +136,16 @@ public DefaultClientTlsStrategy( 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()); } diff --git a/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/HostnameVerificationPolicy.java b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/HostnameVerificationPolicy.java new file mode 100644 index 000000000..6231acdcd --- /dev/null +++ b/httpclient5/src/main/java/org/apache/hc/client5/http/ssl/HostnameVerificationPolicy.java @@ -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 + * . + * + */ + +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 + +}