Home | History | Annotate | Download | only in certpath
      1 /*
      2  * Copyright (c) 2000, 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 
     26 package sun.security.provider.certpath;
     27 
     28 import java.io.IOException;
     29 import java.security.GeneralSecurityException;
     30 import java.security.InvalidAlgorithmParameterException;
     31 import java.security.PublicKey;
     32 import java.security.cert.*;
     33 import java.security.cert.CertPathValidatorException.BasicReason;
     34 import java.security.cert.PKIXReason;
     35 import java.util.ArrayList;
     36 import java.util.Collection;
     37 import java.util.Collections;
     38 import java.util.List;
     39 import java.util.LinkedList;
     40 import java.util.Set;
     41 import javax.security.auth.x500.X500Principal;
     42 
     43 import sun.security.provider.certpath.PKIX.BuilderParams;
     44 import static sun.security.x509.PKIXExtensions.*;
     45 import sun.security.util.Debug;
     46 
     47 /**
     48  * This class builds certification paths in the forward direction.
     49  *
     50  * <p> If successful, it returns a certification path which has successfully
     51  * satisfied all the constraints and requirements specified in the
     52  * PKIXBuilderParameters object and has been validated according to the PKIX
     53  * path validation algorithm defined in RFC 3280.
     54  *
     55  * <p> This implementation uses a depth-first search approach to finding
     56  * certification paths. If it comes to a point in which it cannot find
     57  * any more certificates leading to the target OR the path length is too long
     58  * it backtracks to previous paths until the target has been found or
     59  * all possible paths have been exhausted.
     60  *
     61  * <p> This implementation is not thread-safe.
     62  *
     63  * @since       1.4
     64  * @author      Sean Mullan
     65  * @author      Yassir Elley
     66  */
     67 public final class SunCertPathBuilder extends CertPathBuilderSpi {
     68 
     69     private static final Debug debug = Debug.getInstance("certpath");
     70 
     71     /*
     72      * private objects shared by methods
     73      */
     74     private BuilderParams buildParams;
     75     private CertificateFactory cf;
     76     private boolean pathCompleted = false;
     77     private PolicyNode policyTreeResult;
     78     private TrustAnchor trustAnchor;
     79     private PublicKey finalPublicKey;
     80 
     81     /**
     82      * Create an instance of <code>SunCertPathBuilder</code>.
     83      *
     84      * @throws CertPathBuilderException if an error occurs
     85      */
     86     public SunCertPathBuilder() throws CertPathBuilderException {
     87         try {
     88             cf = CertificateFactory.getInstance("X.509");
     89         } catch (CertificateException e) {
     90             throw new CertPathBuilderException(e);
     91         }
     92     }
     93 
     94     @Override
     95     public CertPathChecker engineGetRevocationChecker() {
     96         return new RevocationChecker();
     97     }
     98 
     99     /**
    100      * Attempts to build a certification path using the Sun build
    101      * algorithm from a trusted anchor(s) to a target subject, which must both
    102      * be specified in the input parameter set. This method will
    103      * attempt to build in the forward direction: from the target to the CA.
    104      *
    105      * <p>The certification path that is constructed is validated
    106      * according to the PKIX specification.
    107      *
    108      * @param params the parameter set for building a path. Must be an instance
    109      *  of <code>PKIXBuilderParameters</code>.
    110      * @return a certification path builder result.
    111      * @exception CertPathBuilderException Exception thrown if builder is
    112      *  unable to build a complete certification path from the trusted anchor(s)
    113      *  to the target subject.
    114      * @throws InvalidAlgorithmParameterException if the given parameters are
    115      *  inappropriate for this certification path builder.
    116      */
    117     @Override
    118     public CertPathBuilderResult engineBuild(CertPathParameters params)
    119         throws CertPathBuilderException, InvalidAlgorithmParameterException {
    120 
    121         if (debug != null) {
    122             debug.println("SunCertPathBuilder.engineBuild(" + params + ")");
    123         }
    124 
    125         buildParams = PKIX.checkBuilderParams(params);
    126         return build();
    127     }
    128 
    129     private PKIXCertPathBuilderResult build() throws CertPathBuilderException {
    130         List<List<Vertex>> adjList = new ArrayList<>();
    131         PKIXCertPathBuilderResult result = buildCertPath(false, adjList);
    132         if (result == null) {
    133             if (debug != null) {
    134                 debug.println("SunCertPathBuilder.engineBuild: 2nd pass; " +
    135                               "try building again searching all certstores");
    136             }
    137             // try again
    138             adjList.clear();
    139             result = buildCertPath(true, adjList);
    140             if (result == null) {
    141                 throw new SunCertPathBuilderException("unable to find valid "
    142                     + "certification path to requested target",
    143                     new AdjacencyList(adjList));
    144             }
    145         }
    146         return result;
    147     }
    148 
    149     private PKIXCertPathBuilderResult buildCertPath(boolean searchAllCertStores,
    150                                                     List<List<Vertex>> adjList)
    151         throws CertPathBuilderException
    152     {
    153         // Init shared variables and build certification path
    154         pathCompleted = false;
    155         trustAnchor = null;
    156         finalPublicKey = null;
    157         policyTreeResult = null;
    158         LinkedList<X509Certificate> certPathList = new LinkedList<>();
    159         try {
    160             buildForward(adjList, certPathList, searchAllCertStores);
    161         } catch (GeneralSecurityException | IOException e) {
    162             if (debug != null) {
    163                 debug.println("SunCertPathBuilder.engineBuild() exception in "
    164                     + "build");
    165                 e.printStackTrace();
    166             }
    167             throw new SunCertPathBuilderException("unable to find valid "
    168                 + "certification path to requested target", e,
    169                 new AdjacencyList(adjList));
    170         }
    171 
    172         // construct SunCertPathBuilderResult
    173         try {
    174             if (pathCompleted) {
    175                 if (debug != null)
    176                     debug.println("SunCertPathBuilder.engineBuild() "
    177                                   + "pathCompleted");
    178 
    179                 // we must return a certpath which has the target
    180                 // as the first cert in the certpath - i.e. reverse
    181                 // the certPathList
    182                 Collections.reverse(certPathList);
    183 
    184                 return new SunCertPathBuilderResult(
    185                     cf.generateCertPath(certPathList), trustAnchor,
    186                     policyTreeResult, finalPublicKey,
    187                     new AdjacencyList(adjList));
    188             }
    189         } catch (CertificateException e) {
    190             if (debug != null) {
    191                 debug.println("SunCertPathBuilder.engineBuild() exception "
    192                               + "in wrap-up");
    193                 e.printStackTrace();
    194             }
    195             throw new SunCertPathBuilderException("unable to find valid "
    196                 + "certification path to requested target", e,
    197                 new AdjacencyList(adjList));
    198         }
    199 
    200         return null;
    201     }
    202 
    203     /*
    204      * Private build forward method.
    205      */
    206     private void buildForward(List<List<Vertex>> adjacencyList,
    207                               LinkedList<X509Certificate> certPathList,
    208                               boolean searchAllCertStores)
    209         throws GeneralSecurityException, IOException
    210     {
    211         if (debug != null) {
    212             debug.println("SunCertPathBuilder.buildForward()...");
    213         }
    214 
    215         /* Initialize current state */
    216         ForwardState currentState = new ForwardState();
    217         currentState.initState(buildParams.certPathCheckers());
    218 
    219         /* Initialize adjacency list */
    220         adjacencyList.clear();
    221         adjacencyList.add(new LinkedList<Vertex>());
    222 
    223         // Android-removed: Android doesn't use this mechanism for checking untrusted certificates
    224         // currentState.untrustedChecker = new UntrustedChecker();
    225 
    226         depthFirstSearchForward(buildParams.targetSubject(), currentState,
    227                                 new ForwardBuilder(buildParams,
    228                                                    searchAllCertStores),
    229                                 adjacencyList, certPathList);
    230     }
    231 
    232     /*
    233      * This method performs a depth first search for a certification
    234      * path while building forward which meets the requirements set in
    235      * the parameters object.
    236      * It uses an adjacency list to store all certificates which were
    237      * tried (i.e. at one time added to the path - they may not end up in
    238      * the final path if backtracking occurs). This information can
    239      * be used later to debug or demo the build.
    240      *
    241      * See "Data Structure and Algorithms, by Aho, Hopcroft, and Ullman"
    242      * for an explanation of the DFS algorithm.
    243      *
    244      * @param dN the distinguished name being currently searched for certs
    245      * @param currentState the current PKIX validation state
    246      */
    247     private void depthFirstSearchForward(X500Principal dN,
    248                                          ForwardState currentState,
    249                                          ForwardBuilder builder,
    250                                          List<List<Vertex>> adjList,
    251                                          LinkedList<X509Certificate> cpList)
    252         throws GeneralSecurityException, IOException
    253     {
    254         if (debug != null) {
    255             debug.println("SunCertPathBuilder.depthFirstSearchForward(" + dN
    256                           + ", " + currentState.toString() + ")");
    257         }
    258 
    259         /*
    260          * Find all the certificates issued to dN which
    261          * satisfy the PKIX certification path constraints.
    262          */
    263         Collection<X509Certificate> certs =
    264             builder.getMatchingCerts(currentState, buildParams.certStores());
    265         List<Vertex> vertices = addVertices(certs, adjList);
    266         if (debug != null) {
    267             debug.println("SunCertPathBuilder.depthFirstSearchForward(): "
    268                           + "certs.size=" + vertices.size());
    269         }
    270 
    271         /*
    272          * For each cert in the collection, verify anything
    273          * that hasn't been checked yet (signature, revocation, etc)
    274          * and check for loops. Call depthFirstSearchForward()
    275          * recursively for each good cert.
    276          */
    277 
    278                vertices:
    279         for (Vertex vertex : vertices) {
    280             /**
    281              * Restore state to currentState each time through the loop.
    282              * This is important because some of the user-defined
    283              * checkers modify the state, which MUST be restored if
    284              * the cert eventually fails to lead to the target and
    285              * the next matching cert is tried.
    286              */
    287             ForwardState nextState = (ForwardState) currentState.clone();
    288             X509Certificate cert = vertex.getCertificate();
    289 
    290             try {
    291                 builder.verifyCert(cert, nextState, cpList);
    292             } catch (GeneralSecurityException gse) {
    293                 if (debug != null) {
    294                     debug.println("SunCertPathBuilder.depthFirstSearchForward()"
    295                                   + ": validation failed: " + gse);
    296                     gse.printStackTrace();
    297                 }
    298                 vertex.setThrowable(gse);
    299                 continue;
    300             }
    301 
    302             /*
    303              * Certificate is good.
    304              * If cert completes the path,
    305              *    process userCheckers that don't support forward checking
    306              *    and process policies over whole path
    307              *    and backtrack appropriately if there is a failure
    308              * else if cert does not complete the path,
    309              *    add it to the path
    310              */
    311             if (builder.isPathCompleted(cert)) {
    312 
    313                 if (debug != null)
    314                     debug.println("SunCertPathBuilder.depthFirstSearchForward()"
    315                                   + ": commencing final verification");
    316 
    317                 List<X509Certificate> appendedCerts = new ArrayList<>(cpList);
    318 
    319                 /*
    320                  * if the trust anchor selected is specified as a trusted
    321                  * public key rather than a trusted cert, then verify this
    322                  * cert (which is signed by the trusted public key), but
    323                  * don't add it yet to the cpList
    324                  */
    325                 if (builder.trustAnchor.getTrustedCert() == null) {
    326                     appendedCerts.add(0, cert);
    327                 }
    328 
    329                 Set<String> initExpPolSet =
    330                     Collections.singleton(PolicyChecker.ANY_POLICY);
    331 
    332                 PolicyNodeImpl rootNode = new PolicyNodeImpl(null,
    333                     PolicyChecker.ANY_POLICY, null, false, initExpPolSet, false);
    334 
    335                 List<PKIXCertPathChecker> checkers = new ArrayList<>();
    336                 PolicyChecker policyChecker
    337                     = new PolicyChecker(buildParams.initialPolicies(),
    338                                         appendedCerts.size(),
    339                                         buildParams.explicitPolicyRequired(),
    340                                         buildParams.policyMappingInhibited(),
    341                                         buildParams.anyPolicyInhibited(),
    342                                         buildParams.policyQualifiersRejected(),
    343                                         rootNode);
    344                 checkers.add(policyChecker);
    345 
    346                 // add the algorithm checker
    347                 checkers.add(new AlgorithmChecker(builder.trustAnchor));
    348 
    349                 BasicChecker basicChecker = null;
    350                 if (nextState.keyParamsNeeded()) {
    351                     PublicKey rootKey = cert.getPublicKey();
    352                     if (builder.trustAnchor.getTrustedCert() == null) {
    353                         rootKey = builder.trustAnchor.getCAPublicKey();
    354                         if (debug != null)
    355                             debug.println(
    356                                 "SunCertPathBuilder.depthFirstSearchForward " +
    357                                 "using buildParams public key: " +
    358                                 rootKey.toString());
    359                     }
    360                     TrustAnchor anchor = new TrustAnchor
    361                         (cert.getSubjectX500Principal(), rootKey, null);
    362 
    363                     // add the basic checker
    364                     basicChecker = new BasicChecker(anchor, buildParams.date(),
    365                                                     buildParams.sigProvider(),
    366                                                     true);
    367                     checkers.add(basicChecker);
    368                 }
    369 
    370                 buildParams.setCertPath(cf.generateCertPath(appendedCerts));
    371 
    372                 boolean revCheckerAdded = false;
    373                 List<PKIXCertPathChecker> ckrs = buildParams.certPathCheckers();
    374                 for (PKIXCertPathChecker ckr : ckrs) {
    375                     if (ckr instanceof PKIXRevocationChecker) {
    376                         if (revCheckerAdded) {
    377                             throw new CertPathValidatorException(
    378                                 "Only one PKIXRevocationChecker can be specified");
    379                         }
    380                         revCheckerAdded = true;
    381                         // if it's our own, initialize it
    382                         if (ckr instanceof RevocationChecker) {
    383                             ((RevocationChecker)ckr).init(builder.trustAnchor,
    384                                                           buildParams);
    385                         }
    386                     }
    387                 }
    388                 // only add a RevocationChecker if revocation is enabled and
    389                 // a PKIXRevocationChecker has not already been added
    390                 if (buildParams.revocationEnabled() && !revCheckerAdded) {
    391                     checkers.add(new RevocationChecker(builder.trustAnchor,
    392                                                        buildParams));
    393                 }
    394 
    395                 checkers.addAll(ckrs);
    396 
    397                 // Why we don't need BasicChecker and RevocationChecker
    398                 // if nextState.keyParamsNeeded() is false?
    399 
    400                 for (int i = 0; i < appendedCerts.size(); i++) {
    401                     X509Certificate currCert = appendedCerts.get(i);
    402                     if (debug != null)
    403                         debug.println("current subject = "
    404                                       + currCert.getSubjectX500Principal());
    405                     Set<String> unresCritExts =
    406                         currCert.getCriticalExtensionOIDs();
    407                     if (unresCritExts == null) {
    408                         unresCritExts = Collections.<String>emptySet();
    409                     }
    410 
    411                     for (PKIXCertPathChecker currChecker : checkers) {
    412                         if (!currChecker.isForwardCheckingSupported()) {
    413                             if (i == 0) {
    414                                 currChecker.init(false);
    415 
    416                                 // The user specified
    417                                 // AlgorithmChecker may not be
    418                                 // able to set the trust anchor until now.
    419                                 if (currChecker instanceof AlgorithmChecker) {
    420                                     ((AlgorithmChecker)currChecker).
    421                                         trySetTrustAnchor(builder.trustAnchor);
    422                                 }
    423                             }
    424 
    425                             try {
    426                                 currChecker.check(currCert, unresCritExts);
    427                             } catch (CertPathValidatorException cpve) {
    428                                 if (debug != null)
    429                                     debug.println
    430                                     ("SunCertPathBuilder.depthFirstSearchForward(): " +
    431                                     "final verification failed: " + cpve);
    432                                 // If the target cert itself is revoked, we
    433                                 // cannot trust it. We can bail out here.
    434                                 if (buildParams.targetCertConstraints().match(currCert)
    435                                         && cpve.getReason() == BasicReason.REVOKED) {
    436                                     throw cpve;
    437                                 }
    438                                 vertex.setThrowable(cpve);
    439                                 continue vertices;
    440                             }
    441                         }
    442                     }
    443 
    444                     /*
    445                      * Remove extensions from user checkers that support
    446                      * forward checking. After this step, we will have
    447                      * removed all extensions that all user checkers
    448                      * are capable of processing.
    449                      */
    450                     for (PKIXCertPathChecker checker :
    451                          buildParams.certPathCheckers())
    452                     {
    453                         if (checker.isForwardCheckingSupported()) {
    454                             Set<String> suppExts =
    455                                 checker.getSupportedExtensions();
    456                             if (suppExts != null) {
    457                                 unresCritExts.removeAll(suppExts);
    458                             }
    459                         }
    460                     }
    461 
    462                     if (!unresCritExts.isEmpty()) {
    463                         unresCritExts.remove(BasicConstraints_Id.toString());
    464                         unresCritExts.remove(NameConstraints_Id.toString());
    465                         unresCritExts.remove(CertificatePolicies_Id.toString());
    466                         unresCritExts.remove(PolicyMappings_Id.toString());
    467                         unresCritExts.remove(PolicyConstraints_Id.toString());
    468                         unresCritExts.remove(InhibitAnyPolicy_Id.toString());
    469                         unresCritExts.remove(
    470                             SubjectAlternativeName_Id.toString());
    471                         unresCritExts.remove(KeyUsage_Id.toString());
    472                         unresCritExts.remove(ExtendedKeyUsage_Id.toString());
    473 
    474                         if (!unresCritExts.isEmpty()) {
    475                             throw new CertPathValidatorException
    476                                 ("unrecognized critical extension(s)", null,
    477                                  null, -1, PKIXReason.UNRECOGNIZED_CRIT_EXT);
    478                         }
    479                     }
    480                 }
    481                 if (debug != null)
    482                     debug.println("SunCertPathBuilder.depthFirstSearchForward()"
    483                         + ": final verification succeeded - path completed!");
    484                 pathCompleted = true;
    485 
    486                 /*
    487                  * if the user specified a trusted public key rather than
    488                  * trusted certs, then add this cert (which is signed by
    489                  * the trusted public key) to the cpList
    490                  */
    491                 if (builder.trustAnchor.getTrustedCert() == null)
    492                     builder.addCertToPath(cert, cpList);
    493                 // Save the trust anchor
    494                 this.trustAnchor = builder.trustAnchor;
    495 
    496                 /*
    497                  * Extract and save the final target public key
    498                  */
    499                 if (basicChecker != null) {
    500                     finalPublicKey = basicChecker.getPublicKey();
    501                 } else {
    502                     Certificate finalCert;
    503                     if (cpList.isEmpty()) {
    504                         finalCert = builder.trustAnchor.getTrustedCert();
    505                     } else {
    506                         finalCert = cpList.getLast();
    507                     }
    508                     finalPublicKey = finalCert.getPublicKey();
    509                 }
    510 
    511                 policyTreeResult = policyChecker.getPolicyTree();
    512                 return;
    513             } else {
    514                 builder.addCertToPath(cert, cpList);
    515             }
    516 
    517             /* Update the PKIX state */
    518             nextState.updateState(cert);
    519 
    520             /*
    521              * Append an entry for cert in adjacency list and
    522              * set index for current vertex.
    523              */
    524             adjList.add(new LinkedList<Vertex>());
    525             vertex.setIndex(adjList.size() - 1);
    526 
    527             /* recursively search for matching certs at next dN */
    528             depthFirstSearchForward(cert.getIssuerX500Principal(), nextState,
    529                                     builder, adjList, cpList);
    530 
    531             /*
    532              * If path has been completed, return ASAP!
    533              */
    534             if (pathCompleted) {
    535                 return;
    536             } else {
    537                 /*
    538                  * If we get here, it means we have searched all possible
    539                  * certs issued by the dN w/o finding any matching certs.
    540                  * This means we have to backtrack to the previous cert in
    541                  * the path and try some other paths.
    542                  */
    543                 if (debug != null)
    544                     debug.println("SunCertPathBuilder.depthFirstSearchForward()"
    545                                   + ": backtracking");
    546                 builder.removeFinalCertFromPath(cpList);
    547             }
    548         }
    549     }
    550 
    551     /*
    552      * Adds a collection of matching certificates to the
    553      * adjacency list.
    554      */
    555     private static List<Vertex> addVertices(Collection<X509Certificate> certs,
    556                                             List<List<Vertex>> adjList)
    557     {
    558         List<Vertex> l = adjList.get(adjList.size() - 1);
    559 
    560         for (X509Certificate cert : certs) {
    561             Vertex v = new Vertex(cert);
    562             l.add(v);
    563         }
    564 
    565         return l;
    566     }
    567 
    568     /**
    569      * Returns true if trust anchor certificate matches specified
    570      * certificate constraints.
    571      */
    572     private static boolean anchorIsTarget(TrustAnchor anchor,
    573                                           CertSelector sel)
    574     {
    575         X509Certificate anchorCert = anchor.getTrustedCert();
    576         if (anchorCert != null) {
    577             return sel.match(anchorCert);
    578         }
    579         return false;
    580     }
    581 }
    582