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
26package sun.security.provider.certpath;
27
28import java.io.IOException;
29import java.security.GeneralSecurityException;
30import java.security.Principal;
31import java.security.cert.CertificateException;
32import java.security.cert.CertPathValidatorException;
33import java.security.cert.CertStore;
34import java.security.cert.CertStoreException;
35import java.security.cert.PKIXBuilderParameters;
36import java.security.cert.PKIXCertPathChecker;
37import java.security.cert.PKIXParameters;
38import java.security.cert.PKIXReason;
39import java.security.cert.TrustAnchor;
40import java.security.cert.X509Certificate;
41import java.security.cert.X509CertSelector;
42import java.util.ArrayList;
43import java.util.Collection;
44import java.util.Collections;
45import java.util.Comparator;
46import java.util.HashSet;
47import java.util.Iterator;
48import java.util.List;
49import java.util.LinkedList;
50import java.util.Set;
51
52import javax.security.auth.x500.X500Principal;
53
54import sun.security.provider.certpath.PKIX.BuilderParams;
55import sun.security.util.Debug;
56import sun.security.x509.Extension;
57import static sun.security.x509.PKIXExtensions.*;
58import sun.security.x509.X500Name;
59import sun.security.x509.X509CertImpl;
60import 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
72class 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