in pgjdbc/src/main/java/org/postgresql/ssl/PGjdbcHostnameVerifier.java [81:223]
public boolean verify(String hostname, SSLSession session) {
X509Certificate[] peerCerts;
try {
peerCerts = (X509Certificate[]) session.getPeerCertificates();
} catch (SSLPeerUnverifiedException e) {
LOGGER.log(Level.SEVERE,
GT.tr("Unable to parse X509Certificate for hostname {0}", hostname), e);
return false;
}
if (peerCerts == null || peerCerts.length == 0) {
LOGGER.log(Level.SEVERE,
GT.tr("No certificates found for hostname {0}", hostname));
return false;
}
String canonicalHostname;
if (hostname.startsWith("[") && hostname.endsWith("]")) {
// IPv6 address like [2001:db8:0:1:1:1:1:1]
canonicalHostname = hostname.substring(1, hostname.length() - 1);
} else {
// This converts unicode domain name to ASCII
try {
canonicalHostname = IDN.toASCII(hostname);
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST, "Canonical host name for {0} is {1}",
new Object[]{hostname, canonicalHostname});
}
} catch (IllegalArgumentException e) {
// e.g. hostname is invalid
LOGGER.log(Level.SEVERE,
GT.tr("Hostname {0} is invalid", hostname), e);
return false;
}
}
X509Certificate serverCert = peerCerts[0];
// Check for Subject Alternative Names (see RFC 6125)
Collection<List<?>> subjectAltNames;
try {
subjectAltNames = serverCert.getSubjectAlternativeNames();
if (subjectAltNames == null) {
subjectAltNames = Collections.emptyList();
}
} catch (CertificateParsingException e) {
LOGGER.log(Level.SEVERE,
GT.tr("Unable to parse certificates for hostname {0}", hostname), e);
return false;
}
boolean anyDnsSan = false;
/*
* Each item in the SAN collection is a 2-element list.
* See {@link X509Certificate#getSubjectAlternativeNames}
* The first element in each list is a number indicating the type of entry.
*/
for (List<?> sanItem : subjectAltNames) {
if (sanItem.size() != 2) {
continue;
}
Integer sanType = (Integer) sanItem.get(0);
if (sanType == null) {
// just in case
continue;
}
if (sanType != TYPE_IP_ADDRESS && sanType != TYPE_DNS_NAME) {
continue;
}
String san = (String) sanItem.get(1);
if (sanType == TYPE_IP_ADDRESS && san != null && san.startsWith("*")) {
// Wildcards should not be present in the IP Address field
continue;
}
anyDnsSan |= sanType == TYPE_DNS_NAME;
if (verifyHostName(canonicalHostname, san)) {
if (LOGGER.isLoggable(Level.FINEST)) {
LOGGER.log(Level.FINEST,
GT.tr("Server name validation pass for {0}, subjectAltName {1}", hostname, san));
}
return true;
}
}
if (anyDnsSan) {
/*
* RFC2818, section 3.1 (I bet you won't recheck :)
* If a subjectAltName extension of type dNSName is present, that MUST
* be used as the identity. Otherwise, the (most specific) Common Name
* field in the Subject field of the certificate MUST be used. Although
* the use of the Common Name is existing practice, it is deprecated and
* Certification Authorities are encouraged to use the dNSName instead.
*/
LOGGER.log(Level.SEVERE,
GT.tr("Server name validation failed: certificate for host {0} dNSName entries subjectAltName,"
+ " but none of them match. Assuming server name validation failed", hostname));
return false;
}
// Last attempt: no DNS Subject Alternative Name entries detected, try common name
LdapName dn;
try {
dn = new LdapName(serverCert.getSubjectX500Principal().getName(X500Principal.RFC2253));
} catch (InvalidNameException e) {
LOGGER.log(Level.SEVERE,
GT.tr("Server name validation failed: unable to extract common name"
+ " from X509Certificate for hostname {0}", hostname), e);
return false;
}
List<String> commonNames = new ArrayList<String>(1);
for (Rdn rdn : dn.getRdns()) {
if ("CN".equals(rdn.getType())) {
commonNames.add((String) rdn.getValue());
}
}
if (commonNames.isEmpty()) {
LOGGER.log(Level.SEVERE,
GT.tr("Server name validation failed: certificate for hostname {0} has no DNS subjectAltNames,"
+ " and it CommonName is missing as well",
hostname));
return false;
}
if (commonNames.size() > 1) {
/*
* RFC2818, section 3.1
* If a subjectAltName extension of type dNSName is present, that MUST
* be used as the identity. Otherwise, the (most specific) Common Name
* field in the Subject field of the certificate MUST be used
*
* The sort is from less specific to most specific.
*/
Collections.sort(commonNames, HOSTNAME_PATTERN_COMPARATOR);
}
String commonName = commonNames.get(commonNames.size() - 1);
boolean result = verifyHostName(canonicalHostname, commonName);
if (!result) {
LOGGER.log(Level.SEVERE,
GT.tr("Server name validation failed: hostname {0} does not match common name {1}",
hostname, commonName));
}
return result;
}