/*
 * Copyright (c) 2000, 2012, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */

package sun.security.provider.certpath;

import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.Principal;
import java.security.cert.CertificateException;
import java.security.cert.CertPathValidatorException;
import java.security.cert.CertStore;
import java.security.cert.CertStoreException;
import java.security.cert.PKIXBuilderParameters;
import java.security.cert.PKIXCertPathChecker;
import java.security.cert.PKIXParameters;
import java.security.cert.PKIXReason;
import java.security.cert.TrustAnchor;
import java.security.cert.X509Certificate;
import java.security.cert.X509CertSelector;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.LinkedList;
import java.util.Set;

import javax.security.auth.x500.X500Principal;

import sun.security.provider.certpath.PKIX.BuilderParams;
import sun.security.util.Debug;
import sun.security.x509.Extension;
import static sun.security.x509.PKIXExtensions.*;
import sun.security.x509.X500Name;
import sun.security.x509.X509CertImpl;
import sun.security.x509.PolicyMappingsExtension;

This class represents a reverse builder, which is able to retrieve matching certificates from CertStores and verify a particular certificate against a ReverseState.
Author: Sean Mullan, Yassir Elley
Since: 1.4
/** * This class represents a reverse builder, which is able to retrieve * matching certificates from CertStores and verify a particular certificate * against a ReverseState. * * @since 1.4 * @author Sean Mullan * @author Yassir Elley */
class ReverseBuilder extends Builder { private Debug debug = Debug.getInstance("certpath"); private final Set<String> initPolicies;
Initialize the builder with the input parameters.
Params:
  • params – the parameter set used to build a certification path
/** * Initialize the builder with the input parameters. * * @param params the parameter set used to build a certification path */
ReverseBuilder(BuilderParams buildParams) { super(buildParams); Set<String> initialPolicies = buildParams.initialPolicies(); initPolicies = new HashSet<String>(); if (initialPolicies.isEmpty()) { // if no initialPolicies are specified by user, set // initPolicies to be anyPolicy by default initPolicies.add(PolicyChecker.ANY_POLICY); } else { initPolicies.addAll(initialPolicies); } }
Retrieves all certs from the specified CertStores that satisfy the requirements specified in the parameters and the current PKIX state (name constraints, policy constraints, etc).
Params:
  • currentState – the current state. Must be an instance of ReverseState
  • certStores – list of CertStores
/** * Retrieves all certs from the specified CertStores that satisfy the * requirements specified in the parameters and the current * PKIX state (name constraints, policy constraints, etc). * * @param currentState the current state. * Must be an instance of <code>ReverseState</code> * @param certStores list of CertStores */
@Override Collection<X509Certificate> getMatchingCerts (State currState, List<CertStore> certStores) throws CertStoreException, CertificateException, IOException { ReverseState currentState = (ReverseState) currState; if (debug != null) debug.println("In ReverseBuilder.getMatchingCerts."); /* * The last certificate could be an EE or a CA certificate * (we may be building a partial certification path or * establishing trust in a CA). * * Try the EE certs before the CA certs. It will be more * common to build a path to an end entity. */ Collection<X509Certificate> certs = getMatchingEECerts(currentState, certStores); certs.addAll(getMatchingCACerts(currentState, certStores)); return certs; } /* * Retrieves all end-entity certificates which satisfy constraints * and requirements specified in the parameters and PKIX state. */ private Collection<X509Certificate> getMatchingEECerts (ReverseState currentState, List<CertStore> certStores) throws CertStoreException, CertificateException, IOException { /* * Compose a CertSelector to filter out * certs which do not satisfy requirements. * * First, retrieve clone of current target cert constraints, and * then add more selection criteria based on current validation state. */ X509CertSelector sel = (X509CertSelector) targetCertConstraints.clone(); /* * Match on issuer (subject of previous cert) */ sel.setIssuer(currentState.subjectDN); /* * Match on certificate validity date. */ sel.setCertificateValid(buildParams.date()); /* * Policy processing optimizations */ if (currentState.explicitPolicy == 0) sel.setPolicy(getMatchingPolicies()); /* * If previous cert has a subject key identifier extension, * use it to match on authority key identifier extension. */ /*if (currentState.subjKeyId != null) { AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension( (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID), null, null); sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue()); }*/ /* * Require EE certs */ sel.setBasicConstraints(-2); /* Retrieve matching certs from CertStores */ HashSet<X509Certificate> eeCerts = new HashSet<>(); addMatchingCerts(sel, certStores, eeCerts, true); if (debug != null) { debug.println("ReverseBuilder.getMatchingEECerts got " + eeCerts.size() + " certs."); } return eeCerts; } /* * Retrieves all CA certificates which satisfy constraints * and requirements specified in the parameters and PKIX state. */ private Collection<X509Certificate> getMatchingCACerts (ReverseState currentState, List<CertStore> certStores) throws CertificateException, CertStoreException, IOException { /* * Compose a CertSelector to filter out * certs which do not satisfy requirements. */ X509CertSelector sel = new X509CertSelector(); /* * Match on issuer (subject of previous cert) */ sel.setIssuer(currentState.subjectDN); /* * Match on certificate validity date. */ sel.setCertificateValid(buildParams.date()); /* * Match on target subject name (checks that current cert's * name constraints permit it to certify target). * (4 is the integer type for DIRECTORY name). */ byte[] subject = targetCertConstraints.getSubjectAsBytes(); if (subject != null) { sel.addPathToName(4, subject); } else { X509Certificate cert = targetCertConstraints.getCertificate(); if (cert != null) { sel.addPathToName(4, cert.getSubjectX500Principal().getEncoded()); } } /* * Policy processing optimizations */ if (currentState.explicitPolicy == 0) sel.setPolicy(getMatchingPolicies()); /* * If previous cert has a subject key identifier extension, * use it to match on authority key identifier extension. */ /*if (currentState.subjKeyId != null) { AuthorityKeyIdentifierExtension authKeyId = new AuthorityKeyIdentifierExtension( (KeyIdentifier) currentState.subjKeyId.get(SubjectKeyIdentifierExtension.KEY_ID), null, null); sel.setAuthorityKeyIdentifier(authKeyId.getExtensionValue()); }*/ /* * Require CA certs */ sel.setBasicConstraints(0); /* Retrieve matching certs from CertStores */ ArrayList<X509Certificate> reverseCerts = new ArrayList<>(); addMatchingCerts(sel, certStores, reverseCerts, true); /* Sort remaining certs using name constraints */ Collections.sort(reverseCerts, new PKIXCertComparator()); if (debug != null) debug.println("ReverseBuilder.getMatchingCACerts got " + reverseCerts.size() + " certs."); return reverseCerts; } /* * This inner class compares 2 PKIX certificates according to which * should be tried first when building a path to the target. For * now, the algorithm is to look at name constraints in each cert and those * which constrain the path closer to the target should be * ranked higher. Later, we may want to consider other components, * such as key identifiers. */ class PKIXCertComparator implements Comparator<X509Certificate> { private Debug debug = Debug.getInstance("certpath"); @Override public int compare(X509Certificate cert1, X509Certificate cert2) { /* * if either cert certifies the target, always * put at head of list. */ X500Principal targetSubject = buildParams.targetSubject(); if (cert1.getSubjectX500Principal().equals(targetSubject)) { return -1; } if (cert2.getSubjectX500Principal().equals(targetSubject)) { return 1; } int targetDist1; int targetDist2; try { X500Name targetSubjectName = X500Name.asX500Name(targetSubject); targetDist1 = Builder.targetDistance( null, cert1, targetSubjectName); targetDist2 = Builder.targetDistance( null, cert2, targetSubjectName); } catch (IOException e) { if (debug != null) { debug.println("IOException in call to Builder.targetDistance"); e.printStackTrace(); } throw new ClassCastException ("Invalid target subject distinguished name"); } if (targetDist1 == targetDist2) return 0; if (targetDist1 == -1) return 1; if (targetDist1 < targetDist2) return -1; return 1; } }
Verifies a matching certificate. This method executes any of the validation steps in the PKIX path validation algorithm which were not satisfied via filtering out non-compliant certificates with certificate matching rules. If the last certificate is being verified (the one whose subject matches the target subject, then the steps in Section 6.1.4 of the Certification Path Validation algorithm are NOT executed, regardless of whether or not the last cert is an end-entity cert or not. This allows callers to certify CA certs as well as EE certs.
Params:
  • cert – the certificate to be verified
  • currentState – the current state against which the cert is verified
  • certPathList – the certPathList generated thus far
/** * Verifies a matching certificate. * * This method executes any of the validation steps in the PKIX path validation * algorithm which were not satisfied via filtering out non-compliant * certificates with certificate matching rules. * * If the last certificate is being verified (the one whose subject * matches the target subject, then the steps in Section 6.1.4 of the * Certification Path Validation algorithm are NOT executed, * regardless of whether or not the last cert is an end-entity * cert or not. This allows callers to certify CA certs as * well as EE certs. * * @param cert the certificate to be verified * @param currentState the current state against which the cert is verified * @param certPathList the certPathList generated thus far */
@Override void verifyCert(X509Certificate cert, State currState, List<X509Certificate> certPathList) throws GeneralSecurityException { if (debug != null) { debug.println("ReverseBuilder.verifyCert(SN: " + Debug.toHexString(cert.getSerialNumber()) + "\n Subject: " + cert.getSubjectX500Principal() + ")"); } ReverseState currentState = (ReverseState) currState; /* we don't perform any validation of the trusted cert */ if (currentState.isInitial()) { return; } // Don't bother to verify untrusted certificate more. currentState.untrustedChecker.check(cert, Collections.<String>emptySet()); /* * check for looping - abort a loop if * ((we encounter the same certificate twice) AND * ((policyMappingInhibited = true) OR (no policy mapping * extensions can be found between the occurrences of the same * certificate))) * in order to facilitate the check to see if there are * any policy mapping extensions found between the occurrences * of the same certificate, we reverse the certpathlist first */ if ((certPathList != null) && (!certPathList.isEmpty())) { List<X509Certificate> reverseCertList = new ArrayList<>(); for (X509Certificate c : certPathList) { reverseCertList.add(0, c); } boolean policyMappingFound = false; for (X509Certificate cpListCert : reverseCertList) { X509CertImpl cpListCertImpl = X509CertImpl.toImpl(cpListCert); PolicyMappingsExtension policyMappingsExt = cpListCertImpl.getPolicyMappingsExtension(); if (policyMappingsExt != null) { policyMappingFound = true; } if (debug != null) debug.println("policyMappingFound = " + policyMappingFound); if (cert.equals(cpListCert)) { if ((buildParams.policyMappingInhibited()) || (!policyMappingFound)){ if (debug != null) debug.println("loop detected!!"); throw new CertPathValidatorException("loop detected"); } } } } /* check if target cert */ boolean finalCert = cert.getSubjectX500Principal().equals(buildParams.targetSubject()); /* check if CA cert */ boolean caCert = (cert.getBasicConstraints() != -1 ? true : false); /* if there are more certs to follow, verify certain constraints */ if (!finalCert) { /* check if CA cert */ if (!caCert) throw new CertPathValidatorException("cert is NOT a CA cert"); /* If the certificate was not self-issued, verify that * remainingCerts is greater than zero */ if ((currentState.remainingCACerts <= 0) && !X509CertImpl.isSelfIssued(cert)) { throw new CertPathValidatorException ("pathLenConstraint violated, path too long", null, null, -1, PKIXReason.PATH_TOO_LONG); } /* * Check keyUsage extension (only if CA cert and not final cert) */ KeyChecker.verifyCAKeyUsage(cert); } else { /* * If final cert, check that it satisfies specified target * constraints */ if (targetCertConstraints.match(cert) == false) { throw new CertPathValidatorException("target certificate " + "constraints check failed"); } } /* * Check revocation. */ if (buildParams.revocationEnabled() && currentState.revChecker != null) { currentState.revChecker.check(cert, Collections.<String>emptySet()); } /* Check name constraints if this is not a self-issued cert */ if (finalCert || !X509CertImpl.isSelfIssued(cert)){ if (currentState.nc != null) { try { if (!currentState.nc.verify(cert)){ throw new CertPathValidatorException ("name constraints check failed", null, null, -1, PKIXReason.INVALID_NAME); } } catch (IOException ioe) { throw new CertPathValidatorException(ioe); } } } /* * Check policy */ X509CertImpl certImpl = X509CertImpl.toImpl(cert); currentState.rootNode = PolicyChecker.processPolicies (currentState.certIndex, initPolicies, currentState.explicitPolicy, currentState.policyMapping, currentState.inhibitAnyPolicy, buildParams.policyQualifiersRejected(), currentState.rootNode, certImpl, finalCert); /* * Check CRITICAL private extensions */ Set<String> unresolvedCritExts = cert.getCriticalExtensionOIDs(); if (unresolvedCritExts == null) { unresolvedCritExts = Collections.<String>emptySet(); } /* * Check that the signature algorithm is not disabled. */ currentState.algorithmChecker.check(cert, unresolvedCritExts); for (PKIXCertPathChecker checker : currentState.userCheckers) { checker.check(cert, unresolvedCritExts); } /* * Look at the remaining extensions and remove any ones we have * already checked. If there are any left, throw an exception! */ if (!unresolvedCritExts.isEmpty()) { unresolvedCritExts.remove(BasicConstraints_Id.toString()); unresolvedCritExts.remove(NameConstraints_Id.toString()); unresolvedCritExts.remove(CertificatePolicies_Id.toString()); unresolvedCritExts.remove(PolicyMappings_Id.toString()); unresolvedCritExts.remove(PolicyConstraints_Id.toString()); unresolvedCritExts.remove(InhibitAnyPolicy_Id.toString()); unresolvedCritExts.remove(SubjectAlternativeName_Id.toString()); unresolvedCritExts.remove(KeyUsage_Id.toString()); unresolvedCritExts.remove(ExtendedKeyUsage_Id.toString()); if (!unresolvedCritExts.isEmpty()) throw new CertPathValidatorException ("Unrecognized critical extension(s)", null, null, -1, PKIXReason.UNRECOGNIZED_CRIT_EXT); } /* * Check signature. */ if (buildParams.sigProvider() != null) { cert.verify(currentState.pubKey, buildParams.sigProvider()); } else { cert.verify(currentState.pubKey); } }
Verifies whether the input certificate completes the path. This checks whether the cert is the target certificate.
Params:
  • cert – the certificate to test
Returns:a boolean value indicating whether the cert completes the path.
/** * Verifies whether the input certificate completes the path. * This checks whether the cert is the target certificate. * * @param cert the certificate to test * @return a boolean value indicating whether the cert completes the path. */
@Override boolean isPathCompleted(X509Certificate cert) { return cert.getSubjectX500Principal().equals(buildParams.targetSubject()); }
Adds the certificate to the certPathList
Params:
  • cert – the certificate to be added
  • certPathList – the certification path list
/** Adds the certificate to the certPathList * * @param cert the certificate to be added * @param certPathList the certification path list */
@Override void addCertToPath(X509Certificate cert, LinkedList<X509Certificate> certPathList) { certPathList.addLast(cert); }
Removes final certificate from the certPathList
Params:
  • certPathList – the certification path list
/** Removes final certificate from the certPathList * * @param certPathList the certification path list */
@Override void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) { certPathList.removeLast(); } }