• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
26 package sun.security.validator;
27 
28 import java.util.*;
29 
30 import java.security.*;
31 import java.security.cert.*;
32 
33 import javax.security.auth.x500.X500Principal;
34 import sun.security.action.GetBooleanAction;
35 import 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  */
52 public 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 
PKIXValidator(String variant, Collection<X509Certificate> trustedCerts)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 
PKIXValidator(String variant, PKIXBuilderParameters params)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 
getTrustedCertificates()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      */
getCertPathLength()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      */
setDefaultParameters(String variant)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      */
getParameters()197     public PKIXBuilderParameters getParameters() { // mutable, should be private
198         return parameterTemplate;
199     }
200 
201     @Override
engineValidate(X509Certificate[] chain, Collection<X509Certificate> otherCerts, AlgorithmConstraints constraints, Object parameter)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 
isSignatureValid(List<PublicKey> keys, X509Certificate sub)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 
toArray(CertPath path, TrustAnchor anchor)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      */
setDate(PKIXBuilderParameters params)329     private void setDate(PKIXBuilderParameters params) {
330         Date date = validationDate;
331         if (date != null) {
332             params.setDate(date);
333         }
334     }
335 
doValidate(X509Certificate[] chain, PKIXBuilderParameters params)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 
doBuild(X509Certificate[] chain, Collection<X509Certificate> otherCerts, PKIXBuilderParameters params)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