alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (HostnameChecker.java)

This example Java source code file (HostnameChecker.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

altname_ip, certificateexception, hostnamechecker, instance_ldap, instance_tls, principal, security, string, stringtokenizer, type_ldap, type_tls, util, x500name, x509certificate

The HostnameChecker.java Java example source code

/*
 * Copyright (c) 2002, 2013, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.util;

import java.io.IOException;
import java.util.*;

import java.security.Principal;
import java.security.cert.*;

import javax.security.auth.x500.X500Principal;

import sun.security.ssl.Krb5Helper;
import sun.security.x509.X500Name;

import sun.net.util.IPAddressUtil;

/**
 * Class to check hostnames against the names specified in a certificate as
 * required for TLS and LDAP.
 *
 */
public class HostnameChecker {

    // Constant for a HostnameChecker for TLS
    public final static byte TYPE_TLS = 1;
    private final static HostnameChecker INSTANCE_TLS =
                                        new HostnameChecker(TYPE_TLS);

    // Constant for a HostnameChecker for LDAP
    public final static byte TYPE_LDAP = 2;
    private final static HostnameChecker INSTANCE_LDAP =
                                        new HostnameChecker(TYPE_LDAP);

    // constants for subject alt names of type DNS and IP
    private final static int ALTNAME_DNS = 2;
    private final static int ALTNAME_IP  = 7;

    // the algorithm to follow to perform the check. Currently unused.
    private final byte checkType;

    private HostnameChecker(byte checkType) {
        this.checkType = checkType;
    }

    /**
     * Get a HostnameChecker instance. checkType should be one of the
     * TYPE_* constants defined in this class.
     */
    public static HostnameChecker getInstance(byte checkType) {
        if (checkType == TYPE_TLS) {
            return INSTANCE_TLS;
        } else if (checkType == TYPE_LDAP) {
            return INSTANCE_LDAP;
        }
        throw new IllegalArgumentException("Unknown check type: " + checkType);
    }

    /**
     * Perform the check.
     *
     * @exception CertificateException if the name does not match any of
     * the names specified in the certificate
     */
    public void match(String expectedName, X509Certificate cert)
            throws CertificateException {
        if (isIpAddress(expectedName)) {
           matchIP(expectedName, cert);
        } else {
           matchDNS(expectedName, cert);
        }
    }

    /**
     * Perform the check for Kerberos.
     */
    public static boolean match(String expectedName, Principal principal) {
        String hostName = getServerName(principal);
        return (expectedName.equalsIgnoreCase(hostName));
    }

    /**
     * Return the Server name from Kerberos principal.
     */
    public static String getServerName(Principal principal) {
        return Krb5Helper.getPrincipalHostName(principal);
    }

    /**
     * Test whether the given hostname looks like a literal IPv4 or IPv6
     * address. The hostname does not need to be a fully qualified name.
     *
     * This is not a strict check that performs full input validation.
     * That means if the method returns true, name need not be a correct
     * IP address, rather that it does not represent a valid DNS hostname.
     * Likewise for IP addresses when it returns false.
     */
    private static boolean isIpAddress(String name) {
        if (IPAddressUtil.isIPv4LiteralAddress(name) ||
            IPAddressUtil.isIPv6LiteralAddress(name)) {
            return true;
        } else {
            return false;
        }
    }

    /**
     * Check if the certificate allows use of the given IP address.
     *
     * From RFC2818:
     * In some cases, the URI is specified as an IP address rather than a
     * hostname. In this case, the iPAddress subjectAltName must be present
     * in the certificate and must exactly match the IP in the URI.
     */
    private static void matchIP(String expectedIP, X509Certificate cert)
            throws CertificateException {
        Collection<List subjAltNames = cert.getSubjectAlternativeNames();
        if (subjAltNames == null) {
            throw new CertificateException
                                ("No subject alternative names present");
        }
        for (List<?> next : subjAltNames) {
            // For IP address, it needs to be exact match
            if (((Integer)next.get(0)).intValue() == ALTNAME_IP) {
                String ipAddress = (String)next.get(1);
                if (expectedIP.equalsIgnoreCase(ipAddress)) {
                    return;
                }
            }
        }
        throw new CertificateException("No subject alternative " +
                        "names matching " + "IP address " +
                        expectedIP + " found");
    }

    /**
     * Check if the certificate allows use of the given DNS name.
     *
     * From RFC2818:
     * If a subjectAltName extension of type dNSName is present, that MUST
     * be used as the identity. Otherwise, the (most specific) Common Name
     * field in the Subject field of the certificate MUST be used. Although
     * the use of the Common Name is existing practice, it is deprecated and
     * Certification Authorities are encouraged to use the dNSName instead.
     *
     * Matching is performed using the matching rules specified by
     * [RFC2459].  If more than one identity of a given type is present in
     * the certificate (e.g., more than one dNSName name, a match in any one
     * of the set is considered acceptable.)
     */
    private void matchDNS(String expectedName, X509Certificate cert)
            throws CertificateException {
        Collection<List subjAltNames = cert.getSubjectAlternativeNames();
        if (subjAltNames != null) {
            boolean foundDNS = false;
            for ( List<?> next : subjAltNames) {
                if (((Integer)next.get(0)).intValue() == ALTNAME_DNS) {
                    foundDNS = true;
                    String dnsName = (String)next.get(1);
                    if (isMatched(expectedName, dnsName)) {
                        return;
                    }
                }
            }
            if (foundDNS) {
                // if certificate contains any subject alt names of type DNS
                // but none match, reject
                throw new CertificateException("No subject alternative DNS "
                        + "name matching " + expectedName + " found.");
            }
        }
        X500Name subjectName = getSubjectX500Name(cert);
        DerValue derValue = subjectName.findMostSpecificAttribute
                                                    (X500Name.commonName_oid);
        if (derValue != null) {
            try {
                if (isMatched(expectedName, derValue.getAsString())) {
                    return;
                }
            } catch (IOException e) {
                // ignore
            }
        }
        String msg = "No name matching " + expectedName + " found";
        throw new CertificateException(msg);
    }


    /**
     * Return the subject of a certificate as X500Name, by reparsing if
     * necessary. X500Name should only be used if access to name components
     * is required, in other cases X500Principal is to be preferred.
     *
     * This method is currently used from within JSSE, do not remove.
     */
    public static X500Name getSubjectX500Name(X509Certificate cert)
            throws CertificateParsingException {
        try {
            Principal subjectDN = cert.getSubjectDN();
            if (subjectDN instanceof X500Name) {
                return (X500Name)subjectDN;
            } else {
                X500Principal subjectX500 = cert.getSubjectX500Principal();
                return new X500Name(subjectX500.getEncoded());
            }
        } catch (IOException e) {
            throw(CertificateParsingException)
                new CertificateParsingException().initCause(e);
        }
    }


    /**
     * Returns true if name matches against template.<p>
     *
     * The matching is performed as per RFC 2818 rules for TLS and
     * RFC 2830 rules for LDAP.<p>
     *
     * The <code>name parameter should represent a DNS name.
     * The <code>template parameter
     * may contain the wildcard character *
     */
    private boolean isMatched(String name, String template) {
        if (checkType == TYPE_TLS) {
            return matchAllWildcards(name, template);
        } else if (checkType == TYPE_LDAP) {
            return matchLeftmostWildcard(name, template);
        } else {
            return false;
        }
    }


    /**
     * Returns true if name matches against template.<p>
     *
     * According to RFC 2818, section 3.1 -
     * Names may contain the wildcard character * which is
     * considered to match any single domain name component
     * or component fragment.
     * E.g., *.a.com matches foo.a.com but not
     * bar.foo.a.com. f*.com matches foo.com but not bar.com.
     */
    private static boolean matchAllWildcards(String name,
         String template) {
        name = name.toLowerCase(Locale.ENGLISH);
        template = template.toLowerCase(Locale.ENGLISH);
        StringTokenizer nameSt = new StringTokenizer(name, ".");
        StringTokenizer templateSt = new StringTokenizer(template, ".");

        if (nameSt.countTokens() != templateSt.countTokens()) {
            return false;
        }

        while (nameSt.hasMoreTokens()) {
            if (!matchWildCards(nameSt.nextToken(),
                        templateSt.nextToken())) {
                return false;
            }
        }
        return true;
    }


    /**
     * Returns true if name matches against template.<p>
     *
     * As per RFC 2830, section 3.6 -
     * The "*" wildcard character is allowed.  If present, it applies only
     * to the left-most name component.
     * E.g. *.bar.com would match a.bar.com, b.bar.com, etc. but not
     * bar.com.
     */
    private static boolean matchLeftmostWildcard(String name,
                         String template) {
        name = name.toLowerCase(Locale.ENGLISH);
        template = template.toLowerCase(Locale.ENGLISH);

        // Retreive leftmost component
        int templateIdx = template.indexOf(".");
        int nameIdx = name.indexOf(".");

        if (templateIdx == -1)
            templateIdx = template.length();
        if (nameIdx == -1)
            nameIdx = name.length();

        if (matchWildCards(name.substring(0, nameIdx),
            template.substring(0, templateIdx))) {

            // match rest of the name
            return template.substring(templateIdx).equals(
                        name.substring(nameIdx));
        } else {
            return false;
        }
    }


    /**
     * Returns true if the name matches against the template that may
     * contain wildcard char * <p>
     */
    private static boolean matchWildCards(String name, String template) {

        int wildcardIdx = template.indexOf("*");
        if (wildcardIdx == -1)
            return name.equals(template);

        boolean isBeginning = true;
        String beforeWildcard = "";
        String afterWildcard = template;

        while (wildcardIdx != -1) {

            // match in sequence the non-wildcard chars in the template.
            beforeWildcard = afterWildcard.substring(0, wildcardIdx);
            afterWildcard = afterWildcard.substring(wildcardIdx + 1);

            int beforeStartIdx = name.indexOf(beforeWildcard);
            if ((beforeStartIdx == -1) ||
                        (isBeginning && beforeStartIdx != 0)) {
                return false;
            }
            isBeginning = false;

            // update the match scope
            name = name.substring(beforeStartIdx + beforeWildcard.length());
            wildcardIdx = afterWildcard.indexOf("*");
        }
        return name.endsWith(afterWildcard);
    }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java HostnameChecker.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2021 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.