Improved compliance with RFC 2818: default hostname verifier to ignore the common name of the certificate subject if alternative subject names (dNSName or iPAddress) are present
git-svn-id: https://svn.apache.org/repos/asf/httpcomponents/httpclient/trunk@1618867 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
parent
196ac3f328
commit
d2400e0758
|
@ -1,6 +1,10 @@
|
||||||
Changes isnce 4.4 ALPHA1
|
Changes since 4.4 ALPHA1
|
||||||
-------------------
|
-------------------
|
||||||
|
|
||||||
|
* Improved compliance with RFC 2818: default hostname verifier to ignore the common name of the
|
||||||
|
certificate subject if alternative subject names (dNSName or iPAddress) are present.
|
||||||
|
Contributed by Oleg Kalnichevski <olegk at apache.org>
|
||||||
|
|
||||||
* [HTTPCLIENT-1540] Support delegated credentials (ISC_REQ_DELEGATE) by Native windows
|
* [HTTPCLIENT-1540] Support delegated credentials (ISC_REQ_DELEGATE) by Native windows
|
||||||
Negotiate/NTLM auth schemes.
|
Negotiate/NTLM auth schemes.
|
||||||
Contributed by Ka-Lok Fung <ka-lok.fung at sap.com>
|
Contributed by Ka-Lok Fung <ka-lok.fung at sap.com>
|
||||||
|
|
|
@ -140,20 +140,10 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
|
||||||
|
|
||||||
final String cn = cns != null && cns.length > 0 ? cns[0] : null;
|
final String cn = cns != null && cns.length > 0 ? cns[0] : null;
|
||||||
final List<String> subjectAltList = subjectAlts != null && subjectAlts.length > 0 ? Arrays.asList(subjectAlts) : null;
|
final List<String> subjectAltList = subjectAlts != null && subjectAlts.length > 0 ? Arrays.asList(subjectAlts) : null;
|
||||||
if (cn == null && subjectAltList == null) {
|
|
||||||
final String msg = "Certificate subject for <" + host + "> doesn't contain a common name " +
|
|
||||||
"and does not have alternative names";
|
|
||||||
throw new SSLException(msg);
|
|
||||||
}
|
|
||||||
final String normalizedHost = InetAddressUtils.isIPv6Address(host) ?
|
final String normalizedHost = InetAddressUtils.isIPv6Address(host) ?
|
||||||
DefaultHostnameVerifier.normaliseAddress(host.toLowerCase(Locale.ROOT)) : host;
|
DefaultHostnameVerifier.normaliseAddress(host.toLowerCase(Locale.ROOT)) : host;
|
||||||
if (cn != null) {
|
|
||||||
final String normalizedCN = InetAddressUtils.isIPv6Address(cn) ?
|
|
||||||
DefaultHostnameVerifier.normaliseAddress(cn) : cn;
|
|
||||||
if (matchIdentity(normalizedHost, normalizedCN, strictWithSubDomains)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (subjectAltList != null) {
|
if (subjectAltList != null) {
|
||||||
for (String subjectAlt: subjectAltList) {
|
for (String subjectAlt: subjectAltList) {
|
||||||
final String normalizedAltSubject = InetAddressUtils.isIPv6Address(subjectAlt) ?
|
final String normalizedAltSubject = InetAddressUtils.isIPv6Address(subjectAlt) ?
|
||||||
|
@ -162,19 +152,20 @@ public abstract class AbstractVerifier implements X509HostnameVerifier {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
||||||
|
"of the subject alternative names: " + subjectAltList);
|
||||||
|
} else if (cn != null) {
|
||||||
|
final String normalizedCN = InetAddressUtils.isIPv6Address(cn) ?
|
||||||
|
DefaultHostnameVerifier.normaliseAddress(cn) : cn;
|
||||||
|
if (matchIdentity(normalizedHost, normalizedCN, strictWithSubDomains)) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
final StringBuilder buf = new StringBuilder();
|
throw new SSLException("Certificate for <" + host + "> doesn't match " +
|
||||||
buf.append("Certificate for <").append(host).append("> doesn't match ");
|
"common name of the certificate subject: " + cn);
|
||||||
if (cn != null) {
|
} else {
|
||||||
buf.append("common name of the certificate subject [").append(cn).append("]");
|
throw new SSLException("Certificate subject for <" + host + "> doesn't contain " +
|
||||||
|
"a common name and does not have alternative names");
|
||||||
}
|
}
|
||||||
if (subjectAltList != null) {
|
|
||||||
if (cn != null) {
|
|
||||||
buf.append(" or ");
|
|
||||||
}
|
|
||||||
buf.append(" any of the subject alternative names").append(subjectAltList);
|
|
||||||
}
|
|
||||||
throw new SSLException(buf.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean matchIdentity(final String host, final String identity, final boolean strictWithSubDomains) {
|
private static boolean matchIdentity(final String host, final String identity, final boolean strictWithSubDomains) {
|
||||||
|
|
|
@ -123,6 +123,10 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
|
||||||
// as fallback only when no subjectAlts are available
|
// as fallback only when no subjectAlts are available
|
||||||
final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
|
final X500Principal subjectPrincipal = cert.getSubjectX500Principal();
|
||||||
final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
|
final String cn = extractCN(subjectPrincipal.getName(X500Principal.RFC2253));
|
||||||
|
if (cn == null) {
|
||||||
|
throw new SSLException("Certificate subject for <" + host + "> doesn't contain " +
|
||||||
|
"a common name and does not have alternative names");
|
||||||
|
}
|
||||||
matchCN(host, cn);
|
matchCN(host, cn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -135,7 +139,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
||||||
"of the subject alternative names " + subjectAlts);
|
"of the subject alternative names: " + subjectAlts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void matchIPv6Address(final String host, final List<String> subjectAlts) throws SSLException {
|
static void matchIPv6Address(final String host, final List<String> subjectAlts) throws SSLException {
|
||||||
|
@ -148,7 +152,7 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
||||||
"of the subject alternative names " + subjectAlts);
|
"of the subject alternative names: " + subjectAlts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void matchDNSName(final String host, final List<String> subjectAlts) throws SSLException {
|
static void matchDNSName(final String host, final List<String> subjectAlts) throws SSLException {
|
||||||
|
@ -159,13 +163,13 @@ public final class DefaultHostnameVerifier implements HostnameVerifier {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
throw new SSLException("Certificate for <" + host + "> doesn't match any " +
|
||||||
"of the subject alternative names " + subjectAlts);
|
"of the subject alternative names: " + subjectAlts);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void matchCN(final String host, final String cn) throws SSLException {
|
static void matchCN(final String host, final String cn) throws SSLException {
|
||||||
if (!matchIdentity(host, cn)) {
|
if (!matchIdentity(host, cn)) {
|
||||||
throw new SSLException("Certificate for <" + host + "> doesn't match " +
|
throw new SSLException("Certificate for <" + host + "> doesn't match " +
|
||||||
"common name of the certificate subject [" + cn + "]");
|
"common name of the certificate subject: " + cn);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,8 @@ public class TestHostnameVerifier {
|
||||||
|
|
||||||
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR);
|
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR);
|
||||||
x509 = (X509Certificate) cf.generateCertificate(in);
|
x509 = (X509Certificate) cf.generateCertificate(in);
|
||||||
DEFAULT.verify("foo.com", x509);
|
exceptionPlease(DEFAULT, "foo.com", x509);
|
||||||
STRICT.verify("foo.com", x509);
|
exceptionPlease(STRICT, "foo.com", x509);
|
||||||
exceptionPlease(DEFAULT, "a.foo.com", x509);
|
exceptionPlease(DEFAULT, "a.foo.com", x509);
|
||||||
exceptionPlease(STRICT, "a.foo.com", x509);
|
exceptionPlease(STRICT, "a.foo.com", x509);
|
||||||
DEFAULT.verify("bar.com", x509);
|
DEFAULT.verify("bar.com", x509);
|
||||||
|
@ -85,8 +85,8 @@ public class TestHostnameVerifier {
|
||||||
|
|
||||||
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR_HANAKO);
|
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_FOO_BAR_HANAKO);
|
||||||
x509 = (X509Certificate) cf.generateCertificate(in);
|
x509 = (X509Certificate) cf.generateCertificate(in);
|
||||||
DEFAULT.verify("foo.com", x509);
|
exceptionPlease(DEFAULT, "foo.com", x509);
|
||||||
STRICT.verify("foo.com", x509);
|
exceptionPlease(STRICT, "foo.com", x509);
|
||||||
exceptionPlease(DEFAULT, "a.foo.com", x509);
|
exceptionPlease(DEFAULT, "a.foo.com", x509);
|
||||||
exceptionPlease(STRICT, "a.foo.com", x509);
|
exceptionPlease(STRICT, "a.foo.com", x509);
|
||||||
DEFAULT.verify("bar.com", x509);
|
DEFAULT.verify("bar.com", x509);
|
||||||
|
@ -159,11 +159,11 @@ public class TestHostnameVerifier {
|
||||||
// try the foo.com variations
|
// try the foo.com variations
|
||||||
exceptionPlease(DEFAULT, "foo.com", x509);
|
exceptionPlease(DEFAULT, "foo.com", x509);
|
||||||
exceptionPlease(STRICT, "foo.com", x509);
|
exceptionPlease(STRICT, "foo.com", x509);
|
||||||
DEFAULT.verify("www.foo.com", x509);
|
exceptionPlease(DEFAULT, "www.foo.com", x509);
|
||||||
STRICT.verify("www.foo.com", x509);
|
exceptionPlease(STRICT, "www.foo.com", x509);
|
||||||
DEFAULT.verify("\u82b1\u5b50.foo.com", x509);
|
exceptionPlease(DEFAULT, "\u82b1\u5b50.foo.com", x509);
|
||||||
STRICT.verify("\u82b1\u5b50.foo.com", x509);
|
exceptionPlease(STRICT, "\u82b1\u5b50.foo.com", x509);
|
||||||
DEFAULT.verify("a.b.foo.com", x509);
|
exceptionPlease(DEFAULT, "a.b.foo.com", x509);
|
||||||
exceptionPlease(STRICT, "a.b.foo.com", x509);
|
exceptionPlease(STRICT, "a.b.foo.com", x509);
|
||||||
// try the bar.com variations
|
// try the bar.com variations
|
||||||
exceptionPlease(DEFAULT, "bar.com", x509);
|
exceptionPlease(DEFAULT, "bar.com", x509);
|
||||||
|
@ -174,19 +174,6 @@ public class TestHostnameVerifier {
|
||||||
STRICT.verify("\u82b1\u5b50.bar.com", x509);
|
STRICT.verify("\u82b1\u5b50.bar.com", x509);
|
||||||
DEFAULT.verify("a.b.bar.com", x509);
|
DEFAULT.verify("a.b.bar.com", x509);
|
||||||
exceptionPlease(STRICT, "a.b.bar.com", x509);
|
exceptionPlease(STRICT, "a.b.bar.com", x509);
|
||||||
// try the \u82b1\u5b50.co.jp variations
|
|
||||||
/*
|
|
||||||
Java isn't extracting international subjectAlts properly. (Or
|
|
||||||
OpenSSL isn't storing them properly).
|
|
||||||
*/
|
|
||||||
//exceptionPlease( DEFAULT, "\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//exceptionPlease( STRICT, "\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//DEFAULT.verify("www.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//STRICT.verify("www.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//DEFAULT.verify("\u82b1\u5b50.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//STRICT.verify("\u82b1\u5b50.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//DEFAULT.verify("a.b.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
//exceptionPlease(STRICT,"a.b.\u82b1\u5b50.co.jp", x509 );
|
|
||||||
|
|
||||||
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_VALUE_AVA);
|
in = new ByteArrayInputStream(CertificatesToPlayWith.X509_MULTIPLE_VALUE_AVA);
|
||||||
x509 = (X509Certificate) cf.generateCertificate(in);
|
x509 = (X509Certificate) cf.generateCertificate(in);
|
||||||
|
@ -206,10 +193,15 @@ public class TestHostnameVerifier {
|
||||||
Assert.assertEquals("CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CH",
|
Assert.assertEquals("CN=localhost, OU=Unknown, O=Unknown, L=Unknown, ST=Unknown, C=CH",
|
||||||
x509.getSubjectDN().getName());
|
x509.getSubjectDN().getName());
|
||||||
|
|
||||||
verifier.verify("localhost", x509);
|
|
||||||
verifier.verify("localhost.localdomain", x509);
|
verifier.verify("localhost.localdomain", x509);
|
||||||
verifier.verify("127.0.0.1", x509);
|
verifier.verify("127.0.0.1", x509);
|
||||||
|
|
||||||
|
try {
|
||||||
|
verifier.verify("localhost", x509);
|
||||||
|
Assert.fail("SSLException should have been thrown");
|
||||||
|
} catch (final SSLException ex) {
|
||||||
|
// expected
|
||||||
|
}
|
||||||
try {
|
try {
|
||||||
verifier.verify("local.host", x509);
|
verifier.verify("local.host", x509);
|
||||||
Assert.fail("SSLException should have been thrown");
|
Assert.fail("SSLException should have been thrown");
|
||||||
|
@ -296,7 +288,7 @@ public class TestHostnameVerifier {
|
||||||
checkMatching(bhv, "s.a.gov.com", cns, alt, false); // OK, gov not 2TLD here
|
checkMatching(bhv, "s.a.gov.com", cns, alt, false); // OK, gov not 2TLD here
|
||||||
checkMatching(shv, "s.a.gov.com", cns, alt, true); // no subdomain allowed
|
checkMatching(shv, "s.a.gov.com", cns, alt, true); // no subdomain allowed
|
||||||
|
|
||||||
cns = new String []{"a*.gov.uk"}; // 2TLD check applies to wildcards
|
alt = new String []{"a*.gov.uk"}; // 2TLD check applies to wildcards
|
||||||
checkMatching(bhv, "a.gov.uk", cns, alt, false); // OK
|
checkMatching(bhv, "a.gov.uk", cns, alt, false); // OK
|
||||||
checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
|
checkMatching(shv, "a.gov.uk", cns, alt, true); // Bad 2TLD
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue