org.bouncycastle.jsse.provider.HostnameUtil Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of impersonator Show documentation
Show all versions of impersonator Show documentation
Spoof TLS/JA3/JA4 and HTTP/2 fingerprints in Java
The newest version!
package org.bouncycastle.jsse.provider;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.StringTokenizer;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.ASN1String;
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.asn1.x509.GeneralName;
import org.bouncycastle.jsse.BCSNIHostName;
import org.bouncycastle.util.IPAddress;
class HostnameUtil
{
static void checkHostname(String hostname, X509Certificate certificate, boolean allWildcards) throws CertificateException
{
if (null == hostname)
{
throw new CertificateException("No hostname specified for HTTPS endpoint ID check");
}
if (IPAddress.isValid(hostname))
{
Collection> subjectAltNames = certificate.getSubjectAlternativeNames();
if (null != subjectAltNames)
{
for (List> subjectAltName : subjectAltNames)
{
int type = ((Integer)subjectAltName.get(0)).intValue();
if (GeneralName.iPAddress != type)
{
continue;
}
String ipAddress = (String)subjectAltName.get(1);
if (hostname.equalsIgnoreCase(ipAddress))
{
return;
}
try
{
if (InetAddress.getByName(hostname).equals(InetAddress.getByName(ipAddress)))
{
return;
}
}
catch (UnknownHostException e)
{
// Ignore
}
catch (SecurityException e)
{
// Ignore
}
}
}
throw new CertificateException("No subject alternative name found matching IP address " + hostname);
}
else if (isValidDomainName(hostname))
{
Collection> subjectAltNames = certificate.getSubjectAlternativeNames();
if (null != subjectAltNames)
{
boolean foundAnyDNSNames = false;
for (List> subjectAltName : subjectAltNames)
{
int type = ((Integer)subjectAltName.get(0)).intValue();
if (GeneralName.dNSName != type)
{
continue;
}
foundAnyDNSNames = true;
String dnsName = (String)subjectAltName.get(1);
if (matchesDNSName(hostname, dnsName, allWildcards))
{
return;
}
}
// NOTE: If any DNS names were found, don't check the subject
if (foundAnyDNSNames)
{
throw new CertificateException("No subject alternative name found matching domain name " + hostname);
}
}
ASN1Primitive commonName = findMostSpecificCN(certificate.getSubjectX500Principal());
if (commonName instanceof ASN1String
&& matchesDNSName(hostname, ((ASN1String)commonName).getString(), allWildcards))
{
return;
}
throw new CertificateException("No name found matching " + hostname);
}
else
{
throw new CertificateException("Invalid hostname specified for HTTPS endpoint ID check");
}
}
private static ASN1Primitive findMostSpecificCN(X500Principal principal)
{
if (null != principal)
{
RDN[] rdns = X500Name.getInstance(principal.getEncoded()).getRDNs();
for (int i = rdns.length - 1; i >= 0; --i)
{
AttributeTypeAndValue[] typesAndValues = rdns[i].getTypesAndValues();
for (int j = 0; j < typesAndValues.length; ++j)
{
AttributeTypeAndValue typeAndValue = typesAndValues[j];
if (BCStyle.CN.equals(typeAndValue.getType()))
{
return typeAndValue.getValue().toASN1Primitive();
}
}
}
}
return null;
}
private static String getLabel(String s, int begin)
{
int end = s.indexOf('.', begin);
if (end < 0)
{
end = s.length();
}
return s.substring(begin, end);
}
private static boolean isValidDomainName(String name)
{
try
{
new BCSNIHostName(name);
return true;
}
catch (RuntimeException e)
{
return false;
}
}
private static boolean labelMatchesPattern(String label, String pattern)
{
int wildcardPos = pattern.indexOf('*');
if (wildcardPos < 0)
{
return label.equals(pattern);
}
int labelPos = 0, patternPos = 0;
do
{
String segment = pattern.substring(patternPos, wildcardPos);
int matchPos = label.indexOf(segment, labelPos);
if (matchPos < 0 || (patternPos == 0 && matchPos > 0))
{
return false;
}
labelPos = matchPos + segment.length();
patternPos = wildcardPos + 1;
}
while ((wildcardPos = pattern.indexOf('*', patternPos)) >= 0);
String labelRest = label.substring(labelPos);
String patternRest = pattern.substring(patternPos);
return labelRest.endsWith(patternRest);
}
private static boolean matchesDNSName(String hostname, String dnsName, boolean allWildcards)
{
try
{
// TODO[jsse] 'hostname' conversion per call is redundant (and with isValidDomainName)
hostname = IDNUtil.toUnicode(IDNUtil.toASCII(hostname, 0), 0);
dnsName = IDNUtil.toUnicode(IDNUtil.toASCII(dnsName, 0), 0);
}
catch (RuntimeException e)
{
return false;
}
if (!validateWildcards(dnsName))
{
return false;
}
if (!isValidDomainName(dnsName.replace('*', 'z')))
{
return false;
}
hostname = hostname.toLowerCase(Locale.ENGLISH);
dnsName = dnsName.toLowerCase(Locale.ENGLISH);
return allWildcards
? matchesWildcardsAllLabels(hostname, dnsName)
: matchesWildcardsFirstLabel(hostname, dnsName);
}
private static boolean matchesWildcardsAllLabels(String hostname, String dnsName)
{
StringTokenizer st1 = new StringTokenizer(hostname, ".");
StringTokenizer st2 = new StringTokenizer(dnsName, ".");
if (st1.countTokens() != st2.countTokens())
{
return false;
}
while (st1.hasMoreTokens())
{
String label = st1.nextToken();
String pattern = st2.nextToken();
if (!labelMatchesPattern(label, pattern))
{
return false;
}
}
return true;
}
private static boolean matchesWildcardsFirstLabel(String hostname, String dnsName)
{
String hostnameLabel = getLabel(hostname, 0);
String dnsNameLabel = getLabel(dnsName, 0);
if (!labelMatchesPattern(hostnameLabel, dnsNameLabel))
{
return false;
}
String hostnameRest = hostname.substring(hostnameLabel.length());
String dnsNameRest = dnsName.substring(dnsNameLabel.length());
return hostnameRest.equals(dnsNameRest);
}
private static boolean validateWildcards(String dnsName)
{
int wildCardIndex = dnsName.lastIndexOf('*');
if (wildCardIndex >= 0)
{
// TODO[jsse] Are these checks redundant?
if (dnsName.equals("*") || dnsName.equals("*."))
{
return false;
}
// There must be at least one dot after the last wildcard
int dotIndex = dnsName.indexOf('.', wildCardIndex + 1);
if (dotIndex < 0)
{
return false;
}
/*
* TODO[jsse] Wildcards not allowed in the label immediately before an ICANN public
* suffix when certificate 'chainsToPublicCA' (a SunJSSE code term - exact meaning?).
*/
// if (chainsToPublicCA)
// {
// String suffix = dnsName.substring(dotIndex).toLowerCase(Locale.ENGLISH);
//
// // TODO[jsse] Implement (case-insensitive)
// if (isPublicSuffix(suffix))
// {
// return false;
// }
// }
}
return true;
}
}