SimpleValidator.java revision 12745:f068a4ffddd2
1/*
2 * Copyright (c) 2002, 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package sun.security.validator;
27
28import java.io.IOException;
29import java.util.*;
30
31import java.security.*;
32import java.security.cert.*;
33
34import javax.security.auth.x500.X500Principal;
35
36import sun.security.x509.X509CertImpl;
37import sun.security.x509.NetscapeCertTypeExtension;
38import sun.security.util.DerValue;
39import sun.security.util.DerInputStream;
40import sun.security.util.ObjectIdentifier;
41
42import sun.security.provider.certpath.AlgorithmChecker;
43import sun.security.provider.certpath.UntrustedChecker;
44
45/**
46 * A simple validator implementation. It is based on code from the JSSE
47 * X509TrustManagerImpl. This implementation is designed for compatibility with
48 * deployed certificates and previous J2SE versions. It will never support
49 * more advanced features and will be deemphasized in favor of the PKIX
50 * validator going forward.
51 * <p>
52 * {@code SimpleValidator} objects are immutable once they have been created.
53 * Please DO NOT add methods that can change the state of an instance once
54 * it has been created.
55 *
56 * @author Andreas Sterbenz
57 */
58public final class SimpleValidator extends Validator {
59
60    // Constants for the OIDs we need
61
62    static final String OID_BASIC_CONSTRAINTS = "2.5.29.19";
63
64    static final String OID_NETSCAPE_CERT_TYPE = "2.16.840.1.113730.1.1";
65
66    static final String OID_KEY_USAGE = "2.5.29.15";
67
68    static final String OID_EXTENDED_KEY_USAGE = "2.5.29.37";
69
70    static final String OID_EKU_ANY_USAGE = "2.5.29.37.0";
71
72    static final ObjectIdentifier OBJID_NETSCAPE_CERT_TYPE =
73        NetscapeCertTypeExtension.NetscapeCertType_Id;
74
75    private static final String NSCT_SSL_CA =
76                                NetscapeCertTypeExtension.SSL_CA;
77
78    private static final String NSCT_CODE_SIGNING_CA =
79                                NetscapeCertTypeExtension.OBJECT_SIGNING_CA;
80
81    /**
82     * The trusted certificates as:
83     * Map (X500Principal)subject of trusted cert -> List of X509Certificate
84     * The list is used because there may be multiple certificates
85     * with an identical subject DN.
86     */
87    private final Map<X500Principal, List<X509Certificate>>
88                                            trustedX500Principals;
89
90    /**
91     * Set of the trusted certificates. Present only for
92     * getTrustedCertificates().
93     */
94    private final Collection<X509Certificate> trustedCerts;
95
96    SimpleValidator(String variant, Collection<X509Certificate> trustedCerts) {
97        super(TYPE_SIMPLE, variant);
98        this.trustedCerts = trustedCerts;
99        trustedX500Principals =
100                        new HashMap<X500Principal, List<X509Certificate>>();
101        for (X509Certificate cert : trustedCerts) {
102            X500Principal principal = cert.getSubjectX500Principal();
103            List<X509Certificate> list = trustedX500Principals.get(principal);
104            if (list == null) {
105                // this actually should be a set, but duplicate entries
106                // are not a problem and we can avoid the Set overhead
107                list = new ArrayList<X509Certificate>(2);
108                trustedX500Principals.put(principal, list);
109            }
110            list.add(cert);
111        }
112    }
113
114    public Collection<X509Certificate> getTrustedCertificates() {
115        return trustedCerts;
116    }
117
118    /**
119     * Perform simple validation of chain. The arguments otherCerts and
120     * parameter are ignored.
121     */
122    @Override
123    X509Certificate[] engineValidate(X509Certificate[] chain,
124            Collection<X509Certificate> otherCerts,
125            List<byte[]> responseList,
126            AlgorithmConstraints constraints,
127            Object parameter) throws CertificateException {
128        if ((chain == null) || (chain.length == 0)) {
129            throw new CertificateException
130                ("null or zero-length certificate chain");
131        }
132
133        // make sure chain includes a trusted cert
134        chain = buildTrustedChain(chain);
135
136        @SuppressWarnings("deprecation")
137        Date date = validationDate;
138        if (date == null) {
139            date = new Date();
140        }
141
142        // create distrusted certificates checker
143        UntrustedChecker untrustedChecker = new UntrustedChecker();
144
145        // check if anchor is untrusted
146        X509Certificate anchorCert = chain[chain.length - 1];
147        try {
148            untrustedChecker.check(anchorCert);
149        } catch (CertPathValidatorException cpve) {
150            throw new ValidatorException(
151                "Untrusted certificate: "+ anchorCert.getSubjectX500Principal(),
152                ValidatorException.T_UNTRUSTED_CERT, anchorCert, cpve);
153        }
154
155        // create default algorithm constraints checker
156        TrustAnchor anchor = new TrustAnchor(anchorCert, null);
157        AlgorithmChecker defaultAlgChecker = new AlgorithmChecker(anchor);
158
159        // create application level algorithm constraints checker
160        AlgorithmChecker appAlgChecker = null;
161        if (constraints != null) {
162            appAlgChecker = new AlgorithmChecker(anchor, constraints);
163        }
164
165        // verify top down, starting at the certificate issued by
166        // the trust anchor
167        int maxPathLength = chain.length - 1;
168        for (int i = chain.length - 2; i >= 0; i--) {
169            X509Certificate issuerCert = chain[i + 1];
170            X509Certificate cert = chain[i];
171
172            // check untrusted certificate
173            try {
174                // Untrusted checker does not care about the unresolved
175                // critical extensions.
176                untrustedChecker.check(cert, Collections.<String>emptySet());
177            } catch (CertPathValidatorException cpve) {
178                throw new ValidatorException(
179                    "Untrusted certificate: " + cert.getSubjectX500Principal(),
180                    ValidatorException.T_UNTRUSTED_CERT, cert, cpve);
181            }
182
183            // check certificate algorithm
184            try {
185                // Algorithm checker does not care about the unresolved
186                // critical extensions.
187                defaultAlgChecker.check(cert, Collections.<String>emptySet());
188                if (appAlgChecker != null) {
189                    appAlgChecker.check(cert, Collections.<String>emptySet());
190                }
191            } catch (CertPathValidatorException cpve) {
192                throw new ValidatorException
193                        (ValidatorException.T_ALGORITHM_DISABLED, cert, cpve);
194            }
195
196            // no validity check for code signing certs
197            if ((variant.equals(VAR_CODE_SIGNING) == false)
198                        && (variant.equals(VAR_JCE_SIGNING) == false)) {
199                cert.checkValidity(date);
200            }
201
202            // check name chaining
203            if (cert.getIssuerX500Principal().equals(
204                        issuerCert.getSubjectX500Principal()) == false) {
205                throw new ValidatorException
206                        (ValidatorException.T_NAME_CHAINING, cert);
207            }
208
209            // check signature
210            try {
211                cert.verify(issuerCert.getPublicKey());
212            } catch (GeneralSecurityException e) {
213                throw new ValidatorException
214                        (ValidatorException.T_SIGNATURE_ERROR, cert, e);
215            }
216
217            // check extensions for CA certs
218            if (i != 0) {
219                maxPathLength = checkExtensions(cert, maxPathLength);
220            }
221        }
222
223        return chain;
224    }
225
226    private int checkExtensions(X509Certificate cert, int maxPathLen)
227            throws CertificateException {
228        Set<String> critSet = cert.getCriticalExtensionOIDs();
229        if (critSet == null) {
230            critSet = Collections.<String>emptySet();
231        }
232
233        // Check the basic constraints extension
234        int pathLenConstraint =
235                checkBasicConstraints(cert, critSet, maxPathLen);
236
237        // Check the key usage and extended key usage extensions
238        checkKeyUsage(cert, critSet);
239
240        // check Netscape certificate type extension
241        checkNetscapeCertType(cert, critSet);
242
243        if (!critSet.isEmpty()) {
244            throw new ValidatorException
245                ("Certificate contains unknown critical extensions: " + critSet,
246                ValidatorException.T_CA_EXTENSIONS, cert);
247        }
248
249        return pathLenConstraint;
250    }
251
252    private void checkNetscapeCertType(X509Certificate cert,
253            Set<String> critSet) throws CertificateException {
254        if (variant.equals(VAR_GENERIC)) {
255            // nothing
256        } else if (variant.equals(VAR_TLS_CLIENT)
257                || variant.equals(VAR_TLS_SERVER)) {
258            if (getNetscapeCertTypeBit(cert, NSCT_SSL_CA) == false) {
259                throw new ValidatorException
260                        ("Invalid Netscape CertType extension for SSL CA "
261                        + "certificate",
262                        ValidatorException.T_CA_EXTENSIONS, cert);
263            }
264            critSet.remove(OID_NETSCAPE_CERT_TYPE);
265        } else if (variant.equals(VAR_CODE_SIGNING)
266                || variant.equals(VAR_JCE_SIGNING)) {
267            if (getNetscapeCertTypeBit(cert, NSCT_CODE_SIGNING_CA) == false) {
268                throw new ValidatorException
269                        ("Invalid Netscape CertType extension for code "
270                        + "signing CA certificate",
271                        ValidatorException.T_CA_EXTENSIONS, cert);
272            }
273            critSet.remove(OID_NETSCAPE_CERT_TYPE);
274        } else {
275            throw new CertificateException("Unknown variant " + variant);
276        }
277    }
278
279    /**
280     * Get the value of the specified bit in the Netscape certificate type
281     * extension. If the extension is not present at all, we return true.
282     */
283    static boolean getNetscapeCertTypeBit(X509Certificate cert, String type) {
284        try {
285            NetscapeCertTypeExtension ext;
286            if (cert instanceof X509CertImpl) {
287                X509CertImpl certImpl = (X509CertImpl)cert;
288                ObjectIdentifier oid = OBJID_NETSCAPE_CERT_TYPE;
289                ext = (NetscapeCertTypeExtension)certImpl.getExtension(oid);
290                if (ext == null) {
291                    return true;
292                }
293            } else {
294                byte[] extVal = cert.getExtensionValue(OID_NETSCAPE_CERT_TYPE);
295                if (extVal == null) {
296                    return true;
297                }
298                DerInputStream in = new DerInputStream(extVal);
299                byte[] encoded = in.getOctetString();
300                encoded = new DerValue(encoded).getUnalignedBitString()
301                                                                .toByteArray();
302                ext = new NetscapeCertTypeExtension(encoded);
303            }
304            Boolean val = ext.get(type);
305            return val.booleanValue();
306        } catch (IOException e) {
307            return false;
308        }
309    }
310
311    private int checkBasicConstraints(X509Certificate cert,
312            Set<String> critSet, int maxPathLen) throws CertificateException {
313
314        critSet.remove(OID_BASIC_CONSTRAINTS);
315        int constraints = cert.getBasicConstraints();
316        // reject, if extension missing or not a CA (constraints == -1)
317        if (constraints < 0) {
318            throw new ValidatorException("End user tried to act as a CA",
319                ValidatorException.T_CA_EXTENSIONS, cert);
320        }
321
322        // if the certificate is self-issued, ignore the pathLenConstraint
323        // checking.
324        if (!X509CertImpl.isSelfIssued(cert)) {
325            if (maxPathLen <= 0) {
326                throw new ValidatorException("Violated path length constraints",
327                    ValidatorException.T_CA_EXTENSIONS, cert);
328            }
329
330            maxPathLen--;
331        }
332
333        if (maxPathLen > constraints) {
334            maxPathLen = constraints;
335        }
336
337        return maxPathLen;
338    }
339
340    /*
341     * Verify the key usage and extended key usage for intermediate
342     * certificates.
343     */
344    private void checkKeyUsage(X509Certificate cert, Set<String> critSet)
345            throws CertificateException {
346
347        critSet.remove(OID_KEY_USAGE);
348        // EKU irrelevant in CA certificates
349        critSet.remove(OID_EXTENDED_KEY_USAGE);
350
351        // check key usage extension
352        boolean[] keyUsageInfo = cert.getKeyUsage();
353        if (keyUsageInfo != null) {
354            // keyUsageInfo[5] is for keyCertSign.
355            if ((keyUsageInfo.length < 6) || (keyUsageInfo[5] == false)) {
356                throw new ValidatorException
357                        ("Wrong key usage: expected keyCertSign",
358                        ValidatorException.T_CA_EXTENSIONS, cert);
359            }
360        }
361    }
362
363    /**
364     * Build a trusted certificate chain. This method always returns a chain
365     * with a trust anchor as the final cert in the chain. If no trust anchor
366     * could be found, a CertificateException is thrown.
367     */
368    private X509Certificate[] buildTrustedChain(X509Certificate[] chain)
369            throws CertificateException {
370        List<X509Certificate> c = new ArrayList<X509Certificate>(chain.length);
371        // scan chain starting at EE cert
372        // if a trusted certificate is found, append it and return
373        for (int i = 0; i < chain.length; i++) {
374            X509Certificate cert = chain[i];
375            X509Certificate trustedCert = getTrustedCertificate(cert);
376            if (trustedCert != null) {
377                c.add(trustedCert);
378                return c.toArray(CHAIN0);
379            }
380            c.add(cert);
381        }
382
383        // check if we can append a trusted cert
384        X509Certificate cert = chain[chain.length - 1];
385        X500Principal subject = cert.getSubjectX500Principal();
386        X500Principal issuer = cert.getIssuerX500Principal();
387        List<X509Certificate> list = trustedX500Principals.get(issuer);
388        if (list != null) {
389            X509Certificate trustedCert = list.iterator().next();
390            c.add(trustedCert);
391            return c.toArray(CHAIN0);
392        }
393
394        // no trusted cert found, error
395        throw new ValidatorException(ValidatorException.T_NO_TRUST_ANCHOR);
396    }
397
398    /**
399     * Return a trusted certificate that matches the input certificate,
400     * or null if no such certificate can be found. This method also handles
401     * cases where a CA re-issues a trust anchor with the same public key and
402     * same subject and issuer names but a new validity period, etc.
403     */
404    private X509Certificate getTrustedCertificate(X509Certificate cert) {
405        Principal certSubjectName = cert.getSubjectX500Principal();
406        List<X509Certificate> list = trustedX500Principals.get(certSubjectName);
407        if (list == null) {
408            return null;
409        }
410
411        Principal certIssuerName = cert.getIssuerX500Principal();
412        PublicKey certPublicKey = cert.getPublicKey();
413
414        for (X509Certificate mycert : list) {
415            if (mycert.equals(cert)) {
416                return cert;
417            }
418            if (!mycert.getIssuerX500Principal().equals(certIssuerName)) {
419                continue;
420            }
421            if (!mycert.getPublicKey().equals(certPublicKey)) {
422                continue;
423            }
424
425            // All tests pass, this must be the one to use...
426            return mycert;
427        }
428        return null;
429    }
430
431}
432