Home | History | Annotate | Download | only in certpath
      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 }
    552