1/*
2 * Copyright (c) 2002, 2010, 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.util.*;
29
30import java.security.*;
31import java.security.cert.*;
32
33import javax.security.auth.x500.X500Principal;
34import sun.security.action.GetBooleanAction;
35import sun.security.provider.certpath.AlgorithmChecker;
36
37/**
38 * Validator implementation built on the PKIX CertPath API. This
39 * implementation will be emphasized going forward.<p>
40 * <p>
41 * Note that the validate() implementation tries to use a PKIX validator
42 * if that appears possible and a PKIX builder otherwise. This increases
43 * performance and currently also leads to better exception messages
44 * in case of failures.
45 * <p>
46 * {@code PKIXValidator} objects are immutable once they have been created.
47 * Please DO NOT add methods that can change the state of an instance once
48 * it has been created.
49 *
50 * @author Andreas Sterbenz
51 */
52public final class PKIXValidator extends Validator {
53
54    /**
55     * Flag indicating whether to enable revocation check for the PKIX trust
56     * manager. Typically, this will only work if the PKIX implementation
57     * supports CRL distribution points as we do not manually setup CertStores.
58     */
59    private final static boolean checkTLSRevocation =
60        AccessController.doPrivileged
61            (new GetBooleanAction("com.sun.net.ssl.checkRevocation"));
62
63    // enable use of the validator if possible
64    private final static boolean TRY_VALIDATOR = true;
65
66    private final Set<X509Certificate> trustedCerts;
67    private final PKIXBuilderParameters parameterTemplate;
68    private int certPathLength = -1;
69
70    // needed only for the validator
71    private final Map<X500Principal, List<PublicKey>> trustedSubjects;
72    private final CertificateFactory factory;
73
74    private final boolean plugin;
75
76    PKIXValidator(String variant, Collection<X509Certificate> trustedCerts) {
77        super(TYPE_PKIX, variant);
78        if (trustedCerts instanceof Set) {
79            this.trustedCerts = (Set<X509Certificate>)trustedCerts;
80        } else {
81            this.trustedCerts = new HashSet<X509Certificate>(trustedCerts);
82        }
83        Set<TrustAnchor> trustAnchors = new HashSet<TrustAnchor>();
84        for (X509Certificate cert : trustedCerts) {
85            trustAnchors.add(new TrustAnchor(cert, null));
86        }
87        try {
88            parameterTemplate = new PKIXBuilderParameters(trustAnchors, null);
89        } catch (InvalidAlgorithmParameterException e) {
90            throw new RuntimeException("Unexpected error: " + e.toString(), e);
91        }
92        setDefaultParameters(variant);
93
94        // initCommon();
95        if (TRY_VALIDATOR) {
96            if (TRY_VALIDATOR == false) {
97                return;
98            }
99            trustedSubjects = new HashMap<X500Principal, List<PublicKey>>();
100            for (X509Certificate cert : trustedCerts) {
101                X500Principal dn = cert.getSubjectX500Principal();
102                List<PublicKey> keys;
103                if (trustedSubjects.containsKey(dn)) {
104                    keys = trustedSubjects.get(dn);
105                } else {
106                    keys = new ArrayList<PublicKey>();
107                    trustedSubjects.put(dn, keys);
108                }
109                keys.add(cert.getPublicKey());
110            }
111            try {
112                factory = CertificateFactory.getInstance("X.509");
113            } catch (CertificateException e) {
114                throw new RuntimeException("Internal error", e);
115            }
116            plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
117        } else {
118            plugin = false;
119        }
120    }
121
122    PKIXValidator(String variant, PKIXBuilderParameters params) {
123        super(TYPE_PKIX, variant);
124        trustedCerts = new HashSet<X509Certificate>();
125        for (TrustAnchor anchor : params.getTrustAnchors()) {
126            X509Certificate cert = anchor.getTrustedCert();
127            if (cert != null) {
128                trustedCerts.add(cert);
129            }
130        }
131        parameterTemplate = params;
132
133        // initCommon();
134        if (TRY_VALIDATOR) {
135            if (TRY_VALIDATOR == false) {
136                return;
137            }
138            trustedSubjects = new HashMap<X500Principal, List<PublicKey>>();
139            for (X509Certificate cert : trustedCerts) {
140                X500Principal dn = cert.getSubjectX500Principal();
141                List<PublicKey> keys;
142                if (trustedSubjects.containsKey(dn)) {
143                    keys = trustedSubjects.get(dn);
144                } else {
145                    keys = new ArrayList<PublicKey>();
146                    trustedSubjects.put(dn, keys);
147                }
148                keys.add(cert.getPublicKey());
149            }
150            try {
151                factory = CertificateFactory.getInstance("X.509");
152            } catch (CertificateException e) {
153                throw new RuntimeException("Internal error", e);
154            }
155            plugin = variant.equals(VAR_PLUGIN_CODE_SIGNING);
156        } else {
157            plugin = false;
158        }
159    }
160
161    public Collection<X509Certificate> getTrustedCertificates() {
162        return trustedCerts;
163    }
164
165    /**
166     * Returns the length of the last certification path that is validated by
167     * CertPathValidator. This is intended primarily as a callback mechanism
168     * for PKIXCertPathCheckers to determine the length of the certification
169     * path that is being validated. It is necessary since engineValidate()
170     * may modify the length of the path.
171     *
172     * @return the length of the last certification path passed to
173     *   CertPathValidator.validate, or -1 if it has not been invoked yet
174     */
175    public int getCertPathLength() { // mutable, should be private
176        return certPathLength;
177    }
178
179    /**
180     * Set J2SE global default PKIX parameters. Currently, hardcoded to disable
181     * revocation checking. In the future, this should be configurable.
182     */
183    private void setDefaultParameters(String variant) {
184        if ((variant == Validator.VAR_TLS_SERVER) ||
185                (variant == Validator.VAR_TLS_CLIENT)) {
186            parameterTemplate.setRevocationEnabled(checkTLSRevocation);
187        } else {
188            parameterTemplate.setRevocationEnabled(false);
189        }
190    }
191
192    /**
193     * Return the PKIX parameters used by this instance. An application may
194     * modify the parameters but must make sure not to perform any concurrent
195     * validations.
196     */
197    public PKIXBuilderParameters getParameters() { // mutable, should be private
198        return parameterTemplate;
199    }
200
201    @Override
202    X509Certificate[] engineValidate(X509Certificate[] chain,
203            Collection<X509Certificate> otherCerts,
204            AlgorithmConstraints constraints,
205            Object parameter) throws CertificateException {
206        if ((chain == null) || (chain.length == 0)) {
207            throw new CertificateException
208                ("null or zero-length certificate chain");
209        }
210
211        // add  new algorithm constraints checker
212        PKIXBuilderParameters pkixParameters =
213                    (PKIXBuilderParameters) parameterTemplate.clone();
214        AlgorithmChecker algorithmChecker = null;
215        if (constraints != null) {
216            algorithmChecker = new AlgorithmChecker(constraints);
217            pkixParameters.addCertPathChecker(algorithmChecker);
218        }
219
220        if (TRY_VALIDATOR) {
221            // check that chain is in correct order and check if chain contains
222            // trust anchor
223            X500Principal prevIssuer = null;
224            for (int i = 0; i < chain.length; i++) {
225                X509Certificate cert = chain[i];
226                X500Principal dn = cert.getSubjectX500Principal();
227                if (i != 0 &&
228                    !dn.equals(prevIssuer)) {
229                    // chain is not ordered correctly, call builder instead
230                    return doBuild(chain, otherCerts, pkixParameters);
231                }
232
233                // Check if chain[i] is already trusted. It may be inside
234                // trustedCerts, or has the same dn and public key as a cert
235                // inside trustedCerts. The latter happens when a CA has
236                // updated its cert with a stronger signature algorithm in JRE
237                // but the weak one is still in circulation.
238
239                if (trustedCerts.contains(cert) ||          // trusted cert
240                        (trustedSubjects.containsKey(dn) && // replacing ...
241                         trustedSubjects.get(dn).contains(  // ... weak cert
242                            cert.getPublicKey()))) {
243                    if (i == 0) {
244                        return new X509Certificate[] {chain[0]};
245                    }
246                    // Remove and call validator on partial chain [0 .. i-1]
247                    X509Certificate[] newChain = new X509Certificate[i];
248                    System.arraycopy(chain, 0, newChain, 0, i);
249                    return doValidate(newChain, pkixParameters);
250                }
251                prevIssuer = cert.getIssuerX500Principal();
252            }
253
254            // apparently issued by trust anchor?
255            X509Certificate last = chain[chain.length - 1];
256            X500Principal issuer = last.getIssuerX500Principal();
257            X500Principal subject = last.getSubjectX500Principal();
258            if (trustedSubjects.containsKey(issuer) &&
259                    isSignatureValid(trustedSubjects.get(issuer), last)) {
260                return doValidate(chain, pkixParameters);
261            }
262
263            // don't fallback to builder if called from plugin/webstart
264            if (plugin) {
265                // Validate chain even if no trust anchor is found. This
266                // allows plugin/webstart to make sure the chain is
267                // otherwise valid
268                if (chain.length > 1) {
269                    X509Certificate[] newChain =
270                        new X509Certificate[chain.length-1];
271                    System.arraycopy(chain, 0, newChain, 0, newChain.length);
272
273                    // temporarily set last cert as sole trust anchor
274                    try {
275                        pkixParameters.setTrustAnchors
276                            (Collections.singleton(new TrustAnchor
277                                (chain[chain.length-1], null)));
278                    } catch (InvalidAlgorithmParameterException iape) {
279                        // should never occur, but ...
280                        throw new CertificateException(iape);
281                    }
282                    doValidate(newChain, pkixParameters);
283                }
284                // if the rest of the chain is valid, throw exception
285                // indicating no trust anchor was found
286                throw new ValidatorException
287                    (ValidatorException.T_NO_TRUST_ANCHOR);
288            }
289            // otherwise, fall back to builder
290        }
291
292        return doBuild(chain, otherCerts, pkixParameters);
293    }
294
295    private boolean isSignatureValid(List<PublicKey> keys,
296            X509Certificate sub) {
297        if (plugin) {
298            for (PublicKey key: keys) {
299                try {
300                    sub.verify(key);
301                    return true;
302                } catch (Exception ex) {
303                    continue;
304                }
305            }
306            return false;
307        }
308        return true; // only check if PLUGIN is set
309    }
310
311    private static X509Certificate[] toArray(CertPath path, TrustAnchor anchor)
312            throws CertificateException {
313        List<? extends java.security.cert.Certificate> list =
314                                                path.getCertificates();
315        X509Certificate[] chain = new X509Certificate[list.size() + 1];
316        list.toArray(chain);
317        X509Certificate trustedCert = anchor.getTrustedCert();
318        if (trustedCert == null) {
319            throw new ValidatorException
320                ("TrustAnchor must be specified as certificate");
321        }
322        chain[chain.length - 1] = trustedCert;
323        return chain;
324    }
325
326    /**
327     * Set the check date (for debugging).
328     */
329    private void setDate(PKIXBuilderParameters params) {
330        Date date = validationDate;
331        if (date != null) {
332            params.setDate(date);
333        }
334    }
335
336    private X509Certificate[] doValidate(X509Certificate[] chain,
337            PKIXBuilderParameters params) throws CertificateException {
338        try {
339            setDate(params);
340
341            // do the validation
342            CertPathValidator validator = CertPathValidator.getInstance("PKIX");
343            CertPath path = factory.generateCertPath(Arrays.asList(chain));
344            certPathLength = chain.length;
345            PKIXCertPathValidatorResult result =
346                (PKIXCertPathValidatorResult)validator.validate(path, params);
347
348            return toArray(path, result.getTrustAnchor());
349        } catch (GeneralSecurityException e) {
350            throw new ValidatorException
351                ("PKIX path validation failed: " + e.toString(), e);
352        }
353    }
354
355    private X509Certificate[] doBuild(X509Certificate[] chain,
356        Collection<X509Certificate> otherCerts,
357        PKIXBuilderParameters params) throws CertificateException {
358
359        try {
360            setDate(params);
361
362            // setup target constraints
363            X509CertSelector selector = new X509CertSelector();
364            selector.setCertificate(chain[0]);
365            params.setTargetCertConstraints(selector);
366
367            // setup CertStores
368            Collection<X509Certificate> certs =
369                                        new ArrayList<X509Certificate>();
370            certs.addAll(Arrays.asList(chain));
371            if (otherCerts != null) {
372                certs.addAll(otherCerts);
373            }
374            CertStore store = CertStore.getInstance("Collection",
375                                new CollectionCertStoreParameters(certs));
376            params.addCertStore(store);
377
378            // do the build
379            CertPathBuilder builder = CertPathBuilder.getInstance("PKIX");
380            PKIXCertPathBuilderResult result =
381                (PKIXCertPathBuilderResult)builder.build(params);
382
383            return toArray(result.getCertPath(), result.getTrustAnchor());
384        } catch (GeneralSecurityException e) {
385            throw new ValidatorException
386                ("PKIX path building failed: " + e.toString(), e);
387        }
388    }
389}
390