/* * 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. * * @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. * * @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). * * @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. * * @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 occurences of the same * certificate))) * in order to facilitate the check to see if there are * any policy mapping extensions found between the occurences * 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. * * @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 * * @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 * * @param certPathList the certification path list */ @Override void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) { certPathList.removeLast(); } }