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 java.util.logging.Level;
import java.util.logging.Logger;

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;
import org.bouncycastle.util.encoders.Hex;


A typical hostname authorizer for verifying a hostname against the available certificates.
/** * A typical hostname authorizer for verifying a hostname against the available certificates. */
public class JsseDefaultHostnameAuthorizer implements JsseHostnameAuthorizer { private static Logger LOG = Logger.getLogger(JsseDefaultHostnameAuthorizer.class.getName()); private final Set<String> knownSuffixes;
Base constructor.

The authorizer attempts to perform matching (including the use of the wildcard) in accordance with RFC 6125.

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.

Params:
  • knownSuffixes – a set of suffixes that cannot be wild-carded, e.g. { ".com", ".net", ".org" }
/** * 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(); int type = ((Number)l.get(0)).intValue(); switch (type) { 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: // ignore, maybe log if (LOG.isLoggable(Level.INFO)) { String value; if (l.get(1) instanceof byte[]) { value = Hex.toHexString((byte[])l.get(1)); } else { value = l.get(1).toString(); } LOG.log(Level.INFO, "ignoring type " + type + " value = " + value); } } } // // 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 = rdNs.length - 1; i >= 0; --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, atv.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)) && 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); } }