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:
parent
13810dd84c
commit
bc5d688a4f
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -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) {
|
||||||
|
|
Loading…
Reference in New Issue