org.bouncycastle.est.jcajce.JsseDefaultHostnameAuthorizer Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of bcpkix-jdk15to18 Show documentation
Show all versions of bcpkix-jdk15to18 Show documentation
The Bouncy Castle Java APIs for CMS, PKCS, EAC, TSP, CMP, CRMF, OCSP, and certificate generation. This jar contains APIs for JDK 1.5 to JDK 1.8. The APIs can be used in conjunction with a JCE/JCA provider such as the one provided with the Bouncy Castle Cryptography APIs.
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 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.
*
*
* @param knownSuffixes a set of suffixes that cannot be wild-carded, e.g. { ".com", ".net", ".org" }
*/
public JsseDefaultHostnameAuthorizer(Set 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[])
{
// -DM Hex.toHexString
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 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);
}
}