All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bouncycastle.jsse.provider.HostnameUtil Maven / Gradle / Ivy

Go to download

The Bouncy Castle Java APIs for the TLS, including a JSSE provider. The APIs are designed primarily to be used in conjunction with the BC FIPS provider. The APIs may also be used with other providers although if being used in a FIPS context it is the responsibility of the user to ensure that any other providers used are FIPS certified and used appropriately.

There is a newer version: 2.0.19
Show 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;
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy