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
public final void verify(
final String host, final X509Certificate cert) throws SSLException {
final boolean ipv4 = InetAddressUtils.isIPv4Address(host);
final boolean ipv6 = InetAddressUtils.isIPv6Address(host);
final int subjectType = ipv4 || ipv6 ? DefaultHostnameVerifier.IP_ADDRESS_TYPE : DefaultHostnameVerifier.DNS_NAME_TYPE;
final int subjectType;
if (InetAddressUtils.isIPv4Address(host)) {
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 X500Principal subjectPrincipal = cert.getSubjectX500Principal();
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) {
final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(
cert, DefaultHostnameVerifier.DNS_NAME_TYPE);
cert, DefaultHostnameVerifier.HostNameType.DNS.subjectType);
return subjectAlts != null && !subjectAlts.isEmpty() ?
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.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.security.auth.x500.X500Principal;
@ -65,10 +66,17 @@ import org.apache.http.conn.util.PublicSuffixMatcher;
@Contract(threading = ThreadingBehavior.IMMUTABLE_CONDITIONAL)
public final class DefaultHostnameVerifier implements HostnameVerifier {
enum TYPE { IPv4, IPv6, DNS }
enum HostNameType {
final static int DNS_NAME_TYPE = 2;
final static int IP_ADDRESS_TYPE = 7;
IPv4(7), IPv6(7), DNS(2);
final int subjectType;
HostNameType(final int subjectType) {
this.subjectType = subjectType;
}
}
private final Log log = LogFactory.getLog(getClass());
@ -99,22 +107,10 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
public void verify(
final String host, final X509Certificate cert) throws SSLException {
TYPE hostFormat = TYPE.DNS;
if (InetAddressUtils.isIPv4Address(host)) {
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);
final HostNameType hostType = determineHostFormat(host);
final List<String> subjectAlts = extractSubjectAlts(cert, hostType.subjectType);
if (subjectAlts != null && !subjectAlts.isEmpty()) {
switch (hostFormat) {
switch (hostType) {
case IPv4:
matchIPAddress(host, subjectAlts);
break;
@ -144,7 +140,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
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);
}
@ -157,7 +153,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
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);
}
@ -171,7 +167,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
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);
}
@ -180,7 +176,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
final String normalizedHost = host.toLowerCase(Locale.ROOT);
final String normalizedCn = cn.toLowerCase(Locale.ROOT);
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);
}
}
@ -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) {
Collection<List<?>> c = null;
try {
@ -302,6 +313,11 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
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.
*/

View File

@ -463,9 +463,9 @@ public class SSLConnectionSocketFactory implements LayeredConnectionSocketFactor
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() + ")");
final List<String> subjectAlts = DefaultHostnameVerifier.extractSubjectAlts(hostname, x509);
throw new SSLPeerUnverifiedException("Certificate for <" + hostname + "> doesn't match any " +
"of the subject alternative names: " + subjectAlts);
}
// verifyHostName() didn't blowup - good!
} catch (final IOException iox) {