blob: 05b6a3641cba8b6f1ff03e27a28cebc0b2dc31f7 [file] [log] [blame]
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.
*/
public class JsseDefaultHostnameAuthorizer
implements JsseHostnameAuthorizer
{
private static Logger LOG = Logger.getLogger(JsseDefaultHostnameAuthorizer.class.getName());
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();
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);
}
}