| /* |
| * 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.util.Debug; |
| import sun.security.x509.Extension; |
| import 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"); |
| |
| Set<String> initPolicies; |
| |
| /** |
| * Initialize the builder with the input parameters. |
| * |
| * @param params the parameter set used to build a certification path |
| */ |
| ReverseBuilder(PKIXBuilderParameters buildParams, |
| X500Principal targetSubjectDN) { |
| |
| super(buildParams, targetSubjectDN); |
| |
| Set<String> initialPolicies = buildParams.getInitialPolicies(); |
| 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 { |
| for (String policy : initialPolicies) { |
| initPolicies.add(policy); |
| } |
| } |
| } |
| |
| /** |
| * 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 |
| */ |
| 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(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<X509Certificate>(); |
| 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(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). |
| */ |
| sel.addPathToName(4, targetCertConstraints.getSubjectAsBytes()); |
| |
| /* |
| * 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<X509Certificate>(); |
| 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"); |
| |
| public int compare(X509Certificate cert1, X509Certificate cert2) { |
| |
| /* |
| * if either cert certifies the target, always |
| * put at head of list. |
| */ |
| if (cert1.getSubjectX500Principal().equals(targetSubjectDN)) { |
| return -1; |
| } |
| if (cert2.getSubjectX500Principal().equals(targetSubjectDN)) { |
| return 1; |
| } |
| |
| int targetDist1; |
| int targetDist2; |
| try { |
| X500Name targetSubjectName = X500Name.asX500Name(targetSubjectDN); |
| 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 |
| */ |
| 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<X509Certificate>(); |
| 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.isPolicyMappingInhibited()) || |
| (!policyMappingFound)){ |
| if (debug != null) |
| debug.println("loop detected!!"); |
| throw new CertPathValidatorException("loop detected"); |
| } |
| } |
| } |
| } |
| |
| /* check if target cert */ |
| boolean finalCert = cert.getSubjectX500Principal().equals(targetSubjectDN); |
| |
| /* 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.isRevocationEnabled()) { |
| |
| currentState.crlChecker.check(cert, |
| currentState.pubKey, |
| currentState.crlSign); |
| } |
| |
| /* 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.getPolicyQualifiersRejected(), 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(PKIXExtensions.BasicConstraints_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.NameConstraints_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.CertificatePolicies_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.PolicyMappings_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.PolicyConstraints_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.InhibitAnyPolicy_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.SubjectAlternativeName_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.KeyUsage_Id.toString()); |
| unresolvedCritExts.remove(PKIXExtensions.ExtendedKeyUsage_Id.toString()); |
| |
| if (!unresolvedCritExts.isEmpty()) |
| throw new CertPathValidatorException |
| ("Unrecognized critical extension(s)", null, null, -1, |
| PKIXReason.UNRECOGNIZED_CRIT_EXT); |
| } |
| |
| /* |
| * Check signature. |
| */ |
| if (buildParams.getSigProvider() != null) { |
| cert.verify(currentState.pubKey, buildParams.getSigProvider()); |
| } 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. |
| */ |
| boolean isPathCompleted(X509Certificate cert) { |
| return cert.getSubjectX500Principal().equals(targetSubjectDN); |
| } |
| |
| /** Adds the certificate to the certPathList |
| * |
| * @param cert the certificate to be added |
| * @param certPathList the certification path list |
| */ |
| void addCertToPath(X509Certificate cert, |
| LinkedList<X509Certificate> certPathList) { |
| certPathList.addLast(cert); |
| } |
| |
| /** Removes final certificate from the certPathList |
| * |
| * @param certPathList the certification path list |
| */ |
| void removeFinalCertFromPath(LinkedList<X509Certificate> certPathList) { |
| certPathList.removeLast(); |
| } |
| } |