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
26package sun.security.provider.certpath;
27
28import java.io.IOException;
29import java.security.GeneralSecurityException;
30import java.security.InvalidAlgorithmParameterException;
31import java.security.PublicKey;
32import java.security.cert.*;
33import java.security.cert.CertPathValidatorException.BasicReason;
34import java.security.cert.PKIXReason;
35import java.util.ArrayList;
36import java.util.Collection;
37import java.util.Collections;
38import java.util.List;
39import java.util.LinkedList;
40import java.util.Set;
41import javax.security.auth.x500.X500Principal;
42
43import sun.security.provider.certpath.PKIX.BuilderParams;
44import static sun.security.x509.PKIXExtensions.*;
45import 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 */
67public 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