View Javadoc
1   /*
2    * Copyright (c) 2000, 2013, 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.provider.certpath;
27  
28  import java.io.IOException;
29  import java.security.GeneralSecurityException;
30  import java.security.Principal;
31  import java.security.cert.CertificateException;
32  import java.security.cert.CertPathValidatorException;
33  import java.security.cert.CertStore;
34  import java.security.cert.CertStoreException;
35  import java.security.cert.PKIXBuilderParameters;
36  import java.security.cert.PKIXCertPathChecker;
37  import java.security.cert.PKIXParameters;
38  import java.security.cert.PKIXReason;
39  import java.security.cert.TrustAnchor;
40  import java.security.cert.X509Certificate;
41  import java.security.cert.X509CertSelector;
42  import java.util.ArrayList;
43  import java.util.Collection;
44  import java.util.Collections;
45  import java.util.Comparator;
46  import java.util.HashSet;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.LinkedList;
50  import java.util.Set;
51  
52  import javax.security.auth.x500.X500Principal;
53  
54  import sun.security.provider.certpath.PKIX.BuilderParams;
55  import sun.security.util.Debug;
56  import sun.security.x509.Extension;
57  import static sun.security.x509.PKIXExtensions.*;
58  import sun.security.x509.X500Name;
59  import sun.security.x509.X509CertImpl;
60  import sun.security.x509.PolicyMappingsExtension;
61  
62  /**
63   * This class represents a reverse builder, which is able to retrieve
64   * matching certificates from CertStores and verify a particular certificate
65   * against a ReverseState.
66   *
67   * @since       1.4
68   * @author      Sean Mullan
69   * @author      Yassir Elley
70   */
71  
72  class ReverseBuilder extends Builder {
73  
74      private Debug debug = Debug.getInstance("certpath");
75  
76      private final Set<String> initPolicies;
77  
78      /**
79       * Initialize the builder with the input parameters.
80       *
81       * @param params the parameter set used to build a certification path
82       */
83      ReverseBuilder(BuilderParams buildParams) {
84          super(buildParams);
85  
86          Set<String> initialPolicies = buildParams.initialPolicies();
87          initPolicies = new HashSet<String>();
88          if (initialPolicies.isEmpty()) {
89              // if no initialPolicies are specified by user, set
90              // initPolicies to be anyPolicy by default
91              initPolicies.add(PolicyChecker.ANY_POLICY);
92          } else {
93              initPolicies.addAll(initialPolicies);
94          }
95      }
96  
97      /**
98       * Retrieves all certs from the specified CertStores that satisfy the
99       * requirements specified in the parameters and the current
100      * PKIX state (name constraints, policy constraints, etc).
101      *
102      * @param currentState the current state.
103      *        Must be an instance of <code>ReverseState</code>
104      * @param certStores list of CertStores
105      */
106     @Override
107     Collection<X509Certificate> getMatchingCerts
108         (State currState, List<CertStore> certStores)
109         throws CertStoreException, CertificateException, IOException
110     {
111         ReverseState currentState = (ReverseState) currState;
112 
113         if (debug != null)
114             debug.println("In ReverseBuilder.getMatchingCerts.");
115 
116         /*
117          * The last certificate could be an EE or a CA certificate
118          * (we may be building a partial certification path or
119          * establishing trust in a CA).
120          *
121          * Try the EE certs before the CA certs. It will be more
122          * common to build a path to an end entity.
123          */
124         Collection<X509Certificate> certs =
125             getMatchingEECerts(currentState, certStores);
126         certs.addAll(getMatchingCACerts(currentState, certStores));
127 
128         return certs;
129     }
130 
131     /*
132      * Retrieves all end-entity certificates which satisfy constraints
133      * and requirements specified in the parameters and PKIX state.
134      */
135     private Collection<X509Certificate> getMatchingEECerts
136         (ReverseState currentState, List<CertStore> certStores)
137         throws CertStoreException, CertificateException, IOException {
138 
139         /*
140          * Compose a CertSelector to filter out
141          * certs which do not satisfy requirements.
142          *
143          * First, retrieve clone of current target cert constraints, and
144          * then add more selection criteria based on current validation state.
145          */
146         X509CertSelector sel = (X509CertSelector) targetCertConstraints.clone();
147 
148         /*
149          * Match on issuer (subject of previous cert)
150          */
151         sel.setIssuer(currentState.subjectDN);
152 
153         /*
154          * Match on certificate validity date.
155          */
156         sel.setCertificateValid(buildParams.date());
157 
158         /*
159          * Policy processing optimizations
160          */
161         if (currentState.explicitPolicy == 0)
162             sel.setPolicy(getMatchingPolicies());
163 
164         /*
165          * If previous cert has a subject key identifier extension,
166          * use it to match on authority key identifier extension.
167          */
168         /*if (currentState.subjKeyId != null) {
169           AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension(
170                 (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID),
171                 null, null);
172         sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue());
173         }*/
174 
175         /*
176          * Require EE certs
177          */
178         sel.setBasicConstraints(-2);
179 
180         /* Retrieve matching certs from CertStores */
181         HashSet<X509Certificate> eeCerts = new HashSet<>();
182         addMatchingCerts(sel, certStores, eeCerts, true);
183 
184         if (debug != null) {
185             debug.println("ReverseBuilder.getMatchingEECerts got "
186                           + eeCerts.size() + " certs.");
187         }
188         return eeCerts;
189     }
190 
191     /*
192      * Retrieves all CA certificates which satisfy constraints
193      * and requirements specified in the parameters and PKIX state.
194      */
195     private Collection<X509Certificate> getMatchingCACerts
196         (ReverseState currentState, List<CertStore> certStores)
197         throws CertificateException, CertStoreException, IOException {
198 
199         /*
200          * Compose a CertSelector to filter out
201          * certs which do not satisfy requirements.
202          */
203         X509CertSelector sel = new X509CertSelector();
204 
205         /*
206          * Match on issuer (subject of previous cert)
207          */
208         sel.setIssuer(currentState.subjectDN);
209 
210         /*
211          * Match on certificate validity date.
212          */
213         sel.setCertificateValid(buildParams.date());
214 
215         /*
216          * Match on target subject name (checks that current cert's
217          * name constraints permit it to certify target).
218          * (4 is the integer type for DIRECTORY name).
219          */
220         byte[] subject = targetCertConstraints.getSubjectAsBytes();
221         if (subject != null) {
222             sel.addPathToName(4, subject);
223         } else {
224             X509Certificate cert = targetCertConstraints.getCertificate();
225             if (cert != null) {
226                 sel.addPathToName(4,
227                                   cert.getSubjectX500Principal().getEncoded());
228             }
229         }
230 
231         /*
232          * Policy processing optimizations
233          */
234         if (currentState.explicitPolicy == 0)
235             sel.setPolicy(getMatchingPolicies());
236 
237         /*
238          * If previous cert has a subject key identifier extension,
239          * use it to match on authority key identifier extension.
240          */
241         /*if (currentState.subjKeyId != null) {
242           AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension(
243                 (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID),
244                                 null, null);
245           sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue());
246         }*/
247 
248         /*
249          * Require CA certs
250          */
251         sel.setBasicConstraints(0);
252 
253         /* Retrieve matching certs from CertStores */
254         ArrayList<X509Certificate> reverseCerts = new ArrayList<>();
255         addMatchingCerts(sel, certStores, reverseCerts, true);
256 
257         /* Sort remaining certs using name constraints */
258         Collections.sort(reverseCerts, new PKIXCertComparator());
259 
260         if (debug != null)
261             debug.println("ReverseBuilder.getMatchingCACerts got " +
262                           reverseCerts.size() + " certs.");
263         return reverseCerts;
264     }
265 
266     /*
267      * This inner class compares 2 PKIX certificates according to which
268      * should be tried first when building a path to the target. For
269      * now, the algorithm is to look at name constraints in each cert and those
270      * which constrain the path closer to the target should be
271      * ranked higher. Later, we may want to consider other components,
272      * such as key identifiers.
273      */
274     class PKIXCertComparator implements Comparator<X509Certificate> {
275 
276         private Debug debug = Debug.getInstance("certpath");
277 
278         @Override
279         public int compare(X509Certificate cert1, X509Certificate cert2) {
280 
281             /*
282              * if either cert certifies the target, always
283              * put at head of list.
284              */
285             X500Principal targetSubject = buildParams.targetSubject();
286             if (cert1.getSubjectX500Principal().equals(targetSubject)) {
287                 return -1;
288             }
289             if (cert2.getSubjectX500Principal().equals(targetSubject)) {
290                 return 1;
291             }
292 
293             int targetDist1;
294             int targetDist2;
295             try {
296                 X500Name targetSubjectName = X500Name.asX500Name(targetSubject);
297                 targetDist1 = Builder.targetDistance(
298                     null, cert1, targetSubjectName);
299                 targetDist2 = Builder.targetDistance(
300                     null, cert2, targetSubjectName);
301             } catch (IOException e) {
302                 if (debug != null) {
303                     debug.println("IOException in call to Builder.targetDistance");
304                     e.printStackTrace();
305                 }
306                 throw new ClassCastException
307                     ("Invalid target subject distinguished name");
308             }
309 
310             if (targetDist1 == targetDist2)
311                 return 0;
312 
313             if (targetDist1 == -1)
314                 return 1;
315 
316             if (targetDist1 < targetDist2)
317                 return -1;
318 
319             return 1;
320         }
321     }
322 
323     /**
324      * Verifies a matching certificate.
325      *
326      * This method executes any of the validation steps in the PKIX path validation
327      * algorithm which were not satisfied via filtering out non-compliant
328      * certificates with certificate matching rules.
329      *
330      * If the last certificate is being verified (the one whose subject
331      * matches the target subject, then the steps in Section 6.1.4 of the
332      * Certification Path Validation algorithm are NOT executed,
333      * regardless of whether or not the last cert is an end-entity
334      * cert or not. This allows callers to certify CA certs as
335      * well as EE certs.
336      *
337      * @param cert the certificate to be verified
338      * @param currentState the current state against which the cert is verified
339      * @param certPathList the certPathList generated thus far
340      */
341     @Override
342     void verifyCert(X509Certificate cert, State currState,
343         List<X509Certificate> certPathList)
344         throws GeneralSecurityException
345     {
346         if (debug != null) {
347             debug.println("ReverseBuilder.verifyCert(SN: "
348                 + Debug.toHexString(cert.getSerialNumber())
349                 + "\n  Subject: " + cert.getSubjectX500Principal() + ")");
350         }
351 
352         ReverseState currentState = (ReverseState) currState;
353 
354         /* we don't perform any validation of the trusted cert */
355         if (currentState.isInitial()) {
356             return;
357         }
358 
359         // Don't bother to verify untrusted certificate more.
360         currentState.untrustedChecker.check(cert,
361                                     Collections.<String>emptySet());
362 
363         /*
364          * check for looping - abort a loop if
365          * ((we encounter the same certificate twice) AND
366          * ((policyMappingInhibited = true) OR (no policy mapping
367          * extensions can be found between the occurrences of the same
368          * certificate)))
369          * in order to facilitate the check to see if there are
370          * any policy mapping extensions found between the occurrences
371          * of the same certificate, we reverse the certpathlist first
372          */
373         if ((certPathList != null) && (!certPathList.isEmpty())) {
374             List<X509Certificate> reverseCertList = new ArrayList<>();
375             for (X509Certificate c : certPathList) {
376                 reverseCertList.add(0, c);
377             }
378 
379             boolean policyMappingFound = false;
380             for (X509Certificate cpListCert : reverseCertList) {
381                 X509CertImpl cpListCertImpl = X509CertImpl.toImpl(cpListCert);
382                 PolicyMappingsExtension policyMappingsExt =
383                         cpListCertImpl.getPolicyMappingsExtension();
384                 if (policyMappingsExt != null) {
385                     policyMappingFound = true;
386                 }
387                 if (debug != null)
388                     debug.println("policyMappingFound = " + policyMappingFound);
389                 if (cert.equals(cpListCert)) {
390                     if ((buildParams.policyMappingInhibited()) ||
391                         (!policyMappingFound)){
392                         if (debug != null)
393                             debug.println("loop detected!!");
394                         throw new CertPathValidatorException("loop detected");
395                     }
396                 }
397             }
398         }
399 
400         /* check if target cert */
401         boolean finalCert = cert.getSubjectX500Principal().equals(buildParams.targetSubject());
402 
403         /* check if CA cert */
404         boolean caCert = (cert.getBasicConstraints() != -1 ? true : false);
405 
406         /* if there are more certs to follow, verify certain constraints */
407         if (!finalCert) {
408 
409             /* check if CA cert */
410             if (!caCert)
411                 throw new CertPathValidatorException("cert is NOT a CA cert");
412 
413             /* If the certificate was not self-issued, verify that
414              * remainingCerts is greater than zero
415              */
416             if ((currentState.remainingCACerts <= 0) && !X509CertImpl.isSelfIssued(cert)) {
417                     throw new CertPathValidatorException
418                         ("pathLenConstraint violated, path too long", null,
419                          null, -1, PKIXReason.PATH_TOO_LONG);
420             }
421 
422             /*
423              * Check keyUsage extension (only if CA cert and not final cert)
424              */
425             KeyChecker.verifyCAKeyUsage(cert);
426 
427         } else {
428 
429             /*
430              * If final cert, check that it satisfies specified target
431              * constraints
432              */
433             if (targetCertConstraints.match(cert) == false) {
434                 throw new CertPathValidatorException("target certificate " +
435                     "constraints check failed");
436             }
437         }
438 
439         /*
440          * Check revocation.
441          */
442         if (buildParams.revocationEnabled() && currentState.revChecker != null) {
443             currentState.revChecker.check(cert, Collections.<String>emptySet());
444         }
445 
446         /* Check name constraints if this is not a self-issued cert */
447         if (finalCert || !X509CertImpl.isSelfIssued(cert)){
448             if (currentState.nc != null) {
449                 try {
450                     if (!currentState.nc.verify(cert)){
451                         throw new CertPathValidatorException
452                             ("name constraints check failed", null, null, -1,
453                              PKIXReason.INVALID_NAME);
454                     }
455                 } catch (IOException ioe) {
456                     throw new CertPathValidatorException(ioe);
457                 }
458             }
459         }
460 
461         /*
462          * Check policy
463          */
464         X509CertImpl certImpl = X509CertImpl.toImpl(cert);
465         currentState.rootNode = PolicyChecker.processPolicies
466             (currentState.certIndex, initPolicies,
467             currentState.explicitPolicy, currentState.policyMapping,
468             currentState.inhibitAnyPolicy,
469             buildParams.policyQualifiersRejected(), currentState.rootNode,
470             certImpl, finalCert);
471 
472         /*
473          * Check CRITICAL private extensions
474          */
475         Set<String> unresolvedCritExts = cert.getCriticalExtensionOIDs();
476         if (unresolvedCritExts == null) {
477             unresolvedCritExts = Collections.<String>emptySet();
478         }
479 
480         /*
481          * Check that the signature algorithm is not disabled.
482          */
483         currentState.algorithmChecker.check(cert, unresolvedCritExts);
484 
485         for (PKIXCertPathChecker checker : currentState.userCheckers) {
486             checker.check(cert, unresolvedCritExts);
487         }
488 
489         /*
490          * Look at the remaining extensions and remove any ones we have
491          * already checked. If there are any left, throw an exception!
492          */
493         if (!unresolvedCritExts.isEmpty()) {
494             unresolvedCritExts.remove(BasicConstraints_Id.toString());
495             unresolvedCritExts.remove(NameConstraints_Id.toString());
496             unresolvedCritExts.remove(CertificatePolicies_Id.toString());
497             unresolvedCritExts.remove(PolicyMappings_Id.toString());
498             unresolvedCritExts.remove(PolicyConstraints_Id.toString());
499             unresolvedCritExts.remove(InhibitAnyPolicy_Id.toString());
500             unresolvedCritExts.remove(SubjectAlternativeName_Id.toString());
501             unresolvedCritExts.remove(KeyUsage_Id.toString());
502             unresolvedCritExts.remove(ExtendedKeyUsage_Id.toString());
503 
504             if (!unresolvedCritExts.isEmpty())
505                 throw new CertPathValidatorException
506                     ("Unrecognized critical extension(s)", null, null, -1,
507                      PKIXReason.UNRECOGNIZED_CRIT_EXT);
508         }
509 
510         /*
511          * Check signature.
512          */
513         if (buildParams.sigProvider() != null) {
514             cert.verify(currentState.pubKey, buildParams.sigProvider());
515         } else {
516             cert.verify(currentState.pubKey);
517         }
518     }
519 
520     /**
521      * Verifies whether the input certificate completes the path.
522      * This checks whether the cert is the target certificate.
523      *
524      * @param cert the certificate to test
525      * @return a boolean value indicating whether the cert completes the path.
526      */
527     @Override
528     boolean isPathCompleted(X509Certificate cert) {
529         return cert.getSubjectX500Principal().equals(buildParams.targetSubject());
530     }
531 
532     /** Adds the certificate to the certPathList
533      *
534      * @param cert the certificate to be added
535      * @param certPathList the certification path list
536      */
537     @Override
538     void addCertToPath(X509Certificate cert,
539         LinkedList<X509Certificate> certPathList) {
540         certPathList.addLast(cert);
541     }
542 
543     /** Removes final certificate from the certPathList
544      *
545      * @param certPathList the certification path list
546      */
547     @Override
548     void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) {
549         certPathList.removeLast();
550     }
551 }