From f5bf414adcbeef7c7eb2c2d70ce2fcc594e6f955 Mon Sep 17 00:00:00 2001 From: Oleg Kalnichevski Date: Fri, 8 Aug 2014 14:00:55 +0000 Subject: [PATCH] Deprecated X509HostnameVerifier interface in favor of standard javax.net.ssl.HostnameVerifier git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1616758 13f79535-47bb-0310-9956-ffa450edef68 --- .../examples/client/ClientConfiguration.java | 8 +- .../http/examples/client/ClientCustomSSL.java | 4 +- .../ssl/AbstractBaseHostnameVerifier.java | 112 -------------- .../ssl/AbstractCommonHostnameVerifier.java | 27 +++- .../http/conn/ssl/AbstractVerifier.java | 59 +++++++- .../conn/ssl/AllowAllHostnameVerifier.java | 3 + .../http/conn/ssl/NoopHostnameVerifier.java | 56 +++++++ .../conn/ssl/SSLConnectionSocketFactory.java | 138 +++++++++++++++--- .../http/conn/ssl/X509HostnameVerifier.java | 3 + .../http/impl/client/HttpClientBuilder.java | 24 ++- .../http/conn/ssl/TestSSLSocketFactory.java | 30 +--- 11 files changed, 290 insertions(+), 174 deletions(-) delete mode 100644 httpclient/src/main/java/org/apache/http/conn/ssl/AbstractBaseHostnameVerifier.java create mode 100644 httpclient/src/main/java/org/apache/http/conn/ssl/NoopHostnameVerifier.java diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientConfiguration.java b/httpclient/src/examples/org/apache/http/examples/client/ClientConfiguration.java index 4c1c58f8c..b4252c6d5 100644 --- a/httpclient/src/examples/org/apache/http/examples/client/ClientConfiguration.java +++ b/httpclient/src/examples/org/apache/http/examples/client/ClientConfiguration.java @@ -60,18 +60,16 @@ import org.apache.http.conn.ManagedHttpClientConnection; import org.apache.http.conn.routing.HttpRoute; import org.apache.http.conn.socket.ConnectionSocketFactory; import org.apache.http.conn.socket.PlainConnectionSocketFactory; -import org.apache.http.conn.ssl.BrowserCompatHostnameVerifier; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; import org.apache.http.conn.ssl.SSLContexts; -import org.apache.http.conn.ssl.X509HostnameVerifier; import org.apache.http.impl.DefaultHttpResponseFactory; import org.apache.http.impl.client.BasicCookieStore; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; -import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; import org.apache.http.impl.conn.DefaultHttpResponseParser; import org.apache.http.impl.conn.DefaultHttpResponseParserFactory; +import org.apache.http.impl.conn.ManagedHttpClientConnectionFactory; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.apache.http.impl.conn.SystemDefaultDnsResolver; import org.apache.http.impl.io.DefaultHttpRequestWriterFactory; @@ -141,14 +139,12 @@ public class ClientConfiguration { // SSL context for secure connections can be created either based on // system or application specific properties. SSLContext sslcontext = SSLContexts.createSystemDefault(); - // Use custom hostname verifier to customize SSL hostname verification. - X509HostnameVerifier hostnameVerifier = new BrowserCompatHostnameVerifier(); // Create a registry of custom connection socket factories for supported // protocol schemes. Registry socketFactoryRegistry = RegistryBuilder.create() .register("http", PlainConnectionSocketFactory.INSTANCE) - .register("https", new SSLConnectionSocketFactory(sslcontext, hostnameVerifier)) + .register("https", new SSLConnectionSocketFactory(sslcontext)) .build(); // Use custom DNS resolver to override the system DNS resolution. diff --git a/httpclient/src/examples/org/apache/http/examples/client/ClientCustomSSL.java b/httpclient/src/examples/org/apache/http/examples/client/ClientCustomSSL.java index fd581b0ec..ec3a565f3 100644 --- a/httpclient/src/examples/org/apache/http/examples/client/ClientCustomSSL.java +++ b/httpclient/src/examples/org/apache/http/examples/client/ClientCustomSSL.java @@ -35,8 +35,8 @@ import javax.net.ssl.SSLContext; import org.apache.http.HttpEntity; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; -import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.SSLConnectionSocketFactory; +import org.apache.http.conn.ssl.SSLContexts; import org.apache.http.conn.ssl.TrustSelfSignedStrategy; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClients; @@ -66,7 +66,7 @@ public class ClientCustomSSL { sslcontext, new String[] { "TLSv1" }, null, - SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + SSLConnectionSocketFactory.getDefaultHostnameVerifier()); CloseableHttpClient httpclient = HttpClients.custom() .setSSLSocketFactory(sslsf) .build(); diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractBaseHostnameVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractBaseHostnameVerifier.java deleted file mode 100644 index 0a6d9483b..000000000 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractBaseHostnameVerifier.java +++ /dev/null @@ -1,112 +0,0 @@ -/* - * ==================================================================== - * 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.http.conn.ssl; - -import java.io.IOException; -import java.io.InputStream; -import java.security.cert.Certificate; -import java.security.cert.X509Certificate; - -import javax.net.ssl.SSLException; -import javax.net.ssl.SSLSession; -import javax.net.ssl.SSLSocket; - -import org.apache.http.annotation.Immutable; -import org.apache.http.util.Args; - -/** - * Abstract base class for all standard {@link org.apache.http.conn.ssl.X509HostnameVerifier} - * implementations that provides common methods extracting - * {@link java.security.cert.X509Certificate} instance to be verified from either - * {@link javax.net.ssl.SSLSocket} or {@link javax.net.ssl.SSLSession}. - * - * @since 4.4 - */ -@Immutable -public abstract class AbstractBaseHostnameVerifier implements X509HostnameVerifier { - - @Override - public final void verify(final String host, final SSLSocket ssl) - throws IOException { - Args.notNull(host, "Host"); - SSLSession session = ssl.getSession(); - if(session == null) { - // In our experience this only happens under IBM 1.4.x when - // spurious (unrelated) certificates show up in the server' - // chain. Hopefully this will unearth the real problem: - final InputStream in = ssl.getInputStream(); - in.available(); - /* - If you're looking at the 2 lines of code above because - you're running into a problem, you probably have two - options: - - #1. Clean up the certificate chain that your server - is presenting (e.g. edit "/etc/apache2/server.crt" - or wherever it is your server's certificate chain - is defined). - - OR - - #2. Upgrade to an IBM 1.5.x or greater JVM, or switch - to a non-IBM JVM. - */ - - // If ssl.getInputStream().available() didn't cause an - // exception, maybe at least now the session is available? - session = ssl.getSession(); - if(session == null) { - // If it's still null, probably a startHandshake() will - // unearth the real problem. - ssl.startHandshake(); - - // Okay, if we still haven't managed to cause an exception, - // might as well go for the NPE. Or maybe we're okay now? - session = ssl.getSession(); - } - } - - final Certificate[] certs = session.getPeerCertificates(); - final X509Certificate x509 = (X509Certificate) certs[0]; - verify(host, x509); - } - - @Override - public final boolean verify(final String host, final SSLSession session) { - try { - final Certificate[] certs = session.getPeerCertificates(); - final X509Certificate x509 = (X509Certificate) certs[0]; - verify(host, x509); - return true; - } - catch(final SSLException e) { - return false; - } - } - -} diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractCommonHostnameVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractCommonHostnameVerifier.java index 86323934e..79e16e77b 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractCommonHostnameVerifier.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractCommonHostnameVerifier.java @@ -29,6 +29,7 @@ package org.apache.http.conn.ssl; import java.net.InetAddress; import java.net.UnknownHostException; +import java.security.cert.Certificate; import java.security.cert.CertificateParsingException; import java.security.cert.X509Certificate; import java.util.ArrayList; @@ -46,7 +47,9 @@ import javax.naming.directory.Attribute; import javax.naming.directory.Attributes; import javax.naming.ldap.LdapName; import javax.naming.ldap.Rdn; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; @@ -54,7 +57,7 @@ import org.apache.http.annotation.Immutable; import org.apache.http.conn.util.InetAddressUtils; /** - * Abstract base class for all standard {@link org.apache.http.conn.ssl.X509HostnameVerifier} + * Abstract base class for all standard {@link javax.net.ssl.HostnameVerifier} * implementations that provides methods to extract Common Name (CN) and alternative subjects * (subjectAlt) from {@link java.security.cert.X509Certificate} being validated as well * as {@link #verify(String, String[], String[], boolean)} method that implements common @@ -63,7 +66,7 @@ import org.apache.http.conn.util.InetAddressUtils; * @since 4.4 */ @Immutable -public abstract class AbstractCommonHostnameVerifier extends AbstractBaseHostnameVerifier { +public abstract class AbstractCommonHostnameVerifier implements HostnameVerifier { /** * This contains a list of 2nd-level domains that aren't allowed to @@ -87,14 +90,30 @@ public abstract class AbstractCommonHostnameVerifier extends AbstractBaseHostnam private final Log log = LogFactory.getLog(getClass()); @Override - public final void verify(final String host, final X509Certificate cert) - throws SSLException { + public final boolean verify(final String host, final SSLSession session) { + try { + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + verify(host, x509); + return true; + } catch(final SSLException ex) { + if (log.isDebugEnabled()) { + log.debug(ex.getMessage(), ex); + } + return false; + } + } + + public final void verify( + final String host, final X509Certificate cert) throws SSLException { final String subjectPrincipal = cert.getSubjectX500Principal().toString(); final String[] cns = extractCNs(subjectPrincipal); final String[] subjectAlts = extractSubjectAlts(cert, host); verify(host, cns, subjectAlts); } + public abstract void verify(String host, String[] cns, String[] subjectAlts) throws SSLException; + public final void verify(final String host, final String[] cns, final String[] subjectAlts, final boolean strictWithSubDomains) diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java index 3531f862d..4e98c0e11 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/AbstractVerifier.java @@ -27,9 +27,16 @@ package org.apache.http.conn.ssl; +import java.io.IOException; +import java.io.InputStream; +import java.security.cert.Certificate; import java.security.cert.X509Certificate; import javax.net.ssl.SSLException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; + +import org.apache.http.util.Args; /** * Abstract base class for all standard {@link X509HostnameVerifier} @@ -37,11 +44,57 @@ import javax.net.ssl.SSLException; * * @since 4.0 * - * @deprecated (4.4) use {@link AbstractBaseHostnameVerifier} or - * {@link org.apache.http.conn.ssl.AbstractCommonHostnameVerifier} + * @deprecated (4.4) use {@link javax.net.ssl.HostnameVerifier} or + * {@link org.apache.http.conn.ssl.AbstractCommonHostnameVerifier}. */ @Deprecated -public abstract class AbstractVerifier extends AbstractCommonHostnameVerifier { +public abstract class AbstractVerifier extends AbstractCommonHostnameVerifier implements X509HostnameVerifier { + + @Override + public final void verify(final String host, final SSLSocket ssl) + throws IOException { + Args.notNull(host, "Host"); + SSLSession session = ssl.getSession(); + if(session == null) { + // In our experience this only happens under IBM 1.4.x when + // spurious (unrelated) certificates show up in the server' + // chain. Hopefully this will unearth the real problem: + final InputStream in = ssl.getInputStream(); + in.available(); + /* + If you're looking at the 2 lines of code above because + you're running into a problem, you probably have two + options: + + #1. Clean up the certificate chain that your server + is presenting (e.g. edit "/etc/apache2/server.crt" + or wherever it is your server's certificate chain + is defined). + + OR + + #2. Upgrade to an IBM 1.5.x or greater JVM, or switch + to a non-IBM JVM. + */ + + // If ssl.getInputStream().available() didn't cause an + // exception, maybe at least now the session is available? + session = ssl.getSession(); + if(session == null) { + // If it's still null, probably a startHandshake() will + // unearth the real problem. + ssl.startHandshake(); + + // Okay, if we still haven't managed to cause an exception, + // might as well go for the NPE. Or maybe we're okay now? + session = ssl.getSession(); + } + } + + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + verify(host, x509); + } public static String[] getCNs(final X509Certificate cert) { final String subjectPrincipal = cert.getSubjectX500Principal().toString(); diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java index 1e2ec8ca5..3dc856ed7 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/AllowAllHostnameVerifier.java @@ -35,7 +35,10 @@ import org.apache.http.annotation.Immutable; * * * @since 4.0 + * + * @deprecated (4.4) Use {@link org.apache.http.conn.ssl.NoopHostnameVerifier} */ +@Deprecated @Immutable public class AllowAllHostnameVerifier extends AbstractVerifier { diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/NoopHostnameVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/NoopHostnameVerifier.java new file mode 100644 index 000000000..1b5cdd954 --- /dev/null +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/NoopHostnameVerifier.java @@ -0,0 +1,56 @@ +/* + * ==================================================================== + * 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.http.conn.ssl; + +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLSession; + +import org.apache.http.annotation.Immutable; + +/** + * The NO_OP HostnameVerifier essentially turns hostname verification + * off. This implementation is a no-op, and never throws the SSLException. + * + * @since 4.4 + */ +@Immutable +public class NoopHostnameVerifier implements HostnameVerifier { + + public static final NoopHostnameVerifier INSTANCE = new NoopHostnameVerifier(); + + @Override + public boolean verify(final String s, final SSLSession sslSession) { + return true; + } + + @Override + public final String toString() { + return "NO_OP"; + } + +} diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLConnectionSocketFactory.java b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLConnectionSocketFactory.java index 064dca43b..12cfdac40 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/SSLConnectionSocketFactory.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/SSLConnectionSocketFactory.java @@ -27,6 +27,22 @@ package org.apache.http.conn.ssl; +import java.io.IOException; +import java.io.InputStream; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.security.cert.Certificate; +import java.security.cert.X509Certificate; + +import javax.net.SocketFactory; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLHandshakeException; +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSession; +import javax.net.ssl.SSLSocket; +import javax.security.auth.x500.X500Principal; + import org.apache.http.HttpHost; import org.apache.http.annotation.ThreadSafe; import org.apache.http.conn.socket.LayeredConnectionSocketFactory; @@ -34,13 +50,6 @@ import org.apache.http.protocol.HttpContext; import org.apache.http.util.Args; import org.apache.http.util.TextUtils; -import javax.net.SocketFactory; -import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLSocket; -import java.io.IOException; -import java.net.InetSocketAddress; -import java.net.Socket; - /** * Layered socket factory for TLS/SSL connections. *

@@ -121,15 +130,25 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor public static final String SSL = "SSL"; public static final String SSLV2 = "SSLv2"; + @Deprecated public static final X509HostnameVerifier ALLOW_ALL_HOSTNAME_VERIFIER = AllowAllHostnameVerifier.INSTANCE; + @Deprecated public static final X509HostnameVerifier BROWSER_COMPATIBLE_HOSTNAME_VERIFIER = BrowserCompatHostnameVerifier.INSTANCE; + @Deprecated public static final X509HostnameVerifier STRICT_HOSTNAME_VERIFIER = StrictHostnameVerifier.INSTANCE; + /** + * @since 4.4 + */ + public static HostnameVerifier getDefaultHostnameVerifier() { + return BrowserCompatHostnameVerifier.INSTANCE; + } + /** * Obtains default SSL socket factory with an SSL context based on the standard JSSE * trust material (cacerts file in the security properties directory). @@ -138,9 +157,7 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor * @return default SSL socket factory */ public static SSLConnectionSocketFactory getSocketFactory() throws SSLInitializationException { - return new SSLConnectionSocketFactory( - SSLContexts.createDefault(), - BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + return new SSLConnectionSocketFactory(SSLContexts.createDefault(), getDefaultHostnameVerifier()); } private static String[] split(final String s) { @@ -164,24 +181,34 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor (javax.net.ssl.SSLSocketFactory) javax.net.ssl.SSLSocketFactory.getDefault(), split(System.getProperty("https.protocols")), split(System.getProperty("https.cipherSuites")), - BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + getDefaultHostnameVerifier()); } private final javax.net.ssl.SSLSocketFactory socketfactory; - private final X509HostnameVerifier hostnameVerifier; + private final HostnameVerifier hostnameVerifier; private final String[] supportedProtocols; private final String[] supportedCipherSuites; public SSLConnectionSocketFactory(final SSLContext sslContext) { - this(sslContext, BROWSER_COMPATIBLE_HOSTNAME_VERIFIER); + this(sslContext, getDefaultHostnameVerifier()); } + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext, + * javax.net.ssl.HostnameVerifier)} + */ + @Deprecated public SSLConnectionSocketFactory( final SSLContext sslContext, final X509HostnameVerifier hostnameVerifier) { this(Args.notNull(sslContext, "SSL context").getSocketFactory(), null, null, hostnameVerifier); } + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLContext, + * String[], String[], javax.net.ssl.HostnameVerifier)} + */ + @Deprecated public SSLConnectionSocketFactory( final SSLContext sslContext, final String[] supportedProtocols, @@ -191,21 +218,72 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor supportedProtocols, supportedCipherSuites, hostnameVerifier); } + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, + * javax.net.ssl.HostnameVerifier)} + */ + @Deprecated public SSLConnectionSocketFactory( final javax.net.ssl.SSLSocketFactory socketfactory, final X509HostnameVerifier hostnameVerifier) { this(socketfactory, null, null, hostnameVerifier); } + /** + * @deprecated (4.4) Use {@link #SSLConnectionSocketFactory(javax.net.ssl.SSLSocketFactory, + * String[], String[], javax.net.ssl.HostnameVerifier)} + */ + @Deprecated public SSLConnectionSocketFactory( final javax.net.ssl.SSLSocketFactory socketfactory, final String[] supportedProtocols, final String[] supportedCipherSuites, final X509HostnameVerifier hostnameVerifier) { + this(socketfactory, supportedProtocols, supportedCipherSuites, (HostnameVerifier) hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final SSLContext sslContext, final HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + null, null, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final SSLContext sslContext, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { + this(Args.notNull(sslContext, "SSL context").getSocketFactory(), + supportedProtocols, supportedCipherSuites, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final HostnameVerifier hostnameVerifier) { + this(socketfactory, null, null, hostnameVerifier); + } + + /** + * @since 4.4 + */ + public SSLConnectionSocketFactory( + final javax.net.ssl.SSLSocketFactory socketfactory, + final String[] supportedProtocols, + final String[] supportedCipherSuites, + final HostnameVerifier hostnameVerifier) { this.socketfactory = Args.notNull(socketfactory, "SSL socket factory"); this.supportedProtocols = supportedProtocols; this.supportedCipherSuites = supportedCipherSuites; - this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + this.hostnameVerifier = hostnameVerifier != null ? hostnameVerifier : getDefaultHostnameVerifier(); } /** @@ -281,13 +359,35 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor return sslsock; } - X509HostnameVerifier getHostnameVerifier() { - return this.hostnameVerifier; - } - private void verifyHostname(final SSLSocket sslsock, final String hostname) throws IOException { try { - this.hostnameVerifier.verify(hostname, sslsock); + SSLSession session = sslsock.getSession(); + if (session == null) { + // In our experience this only happens under IBM 1.4.x when + // spurious (unrelated) certificates show up in the server' + // chain. Hopefully this will unearth the real problem: + final InputStream in = sslsock.getInputStream(); + in.available(); + // If ssl.getInputStream().available() didn't cause an + // exception, maybe at least now the session is available? + session = sslsock.getSession(); + if (session == null) { + // If it's still null, probably a startHandshake() will + // unearth the real problem. + sslsock.startHandshake(); + session = sslsock.getSession(); + } + } + if (session == null) { + throw new SSLHandshakeException("SSL session not available"); + } + if (!this.hostnameVerifier.verify(hostname, session)) { + final Certificate[] certs = session.getPeerCertificates(); + final X509Certificate x509 = (X509Certificate) certs[0]; + final X500Principal x500Principal = x509.getSubjectX500Principal(); + throw new SSLPeerUnverifiedException("Host name '" + hostname + "' does not match " + + "the certificate subject provided by the peer (" + x500Principal.toString() + ")"); + } // verifyHostName() didn't blowup - good! } catch (final IOException iox) { // close the socket before re-throwing the exception diff --git a/httpclient/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java b/httpclient/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java index 55fa88b42..8bca83aae 100644 --- a/httpclient/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java +++ b/httpclient/src/main/java/org/apache/http/conn/ssl/X509HostnameVerifier.java @@ -41,7 +41,10 @@ import javax.net.ssl.SSLSocket; * methods added by X509HostnameVerifier. * * @since 4.0 + * + * @deprecated (4.4) Use {@link javax.net.ssl.HostnameVerifier}. */ +@Deprecated public interface X509HostnameVerifier extends HostnameVerifier { /** diff --git a/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java b/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java index fa36b9f1f..134e58085 100644 --- a/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java +++ b/httpclient/src/main/java/org/apache/http/impl/client/HttpClientBuilder.java @@ -38,6 +38,7 @@ import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; import javax.net.ssl.SSLSocketFactory; @@ -158,7 +159,7 @@ import org.apache.http.util.VersionInfo; public class HttpClientBuilder { private HttpRequestExecutor requestExec; - private X509HostnameVerifier hostnameVerifier; + private HostnameVerifier hostnameVerifier; private LayeredConnectionSocketFactory sslSocketFactory; private SSLContext sslcontext; private HttpClientConnectionManager connManager; @@ -232,12 +233,29 @@ public class HttpClientBuilder { * Please note this value can be overridden by the {@link #setConnectionManager( * org.apache.http.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( * org.apache.http.conn.socket.LayeredConnectionSocketFactory)} methods. + * + * @deprecated (4.4) */ + @Deprecated public final HttpClientBuilder setHostnameVerifier(final X509HostnameVerifier hostnameVerifier) { this.hostnameVerifier = hostnameVerifier; return this; } + /** + * Assigns {@link javax.net.ssl.HostnameVerifier} instance. + *

+ * Please note this value can be overridden by the {@link #setConnectionManager( + * org.apache.http.conn.HttpClientConnectionManager)} and the {@link #setSSLSocketFactory( + * org.apache.http.conn.socket.LayeredConnectionSocketFactory)} methods. + * + * @since 4.4 + */ + public final HttpClientBuilder setSSLHostnameVerifier(final HostnameVerifier hostnameVerifier) { + this.hostnameVerifier = hostnameVerifier; + return this; + } + /** * Assigns {@link SSLContext} instance. *

@@ -780,9 +798,9 @@ public class HttpClientBuilder { System.getProperty("https.protocols")) : null; final String[] supportedCipherSuites = systemProperties ? split( System.getProperty("https.cipherSuites")) : null; - X509HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier; + HostnameVerifier hostnameVerifierCopy = this.hostnameVerifier; if (hostnameVerifierCopy == null) { - hostnameVerifierCopy = SSLConnectionSocketFactory.BROWSER_COMPATIBLE_HOSTNAME_VERIFIER; + hostnameVerifierCopy = SSLConnectionSocketFactory.getDefaultHostnameVerifier(); } if (sslcontext != null) { sslSocketFactoryCopy = new SSLConnectionSocketFactory( diff --git a/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java b/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java index d628cae9e..153e69366 100644 --- a/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java +++ b/httpclient/src/test/java/org/apache/http/conn/ssl/TestSSLSocketFactory.java @@ -35,8 +35,8 @@ import java.security.cert.X509Certificate; import java.util.Map; import java.util.concurrent.TimeUnit; +import javax.net.ssl.HostnameVerifier; import javax.net.ssl.SSLContext; -import javax.net.ssl.SSLException; import javax.net.ssl.SSLHandshakeException; import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSocket; @@ -67,26 +67,14 @@ public class TestSSLSocketFactory { } } - static class TestX509HostnameVerifier implements X509HostnameVerifier { + static class TestX509HostnameVerifier implements HostnameVerifier { private boolean fired = false; @Override public boolean verify(final String host, final SSLSession session) { - return true; - } - - @Override - public void verify(final String host, final SSLSocket ssl) throws IOException { this.fired = true; - } - - @Override - public void verify(final String host, final String[] cns, final String[] subjectAlts) throws SSLException { - } - - @Override - public void verify(final String host, final X509Certificate cert) throws SSLException { + return true; } public boolean isFired() { @@ -227,7 +215,7 @@ public class TestSSLSocketFactory { final SSLContext defaultsslcontext = SSLContexts.createDefault(); final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory(defaultsslcontext, - SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + NoopHostnameVerifier.INSTANCE); final Socket socket = socketFactory.createSocket(context); final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort()); @@ -260,7 +248,7 @@ public class TestSSLSocketFactory { .build(); final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( sslcontext, - SSLConnectionSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + NoopHostnameVerifier.INSTANCE); final Socket socket = socketFactory.createSocket(context); final InetSocketAddress remoteAddress = new InetSocketAddress("localhost", this.server.getLocalPort()); @@ -269,12 +257,4 @@ public class TestSSLSocketFactory { sslSocket.close(); } - @Test - public void testDefaultHostnameVerifier() throws Exception { - final SSLConnectionSocketFactory socketFactory = new SSLConnectionSocketFactory( - SSLContexts.createDefault(), - null); - Assert.assertNotNull(socketFactory.getHostnameVerifier()); - } - }