405 lines · 15.2 KB · Java
RawDownload
/* * Copyright (c) 2002, 2011, 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.validator; import java.io.IOException;import java.util.*; import java.security.*;import java.security.cert.*; import javax.security.auth.x500.X500Principal; import sun.security.x509.X509CertImpl;import sun.security.x509.NetscapeCertTypeExtension;import sun.security.util.DerValue;import sun.security.util.DerInputStream;import sun.security.util.DerOutputStream;import sun.security.util.ObjectIdentifier; import sun.security.provider.certpath.AlgorithmChecker; /** * A simple validator implementation. It is based on code from the JSSE * X509TrustManagerImpl. This implementation is designed for compatibility with * deployed certificates and previous J2SE versions. It will never support * more advanced features and will be deemphasized in favor of the PKIX * validator going forward. * <p> * {@code SimpleValidator} objects are immutable once they have been created. * Please DO NOT add methods that can change the state of an instance once * it has been created. * * @author Andreas Sterbenz */public final class SimpleValidator extends Validator {     // Constants for the OIDs we need     final static String OID_BASIC_CONSTRAINTS = "2.5.29.19";     final static String OID_NETSCAPE_CERT_TYPE = "2.16.840.1.113730.1.1";     final static String OID_KEY_USAGE = "2.5.29.15";     final static String OID_EXTENDED_KEY_USAGE = "2.5.29.37";     final static String OID_EKU_ANY_USAGE = "2.5.29.37.0";     final static ObjectIdentifier OBJID_NETSCAPE_CERT_TYPE =        NetscapeCertTypeExtension.NetscapeCertType_Id;     private final static String NSCT_SSL_CA =                                NetscapeCertTypeExtension.SSL_CA;     private final static String NSCT_CODE_SIGNING_CA =                                NetscapeCertTypeExtension.OBJECT_SIGNING_CA;     /**     * The trusted certificates as:     * Map (X500Principal)subject of trusted cert -> List of X509Certificate     * The list is used because there may be multiple certificates     * with an identical subject DN.     */    private final Map<X500Principal, List<X509Certificate>>                                            trustedX500Principals;     /**     * Set of the trusted certificates. Present only for     * getTrustedCertificates().     */    private final Collection<X509Certificate> trustedCerts;     SimpleValidator(String variant, Collection<X509Certificate> trustedCerts) {        super(TYPE_SIMPLE, variant);        this.trustedCerts = trustedCerts;        trustedX500Principals =                        new HashMap<X500Principal, List<X509Certificate>>();        for (X509Certificate cert : trustedCerts) {            X500Principal principal = cert.getSubjectX500Principal();            List<X509Certificate> list = trustedX500Principals.get(principal);            if (list == null) {                // this actually should be a set, but duplicate entries                // are not a problem and we can avoid the Set overhead                list = new ArrayList<X509Certificate>(2);                trustedX500Principals.put(principal, list);            }            list.add(cert);        }    }     public Collection<X509Certificate> getTrustedCertificates() {        return trustedCerts;    }     /**     * Perform simple validation of chain. The arguments otherCerts and     * parameter are ignored.     */    @Override    X509Certificate[] engineValidate(X509Certificate[] chain,            Collection<X509Certificate> otherCerts,            AlgorithmConstraints constraints,            Object parameter) throws CertificateException {        if ((chain == null) || (chain.length == 0)) {            throw new CertificateException                ("null or zero-length certificate chain");        }         // make sure chain includes a trusted cert        chain = buildTrustedChain(chain);         Date date = validationDate;        if (date == null) {            date = new Date();        }         // create default algorithm constraints checker        TrustAnchor anchor = new TrustAnchor(chain[chain.length - 1], null);        AlgorithmChecker defaultAlgChecker = new AlgorithmChecker(anchor);         // create application level algorithm constraints checker        AlgorithmChecker appAlgChecker = null;        if (constraints != null) {            appAlgChecker = new AlgorithmChecker(anchor, constraints);        }         // verify top down, starting at the certificate issued by        // the trust anchor        int maxPathLength = chain.length - 1;        for (int i = chain.length - 2; i >= 0; i--) {            X509Certificate issuerCert = chain[i + 1];            X509Certificate cert = chain[i];             // check certificate algorithm            try {                // Algorithm checker does not care about the unresolved                // critical extensions.                defaultAlgChecker.check(cert, Collections.<String>emptySet());                if (appAlgChecker != null) {                    appAlgChecker.check(cert, Collections.<String>emptySet());                }            } catch (CertPathValidatorException cpve) {                throw new ValidatorException                        (ValidatorException.T_ALGORITHM_DISABLED, cert, cpve);            }             // no validity check for code signing certs            if ((variant.equals(VAR_CODE_SIGNING) == false)                        && (variant.equals(VAR_JCE_SIGNING) == false)) {                cert.checkValidity(date);            }             // check name chaining            if (cert.getIssuerX500Principal().equals(                        issuerCert.getSubjectX500Principal()) == false) {                throw new ValidatorException                        (ValidatorException.T_NAME_CHAINING, cert);            }             // check signature            try {                cert.verify(issuerCert.getPublicKey());            } catch (GeneralSecurityException e) {                throw new ValidatorException                        (ValidatorException.T_SIGNATURE_ERROR, cert, e);            }             // check extensions for CA certs            if (i != 0) {                maxPathLength = checkExtensions(cert, maxPathLength);            }        }         return chain;    }     private int checkExtensions(X509Certificate cert, int maxPathLen)            throws CertificateException {        Set<String> critSet = cert.getCriticalExtensionOIDs();        if (critSet == null) {            critSet = Collections.<String>emptySet();        }         // Check the basic constraints extension        int pathLenConstraint =                checkBasicConstraints(cert, critSet, maxPathLen);         // Check the key usage and extended key usage extensions        checkKeyUsage(cert, critSet);         // check Netscape certificate type extension        checkNetscapeCertType(cert, critSet);         if (!critSet.isEmpty()) {            throw new ValidatorException                ("Certificate contains unknown critical extensions: " + critSet,                ValidatorException.T_CA_EXTENSIONS, cert);        }         return pathLenConstraint;    }     private void checkNetscapeCertType(X509Certificate cert,            Set<String> critSet) throws CertificateException {        if (variant.equals(VAR_GENERIC)) {            // nothing        } else if (variant.equals(VAR_TLS_CLIENT)                || variant.equals(VAR_TLS_SERVER)) {            if (getNetscapeCertTypeBit(cert, NSCT_SSL_CA) == false) {                throw new ValidatorException                        ("Invalid Netscape CertType extension for SSL CA "                        + "certificate",                        ValidatorException.T_CA_EXTENSIONS, cert);            }            critSet.remove(OID_NETSCAPE_CERT_TYPE);        } else if (variant.equals(VAR_CODE_SIGNING)                || variant.equals(VAR_JCE_SIGNING)) {            if (getNetscapeCertTypeBit(cert, NSCT_CODE_SIGNING_CA) == false) {                throw new ValidatorException                        ("Invalid Netscape CertType extension for code "                        + "signing CA certificate",                        ValidatorException.T_CA_EXTENSIONS, cert);            }            critSet.remove(OID_NETSCAPE_CERT_TYPE);        } else {            throw new CertificateException("Unknown variant " + variant);        }    }     /**     * Get the value of the specified bit in the Netscape certificate type     * extension. If the extension is not present at all, we return true.     */    static boolean getNetscapeCertTypeBit(X509Certificate cert, String type) {        try {            NetscapeCertTypeExtension ext;            if (cert instanceof X509CertImpl) {                X509CertImpl certImpl = (X509CertImpl)cert;                ObjectIdentifier oid = OBJID_NETSCAPE_CERT_TYPE;                ext = (NetscapeCertTypeExtension)certImpl.getExtension(oid);                if (ext == null) {                    return true;                }            } else {                byte[] extVal = cert.getExtensionValue(OID_NETSCAPE_CERT_TYPE);                if (extVal == null) {                    return true;                }                DerInputStream in = new DerInputStream(extVal);                byte[] encoded = in.getOctetString();                encoded = new DerValue(encoded).getUnalignedBitString()                                                                .toByteArray();                ext = new NetscapeCertTypeExtension(encoded);            }            Boolean val = (Boolean)ext.get(type);            return val.booleanValue();        } catch (IOException e) {            return false;        }    }     private int checkBasicConstraints(X509Certificate cert,            Set<String> critSet, int maxPathLen) throws CertificateException {         critSet.remove(OID_BASIC_CONSTRAINTS);        int constraints = cert.getBasicConstraints();        // reject, if extension missing or not a CA (constraints == -1)        if (constraints < 0) {            throw new ValidatorException("End user tried to act as a CA",                ValidatorException.T_CA_EXTENSIONS, cert);        }         // if the certificate is self-issued, ignore the pathLenConstraint        // checking.        if (!X509CertImpl.isSelfIssued(cert)) {            if (maxPathLen <= 1) {   // reserved one for end-entity certificate                throw new ValidatorException("Violated path length constraints",                    ValidatorException.T_CA_EXTENSIONS, cert);            }             maxPathLen--;        }         if (maxPathLen > constraints) {            maxPathLen = constraints;        }         return maxPathLen;    }     /*     * Verify the key usage and extended key usage for intermediate     * certificates.     */    private void checkKeyUsage(X509Certificate cert, Set<String> critSet)            throws CertificateException {         critSet.remove(OID_KEY_USAGE);        // EKU irrelevant in CA certificates        critSet.remove(OID_EXTENDED_KEY_USAGE);         // check key usage extension        boolean[] keyUsageInfo = cert.getKeyUsage();        if (keyUsageInfo != null) {            // keyUsageInfo[5] is for keyCertSign.            if ((keyUsageInfo.length < 6) || (keyUsageInfo[5] == false)) {                throw new ValidatorException                        ("Wrong key usage: expected keyCertSign",                        ValidatorException.T_CA_EXTENSIONS, cert);            }        }    }     /**     * Build a trusted certificate chain. This method always returns a chain     * with a trust anchor as the final cert in the chain. If no trust anchor     * could be found, a CertificateException is thrown.     */    private X509Certificate[] buildTrustedChain(X509Certificate[] chain)            throws CertificateException {        List<X509Certificate> c = new ArrayList<X509Certificate>(chain.length);        // scan chain starting at EE cert        // if a trusted certificate is found, append it and return        for (int i = 0; i < chain.length; i++) {            X509Certificate cert = chain[i];            X509Certificate trustedCert = getTrustedCertificate(cert);            if (trustedCert != null) {                c.add(trustedCert);                return c.toArray(CHAIN0);            }            c.add(cert);        }         // check if we can append a trusted cert        X509Certificate cert = chain[chain.length - 1];        X500Principal subject = cert.getSubjectX500Principal();        X500Principal issuer = cert.getIssuerX500Principal();        List<X509Certificate> list = trustedX500Principals.get(issuer);        if (list != null) {            X509Certificate trustedCert = list.iterator().next();            c.add(trustedCert);            return c.toArray(CHAIN0);        }         // no trusted cert found, error        throw new ValidatorException(ValidatorException.T_NO_TRUST_ANCHOR);    }     /**     * Return a trusted certificate that matches the input certificate,     * or null if no such certificate can be found. This method also handles     * cases where a CA re-issues a trust anchor with the same public key and     * same subject and issuer names but a new validity period, etc.     */    private X509Certificate getTrustedCertificate(X509Certificate cert) {        Principal certSubjectName = cert.getSubjectX500Principal();        List<X509Certificate> list = trustedX500Principals.get(certSubjectName);        if (list == null) {            return null;        }         Principal certIssuerName = cert.getIssuerX500Principal();        PublicKey certPublicKey = cert.getPublicKey();         for (X509Certificate mycert : list) {            if (mycert.equals(cert)) {                return cert;            }            if (!mycert.getIssuerX500Principal().equals(certIssuerName)) {                continue;            }            if (!mycert.getPublicKey().equals(certPublicKey)) {                continue;            }             // All tests pass, this must be the one to use...            return mycert;        }        return null;    } }