package org.bouncycastle.est.jcajce; import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.InetAddress; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Collection; import java.util.Iterator; import java.util.List; import java.util.Set; import javax.net.ssl.SSLSession; import org.bouncycastle.asn1.x500.AttributeTypeAndValue; import org.bouncycastle.asn1.x500.RDN; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x500.style.BCStyle; import org.bouncycastle.est.ESTException; import org.bouncycastle.util.Strings; /** * A typical hostname authorizer for verifying a hostname against the available certificates. */ public class JsseDefaultHostnameAuthorizer implements JsseHostnameAuthorizer { private final Set<String> knownSuffixes; /** * Base constructor. * <p> * The authorizer attempts to perform matching (including the use of the wildcard) in accordance with RFC 6125. * </p> * <p> * Known suffixes is a list of public domain suffixes that can't be used as wild cards for * example *.com, or c*c.com, as a dns wildcard could match every/most .com domains if a registrar were issue it. * If *.com is in the known suffixes list will not be allowed to match. * </p> * * @param knownSuffixes a set of suffixes that cannot be wild-carded, e.g. { ".com", ".net", ".org" } */ public JsseDefaultHostnameAuthorizer(Set<String> knownSuffixes) { this.knownSuffixes = knownSuffixes; } public boolean verified(String name, SSLSession context) throws IOException { try { CertificateFactory fac = CertificateFactory.getInstance("X509"); X509Certificate cert = ( java.security.cert.X509Certificate)fac.generateCertificate( new ByteArrayInputStream((context.getPeerCertificates()[0]).getEncoded())); return verify(name, cert); } catch (Exception ex) { if (ex instanceof ESTException) { throw (ESTException)ex; } throw new ESTException(ex.getMessage(), ex); } } public boolean verify(String name, X509Certificate cert) throws IOException { // // Test against san. // try { Collection n = cert.getSubjectAlternativeNames(); if (n != null) { for (Iterator it = n.iterator(); it.hasNext();) { List l = (List)it.next(); switch (((Number)l.get(0)).intValue()) { case 2: if (isValidNameMatch(name, l.get(1).toString(), knownSuffixes)) { return true; } break; case 7: if (InetAddress.getByName(name).equals(InetAddress.getByName(l.get(1).toString()))) { return true; } break; default: throw new RuntimeException("Unable to handle "); } } // // As we had subject alternative names, we must not attempt to match against the CN. // return false; } } catch (Exception ex) { throw new ESTException(ex.getMessage(), ex); } // can't match - would need to check subjectAltName if (cert.getSubjectX500Principal() == null) { return false; } // Common Name match only. RDN[] rdNs = X500Name.getInstance(cert.getSubjectX500Principal().getEncoded()).getRDNs(); for (int i = 0; i != rdNs.length; i++) { RDN rdn = rdNs[i]; AttributeTypeAndValue[] typesAndValues = rdn.getTypesAndValues(); for (int j = 0; j != typesAndValues.length; j++) { AttributeTypeAndValue atv = typesAndValues[j]; if (atv.getType().equals(BCStyle.CN)) { return isValidNameMatch(name, rdn.getFirst().getValue().toString(), knownSuffixes); } } } return false; } public static boolean isValidNameMatch(String name, String dnsName, Set<String> suffixes) throws IOException { // // Wild card matching. // if (dnsName.contains("*")) { // Only one astrix int wildIndex = dnsName.indexOf('*'); if (wildIndex == dnsName.lastIndexOf("*")) { if (dnsName.contains("..") || dnsName.charAt(dnsName.length() - 1) == '*') { return false; } int dnsDotIndex = dnsName.indexOf('.', wildIndex); if (suffixes != null && suffixes.contains(Strings.toLowerCase(dnsName.substring(dnsDotIndex)))) { throw new IOException("Wildcard `" + dnsName + "` matches known public suffix."); } String end = Strings.toLowerCase(dnsName.substring(wildIndex + 1)); String loweredName = Strings.toLowerCase(name); if (loweredName.equals(end)) { return false; // Must not match wild card exactly there must content to the left of the wildcard. } if (end.length() > loweredName.length()) { return false; } if (wildIndex > 0) { if (loweredName.startsWith(dnsName.substring(0, wildIndex - 1)) && loweredName.endsWith(end)) { return loweredName.substring(wildIndex, loweredName.length() - end.length()).indexOf('.') < 0; } else { return false; } } // Must be only one '*' and it must be at position 0. String prefix = loweredName.substring(0, loweredName.length() - end.length()); if (prefix.indexOf('.') > 0) { return false; } return loweredName.endsWith(end); } return false; } // // No wild card full equality but ignore case. // return name.equalsIgnoreCase(dnsName); } }