HTTPCLIENT-1792: SSLConnectionSocketFactory to throw SSLPeerUnverifiedException with a better error message when hostname verification fails

git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/branches/4.5.x@1778409 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Oleg Kalnichevski 2017-01-12 10:07:29 +00:00
parent 13810dd84c
commit bc5d688a4f
3 changed files with 50 additions and 29 deletions

View File

@ -132,9 +132,14 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
@Override @Override
public final void verify( public final void verify(
final String host, final X509Certificate cert) throws SSLException { final String host, final X509Certificate cert) throws SSLException {
final boolean ipv4 = InetAddressUtils.isIPv4Address(host); final int subjectType;
final boolean ipv6 = InetAddressUtils.isIPv6Address(host); if (InetAddressUtils.isIPv4Address(host)) {
final int subjectType = ipv4 || ipv6 ? DefaultHostnameVerifier.IP_ADDRESS_TYPE : DefaultHostnameVerifier.DNS_NAME_TYPE; subjectType = DefaultHostnameVerifier.HostNameType.IPv4.subjectType;
} else if (InetAddressUtils.isIPv6Address(host)) {
subjectType = DefaultHostnameVerifier.HostNameType.IPv6.subjectType;
} else {
subjectType = DefaultHostnameVerifier.HostNameType.DNS.subjectType;
}
final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(cert, subjectType); final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(cert, subjectType);
final X500Principal subjectPrincipal = cert.getSubjectX500Principal(); final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
final String cn = DefaultHostnameVerifier.extractCN(subjectPrincipal.getName(X500Principal.RFC2253)); final String cn = DefaultHostnameVerifier.extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
@ -246,7 +251,7 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
*/ */
public static String[] getDNSSubjectAlts(final X509Certificate cert) { public static String[] getDNSSubjectAlts(final X509Certificate cert) {
final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts( final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(
cert, DefaultHostnameVerifier.DNS_NAME_TYPE); cert, DefaultHostnameVerifier.HostNameType.DNS.subjectType);
return subjectAlts != null && !subjectAlts.isEmpty() ? return subjectAlts != null && !subjectAlts.isEmpty() ?
subjectAlts.toArray(new String[subjectAlts.size()]) : null; subjectAlts.toArray(new String[subjectAlts.size()]) : null;
} }

View File

@ -46,6 +46,7 @@ import javax.naming.ldap.LdapName;
import javax.naming.ldap.Rdn; import javax.naming.ldap.Rdn;
import javax.net.ssl.HostnameVerifier; import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLException; import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession; import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal; import javax.security.auth.x500.X500Principal;
@ -65,10 +66,17 @@ import org.apache.http.conn.util.PublicSuffixMatcher;
@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL) @Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
public final class DefaultHostnameVerifier implements HostnameVerifier { public final class DefaultHostnameVerifier implements HostnameVerifier {
enum TYPE { IPv4, IPv6, DNS } enum HostNameType {
final static int DNS_NAME_TYPE = 2; IPv4(7), IPv6(7), DNS(2);
final static int IP_ADDRESS_TYPE = 7;
final int subjectType;
HostNameType(final int subjectType) {
this.subjectType = subjectType;
}
}
private final Log log = LogFactory.getLog(getClass()); private final Log log = LogFactory.getLog(getClass());
@ -99,22 +107,10 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
public void verify( public void verify(
final String host, final X509Certificate cert) throws SSLException { final String host, final X509Certificate cert) throws SSLException {
TYPE hostFormat = TYPE.DNS; final HostNameType hostType = determineHostFormat(host);
if (InetAddressUtils.isIPv4Address(host)) { final List<String> subjectAlts = extractSubjectAlts(cert, hostType.subjectType);
hostFormat = TYPE.IPv4;
} else {
String s = host;
if (s.startsWith("[") && s.endsWith("]")) {
s = host.substring(1, host.length() - 1);
}
if (InetAddressUtils.isIPv6Address(s)) {
hostFormat = TYPE.IPv6;
}
}
final int subjectType = hostFormat == TYPE.IPv4 || hostFormat == TYPE.IPv6 ? IP_ADDRESS_TYPE : DNS_NAME_TYPE;
final List<String> subjectAlts = extractSubjectAlts(cert, subjectType);
if (subjectAlts != null && !subjectAlts.isEmpty()) { if (subjectAlts != null && !subjectAlts.isEmpty()) {
switch (hostFormat) { switch (hostType) {
case IPv4: case IPv4:
matchIPAddress(host, subjectAlts); matchIPAddress(host, subjectAlts);
break; break;
@ -144,7 +140,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
return; return;
} }
} }
throw new SSLException("Certificate for <" + host + "> doesn't match any " + throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
"of the subject alternative names: " + subjectAlts); "of the subject alternative names: " + subjectAlts);
} }
@ -157,7 +153,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
return; return;
} }
} }
throw new SSLException("Certificate for <" + host + "> doesn't match any " + throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
"of the subject alternative names: " + subjectAlts); "of the subject alternative names: " + subjectAlts);
} }
@ -171,7 +167,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
return; return;
} }
} }
throw new SSLException("Certificate for <" + host + "> doesn't match any " + throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match any " +
"of the subject alternative names: " + subjectAlts); "of the subject alternative names: " + subjectAlts);
} }
@ -180,7 +176,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
final String normalizedHost = host.toLowerCase(Locale.ROOT); final String normalizedHost = host.toLowerCase(Locale.ROOT);
final String normalizedCn = cn.toLowerCase(Locale.ROOT); final String normalizedCn = cn.toLowerCase(Locale.ROOT);
if (!matchIdentityStrict(normalizedHost, normalizedCn, publicSuffixMatcher)) { if (!matchIdentityStrict(normalizedHost, normalizedCn, publicSuffixMatcher)) {
throw new SSLException("Certificate for <" + host + "> doesn't match " + throw new SSLPeerUnverifiedException("Certificate for <" + host + "> doesn't match " +
"common name of the certificate subject: " + cn); "common name of the certificate subject: " + cn);
} }
} }
@ -278,6 +274,21 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
} }
} }
static HostNameType determineHostFormat(final String host) {
if (InetAddressUtils.isIPv4Address(host)) {
return HostNameType.IPv4;
} else {
String s = host;
if (s.startsWith("[") && s.endsWith("]")) {
s = host.substring(1, host.length() - 1);
}
if (InetAddressUtils.isIPv6Address(s)) {
return HostNameType.IPv6;
}
}
return HostNameType.DNS;
}
static List<String> extractSubjectAlts(final X509Certificate cert, final int subjectType) { static List<String> extractSubjectAlts(final X509Certificate cert, final int subjectType) {
Collection<List<?>> c = null; Collection<List<?>> c = null;
try { try {
@ -302,6 +313,11 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
return subjectAltList; return subjectAltList;
} }
static List<String> extractSubjectAlts(final String host, final X509Certificate cert) {
final HostNameType hostType = determineHostFormat(host);
return extractSubjectAlts(cert, hostType.subjectType);
}
/* /*
* Normalize IPv6 or DNS name. * Normalize IPv6 or DNS name.
*/ */

View File

@ -463,9 +463,9 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
if (!this.hostnameVerifier.verify(hostname, session)) { if (!this.hostnameVerifier.verify(hostname, session)) {
final Certificate[] certs = session.getPeerCertificates(); final Certificate[] certs = session.getPeerCertificates();
final X509Certificate x509 = (X509Certificate) certs[0]; final X509Certificate x509 = (X509Certificate) certs[0];
final X500Principal x500Principal = x509.getSubjectX500Principal(); final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(hostname, x509);
throw new SSLPeerUnverifiedException("Host name '" + hostname + "' does not match " + throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
"the certificate subject provided by the peer (" + x500Principal.toString() + ")"); "of the subject alternative names: " + subjectAlts);
} }
// verifyHostName() didn't blowup - good! // verifyHostName() didn't blowup - good!
} catch (final IOException iox) { } catch (final IOException iox) {