All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.hubspot.horizon.ning.internal.NingHostnameVerifier Maven / Gradle / Ivy

There is a newer version: 0.3.1
Show newest version
package com.hubspot.horizon.ning.internal;

import com.hubspot.horizon.SSLConfig;

import javax.annotation.Nullable;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSession;
import java.security.cert.Certificate;
import java.security.cert.CertificateParsingException;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import java.util.regex.Pattern;

/**
 * Tidied up version of org.apache.http.conn.ssl.AbstractVerifier
 */
public class NingHostnameVerifier implements HostnameVerifier {
  private final boolean acceptAllSSL;

  public NingHostnameVerifier(SSLConfig config) {
    this.acceptAllSSL = config.isAcceptAllSSL();
  }

  /**
   * This contains a list of 2nd-level domains that aren't allowed to
   * have wildcards when combined with country-codes.
   * For example: [*.co.uk].
   * 

* The [*.co.uk] problem is an interesting one. Should we just hope * that CA's would never foolishly allow such a certificate to happen? * Looks like we're the only implementation guarding against this. * Firefox, Curl, Sun Java 1.4, 5, 6 don't bother with this check. */ private static final String[] BAD_COUNTRY_2LDS = { "ac", "co", "com", "ed", "edu", "go", "gouv", "gov", "info", "lg", "ne", "net", "or", "org" }; static { // Just in case developer forgot to manually sort the array. :-) Arrays.sort(BAD_COUNTRY_2LDS); } @Override public boolean verify(String host, SSLSession session) { if (acceptAllSSL) { return true; } else { try { Certificate[] certs = session.getPeerCertificates(); X509Certificate x509 = (X509Certificate) certs[0]; verify(host, x509); return true; } catch(SSLException e) { return false; } } } private void verify(String host, X509Certificate cert) throws SSLException { List cns = getCNs(cert); List subjectAlts = getSubjectAlts(cert, host); // Build the list of names we're going to check. Our DEFAULT and // STRICT implementations of the HostnameVerifier only use the // first CN provided. All other CNs are ignored. // (Firefox, wget, curl, Sun Java 1.4, 5, 6 all work this way). LinkedList names = new LinkedList(); if (!cns.isEmpty()) { names.add(cns.get(0)); } names.addAll(subjectAlts); if (names.isEmpty()) { String msg = "Certificate for <" + host + "> doesn't contain CN or DNS subjectAlt"; throw new SSLException(msg); } // StringBuilder for building the error message. StringBuilder buf = new StringBuilder(); // We're can be case-insensitive when comparing the host we used to // establish the socket to the hostname in the certificate. String hostName = host.trim().toLowerCase(Locale.US); boolean match = false; for (Iterator it = names.iterator(); it.hasNext();) { // Don't trim the CN, though! String cn = it.next(); cn = cn.toLowerCase(Locale.US); // Store CN in StringBuilder in case we need to report an error. buf.append(" <"); buf.append(cn); buf.append('>'); if(it.hasNext()) { buf.append(" OR"); } // The CN better have at least two dots if it wants wildcard // action. It also can't be [*.co.uk] or [*.co.jp] or // [*.org.uk], etc... String parts[] = cn.split("\\."); boolean doWildcard = parts.length >= 3 && parts[0].endsWith("*") && acceptableCountryWildcard(cn) && !isIPAddress(host); if(doWildcard) { String firstpart = parts[0]; if (firstpart.length() > 1) { // e.g. server* String prefix = firstpart.substring(0, firstpart.length() - 1); // e.g. server String suffix = cn.substring(firstpart.length()); // skip wildcard part from cn String hostSuffix = hostName.substring(prefix.length()); // skip wildcard part from host match = hostName.startsWith(prefix) && hostSuffix.endsWith(suffix); } else { match = hostName.endsWith(cn.substring(1)); } } else { match = hostName.equals(cn); } if(match) { break; } } if(!match) { throw new SSLException("hostname in certificate didn't match: <" + host + "> !=" + buf); } } private static boolean acceptableCountryWildcard(String cn) { String parts[] = cn.split("\\."); if (parts.length != 3 || parts[2].length() != 2) { return true; // it's not an attempt to wildcard a 2TLD within a country code } return Arrays.binarySearch(BAD_COUNTRY_2LDS, parts[1]) < 0; } private static List getCNs(X509Certificate cert) { LinkedList cnList = new LinkedList(); /* Sebastian Hauer's original StrictSSLProtocolSocketFactory used getName() and had the following comment: Parses a X.500 distinguished name for the value of the "Common Name" field. This is done a bit sloppy right now and should probably be done a bit more according to RFC 2253. I've noticed that toString() seems to do a better job than getName() on these X500Principal objects, so I'm hoping that addresses Sebastian's concern. For example, getName() gives me this: 1.2.840.113549.1.9.1=#16166a756c6975736461766965734063756362632e636f6d whereas toString() gives me this: [email protected] Looks like toString() even works with non-ascii domain names! I tested it with "花子.co.jp" and it worked fine. */ String subjectPrincipal = cert.getSubjectX500Principal().toString(); StringTokenizer st = new StringTokenizer(subjectPrincipal, ",+"); while (st.hasMoreTokens()) { String tok = st.nextToken().trim(); if (tok.length() > 3) { if (tok.substring(0, 3).equalsIgnoreCase("CN=")) { cnList.add(tok.substring(3)); } } } return cnList; } private static List getSubjectAlts(X509Certificate cert, @Nullable String hostname) { int subjectType; if (isIPAddress(hostname)) { subjectType = 7; } else { subjectType = 2; } LinkedList subjectAltList = new LinkedList(); try { for (List aC : cert.getSubjectAlternativeNames()) { int type = ((Integer) aC.get(0)); if (type == subjectType) { String s = (String) aC.get(1); if (s != null) { subjectAltList.add(s); } } } } catch(CertificateParsingException cpe) { // ignored } return subjectAltList; } private static final Pattern IPV4_PATTERN = Pattern.compile( "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])\\.){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$"); private static final Pattern IPV6_STD_PATTERN = Pattern.compile( "^(?:[0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$"); private static final Pattern IPV6_HEX_COMPRESSED_PATTERN = Pattern.compile( "^((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)::((?:[0-9A-Fa-f]{1,4}(?::[0-9A-Fa-f]{1,4})*)?)$"); private static boolean isIPAddress(@Nullable String hostname) { return hostname != null && (isIPv4Address(hostname) || isIPv6Address(hostname)); } private static boolean isIPv4Address(String input) { return IPV4_PATTERN.matcher(input).matches(); } private static boolean isIPv6Address(String input) { return isIPv6StdAddress(input) || isIPv6HexCompressedAddress(input); } private static boolean isIPv6StdAddress(String input) { return IPV6_STD_PATTERN.matcher(input).matches(); } private static boolean isIPv6HexCompressedAddress(String input) { int colonCount = 0; for(int i = 0; i < input.length(); i++) { if (input.charAt(i) == ':') { colonCount++; } } return colonCount <= 7 && IPV6_HEX_COMPRESSED_PATTERN.matcher(input).matches(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy