/* * Copyright (c) 2002, 2013, 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.*; import java.net.URI; import java.security.*; import java.security.cert.*; import javax.security.auth.x500.X500Principal; import java.util.*; import sun.security.util.Debug; import sun.security.util.DerOutputStream; import static sun.security.x509.PKIXExtensions.*; import sun.security.x509.*; /** * Class to obtain CRLs via the CRLDistributionPoints extension. * Note that the functionality of this class must be explicitly enabled * via a system property, see the USE_CRLDP variable below. * * This class uses the URICertStore class to fetch CRLs. The URICertStore * class also implements CRL caching: see the class description for more * information. * * @author Andreas Sterbenz * @author Sean Mullan * @since 1.4.2 */ public class DistributionPointFetcher { private static final Debug debug = Debug.getInstance("certpath"); private static final boolean[] ALL_REASONS = {true, true, true, true, true, true, true, true, true}; /** * Private instantiation only. */ private DistributionPointFetcher() {} /** * Return the X509CRLs matching this selector. The selector must be * an X509CRLSelector with certificateChecking set. */ public static Collection<X509CRL> getCRLs(X509CRLSelector selector, boolean signFlag, PublicKey prevKey, String provider, List<CertStore> certStores, boolean[] reasonsMask, Set<TrustAnchor> trustAnchors, Date validity) throws CertStoreException { X509Certificate cert = selector.getCertificateChecking(); if (cert == null) { return Collections.emptySet(); } try { X509CertImpl certImpl = X509CertImpl.toImpl(cert); if (debug != null) { debug.println("DistributionPointFetcher.getCRLs: Checking " + "CRLDPs for " + certImpl.getSubjectX500Principal()); } CRLDistributionPointsExtension ext = certImpl.getCRLDistributionPointsExtension(); if (ext == null) { if (debug != null) { debug.println("No CRLDP ext"); } return Collections.emptySet(); } List<DistributionPoint> points = ext.get(CRLDistributionPointsExtension.POINTS); Set<X509CRL> results = new HashSet<>(); for (Iterator<DistributionPoint> t = points.iterator(); t.hasNext() && !Arrays.equals(reasonsMask, ALL_REASONS); ) { DistributionPoint point = t.next(); Collection<X509CRL> crls = getCRLs(selector, certImpl, point, reasonsMask, signFlag, prevKey, provider, certStores, trustAnchors, validity); results.addAll(crls); } if (debug != null) { debug.println("Returning " + results.size() + " CRLs"); } return results; } catch (CertificateException | IOException e) { return Collections.emptySet(); } } /** * Download CRLs from the given distribution point, verify and return them. * See the top of the class for current limitations. * * @throws CertStoreException if there is an error retrieving the CRLs * from one of the GeneralNames and no other CRLs are retrieved from * the other GeneralNames. If more than one GeneralName throws an * exception then the one from the last GeneralName is thrown. */ private static Collection<X509CRL> getCRLs(X509CRLSelector selector, X509CertImpl certImpl, DistributionPoint point, boolean[] reasonsMask, boolean signFlag, PublicKey prevKey, String provider, List<CertStore> certStores, Set<TrustAnchor> trustAnchors, Date validity) throws CertStoreException { // check for full name GeneralNames fullName = point.getFullName(); if (fullName == null) { // check for relative name RDN relativeName = point.getRelativeName(); if (relativeName == null) { return Collections.emptySet(); } try { GeneralNames crlIssuers = point.getCRLIssuer(); if (crlIssuers == null) { fullName = getFullNames ((X500Name) certImpl.getIssuerDN(), relativeName); } else { // should only be one CRL Issuer if (crlIssuers.size() != 1) { return Collections.emptySet(); } else { fullName = getFullNames ((X500Name) crlIssuers.get(0).getName(), relativeName); } } } catch (IOException ioe) { return Collections.emptySet(); } } Collection<X509CRL> possibleCRLs = new ArrayList<>(); CertStoreException savedCSE = null; for (Iterator<GeneralName> t = fullName.iterator(); t.hasNext(); ) { try { GeneralName name = t.next(); if (name.getType() == GeneralNameInterface.NAME_DIRECTORY) { X500Name x500Name = (X500Name) name.getName(); possibleCRLs.addAll( getCRLs(x500Name, certImpl.getIssuerX500Principal(), certStores)); } else if (name.getType() == GeneralNameInterface.NAME_URI) { URIName uriName = (URIName)name.getName(); X509CRL crl = getCRL(uriName); if (crl != null) { possibleCRLs.add(crl); } } } catch (CertStoreException cse) { savedCSE = cse; } } // only throw CertStoreException if no CRLs are retrieved if (possibleCRLs.isEmpty() && savedCSE != null) { throw savedCSE; } Collection<X509CRL> crls = new ArrayList<>(2); for (X509CRL crl : possibleCRLs) { try { // make sure issuer is not set // we check the issuer in verifyCRLs method selector.setIssuerNames(null); if (selector.match(crl) && verifyCRL(certImpl, point, crl, reasonsMask, signFlag, prevKey, provider, trustAnchors, certStores, validity)) { crls.add(crl); } } catch (IOException | CRLException e) { // don't add the CRL if (debug != null) { debug.println("Exception verifying CRL: " + e.getMessage()); e.printStackTrace(); } } } return crls; } /** * Download CRL from given URI. */ private static X509CRL getCRL(URIName name) throws CertStoreException { URI uri = name.getURI(); if (debug != null) { debug.println("Trying to fetch CRL from DP " + uri); } CertStore ucs = null; try { ucs = URICertStore.getInstance (new URICertStore.URICertStoreParameters(uri)); } catch (InvalidAlgorithmParameterException | NoSuchAlgorithmException e) { if (debug != null) { debug.println("Can't create URICertStore: " + e.getMessage()); } return null; } Collection<? extends CRL> crls = ucs.getCRLs(null); if (crls.isEmpty()) { return null; } else { return (X509CRL) crls.iterator().next(); } } /** * Fetch CRLs from certStores. * * @throws CertStoreException if there is an error retrieving the CRLs from * one of the CertStores and no other CRLs are retrieved from * the other CertStores. If more than one CertStore throws an * exception then the one from the last CertStore is thrown. */ private static Collection<X509CRL> getCRLs(X500Name name, X500Principal certIssuer, List<CertStore> certStores) throws CertStoreException { if (debug != null) { debug.println("Trying to fetch CRL from DP " + name); } X509CRLSelector xcs = new X509CRLSelector(); xcs.addIssuer(name.asX500Principal()); xcs.addIssuer(certIssuer); Collection<X509CRL> crls = new ArrayList<>(); CertStoreException savedCSE = null; for (CertStore store : certStores) { try { for (CRL crl : store.getCRLs(xcs)) { crls.add((X509CRL)crl); } } catch (CertStoreException cse) { if (debug != null) { debug.println("Exception while retrieving " + "CRLs: " + cse); cse.printStackTrace(); } savedCSE = new PKIX.CertStoreTypeException(store.getType(),cse); } } // only throw CertStoreException if no CRLs are retrieved if (crls.isEmpty() && savedCSE != null) { throw savedCSE; } else { return crls; } } /** * Verifies a CRL for the given certificate's Distribution Point to * ensure it is appropriate for checking the revocation status. * * @param certImpl the certificate whose revocation status is being checked * @param point one of the distribution points of the certificate * @param crl the CRL * @param reasonsMask the interim reasons mask * @param signFlag true if prevKey can be used to verify the CRL * @param prevKey the public key that verifies the certificate's signature * @param provider the Signature provider to use * @param trustAnchors a {@code Set} of {@code TrustAnchor}s * @param certStores a {@code List} of {@code CertStore}s to be used in * finding certificates and CRLs * @param validity the time for which the validity of the CRL issuer's * certification path should be determined * @return true if ok, false if not */ static boolean verifyCRL(X509CertImpl certImpl, DistributionPoint point, X509CRL crl, boolean[] reasonsMask, boolean signFlag, PublicKey prevKey, String provider, Set<TrustAnchor> trustAnchors, List<CertStore> certStores, Date validity) throws CRLException, IOException { boolean indirectCRL = false; X509CRLImpl crlImpl = X509CRLImpl.toImpl(crl); IssuingDistributionPointExtension idpExt = crlImpl.getIssuingDistributionPointExtension(); X500Name certIssuer = (X500Name) certImpl.getIssuerDN(); X500Name crlIssuer = (X500Name) crlImpl.getIssuerDN(); // if crlIssuer is set, verify that it matches the issuer of the // CRL and the CRL contains an IDP extension with the indirectCRL // boolean asserted. Otherwise, verify that the CRL issuer matches the // certificate issuer. GeneralNames pointCrlIssuers = point.getCRLIssuer(); X500Name pointCrlIssuer = null; if (pointCrlIssuers != null) { if (idpExt == null || ((Boolean) idpExt.get (IssuingDistributionPointExtension.INDIRECT_CRL)).equals (Boolean.FALSE)) { return false; } boolean match = false; for (Iterator<GeneralName> t = pointCrlIssuers.iterator(); !match && t.hasNext(); ) { GeneralNameInterface name = t.next().getName(); if (crlIssuer.equals(name) == true) { pointCrlIssuer = (X500Name) name; match = true; } } if (match == false) { return false; } // we accept the case that a CRL issuer provide status // information for itself. if (issues(certImpl, crlImpl, provider)) { // reset the public key used to verify the CRL's signature prevKey = certImpl.getPublicKey(); } else { indirectCRL = true; } } else if (crlIssuer.equals(certIssuer) == false) { if (debug != null) { debug.println("crl issuer does not equal cert issuer"); } return false; } else { // in case of self-issued indirect CRL issuer. KeyIdentifier certAKID = certImpl.getAuthKeyId(); KeyIdentifier crlAKID = crlImpl.getAuthKeyId(); if (certAKID == null || crlAKID == null) { // cannot recognize indirect CRL without AKID // we accept the case that a CRL issuer provide status // information for itself. if (issues(certImpl, crlImpl, provider)) { // reset the public key used to verify the CRL's signature prevKey = certImpl.getPublicKey(); } } else if (!certAKID.equals(crlAKID)) { // we accept the case that a CRL issuer provide status // information for itself. if (issues(certImpl, crlImpl, provider)) { // reset the public key used to verify the CRL's signature prevKey = certImpl.getPublicKey(); } else { indirectCRL = true; } } } if (!indirectCRL && !signFlag) { // cert's key cannot be used to verify the CRL return false; } if (idpExt != null) { DistributionPointName idpPoint = (DistributionPointName) idpExt.get(IssuingDistributionPointExtension.POINT); if (idpPoint != null) { GeneralNames idpNames = idpPoint.getFullName(); if (idpNames == null) { RDN relativeName = idpPoint.getRelativeName(); if (relativeName == null) { if (debug != null) { debug.println("IDP must be relative or full DN"); } return false; } if (debug != null) { debug.println("IDP relativeName:" + relativeName); } idpNames = getFullNames(crlIssuer, relativeName); } // if the DP name is present in the IDP CRL extension and the // DP field is present in the DP, then verify that one of the // names in the IDP matches one of the names in the DP if (point.getFullName() != null || point.getRelativeName() != null) { GeneralNames pointNames = point.getFullName(); if (pointNames == null) { RDN relativeName = point.getRelativeName(); if (relativeName == null) { if (debug != null) { debug.println("DP must be relative or full DN"); } return false; } if (debug != null) { debug.println("DP relativeName:" + relativeName); } if (indirectCRL) { if (pointCrlIssuers.size() != 1) { // RFC 3280: there must be only 1 CRL issuer // name when relativeName is present if (debug != null) { debug.println("must only be one CRL " + "issuer when relative name present"); } return false; } pointNames = getFullNames (pointCrlIssuer, relativeName); } else { pointNames = getFullNames(certIssuer, relativeName); } } boolean match = false; for (Iterator<GeneralName> i = idpNames.iterator(); !match && i.hasNext(); ) { GeneralNameInterface idpName = i.next().getName(); if (debug != null) { debug.println("idpName: " + idpName); } for (Iterator<GeneralName> p = pointNames.iterator(); !match && p.hasNext(); ) { GeneralNameInterface pointName = p.next().getName(); if (debug != null) { debug.println("pointName: " + pointName); } match = idpName.equals(pointName); } } if (!match) { if (debug != null) { debug.println("IDP name does not match DP name"); } return false; } // if the DP name is present in the IDP CRL extension and the // DP field is absent from the DP, then verify that one of the // names in the IDP matches one of the names in the crlIssuer // field of the DP } else { // verify that one of the names in the IDP matches one of // the names in the cRLIssuer of the cert's DP boolean match = false; for (Iterator<GeneralName> t = pointCrlIssuers.iterator(); !match && t.hasNext(); ) { GeneralNameInterface crlIssuerName = t.next().getName(); for (Iterator<GeneralName> i = idpNames.iterator(); !match && i.hasNext(); ) { GeneralNameInterface idpName = i.next().getName(); match = crlIssuerName.equals(idpName); } } if (!match) { return false; } } } // if the onlyContainsUserCerts boolean is asserted, verify that the // cert is not a CA cert Boolean b = (Boolean) idpExt.get(IssuingDistributionPointExtension.ONLY_USER_CERTS); if (b.equals(Boolean.TRUE) && certImpl.getBasicConstraints() != -1) { if (debug != null) { debug.println("cert must be a EE cert"); } return false; } // if the onlyContainsCACerts boolean is asserted, verify that the // cert is a CA cert b = (Boolean) idpExt.get(IssuingDistributionPointExtension.ONLY_CA_CERTS); if (b.equals(Boolean.TRUE) && certImpl.getBasicConstraints() == -1) { if (debug != null) { debug.println("cert must be a CA cert"); } return false; } // verify that the onlyContainsAttributeCerts boolean is not // asserted b = (Boolean) idpExt.get (IssuingDistributionPointExtension.ONLY_ATTRIBUTE_CERTS); if (b.equals(Boolean.TRUE)) { if (debug != null) { debug.println("cert must not be an AA cert"); } return false; } } // compute interim reasons mask boolean[] interimReasonsMask = new boolean[9]; ReasonFlags reasons = null; if (idpExt != null) { reasons = (ReasonFlags) idpExt.get(IssuingDistributionPointExtension.REASONS); } boolean[] pointReasonFlags = point.getReasonFlags(); if (reasons != null) { if (pointReasonFlags != null) { // set interim reasons mask to the intersection of // reasons in the DP and onlySomeReasons in the IDP boolean[] idpReasonFlags = reasons.getFlags(); for (int i = 0; i < idpReasonFlags.length; i++) { if (idpReasonFlags[i] && pointReasonFlags[i]) { interimReasonsMask[i] = true; } } } else { // set interim reasons mask to the value of // onlySomeReasons in the IDP (and clone it since we may // modify it) interimReasonsMask = reasons.getFlags().clone(); } } else if (idpExt == null || reasons == null) { if (pointReasonFlags != null) { // set interim reasons mask to the value of DP reasons interimReasonsMask = pointReasonFlags.clone(); } else { // set interim reasons mask to the special value all-reasons interimReasonsMask = new boolean[9]; Arrays.fill(interimReasonsMask, true); } } // verify that interim reasons mask includes one or more reasons // not included in the reasons mask boolean oneOrMore = false; for (int i = 0; i < interimReasonsMask.length && !oneOrMore; i++) { if (!reasonsMask[i] && interimReasonsMask[i]) { oneOrMore = true; } } if (!oneOrMore) { return false; } // Obtain and validate the certification path for the complete // CRL issuer (if indirect CRL). If a key usage extension is present // in the CRL issuer's certificate, verify that the cRLSign bit is set. if (indirectCRL) { X509CertSelector certSel = new X509CertSelector(); certSel.setSubject(crlIssuer.asX500Principal()); boolean[] crlSign = {false,false,false,false,false,false,true}; certSel.setKeyUsage(crlSign); // Currently by default, forward builder does not enable // subject/authority key identifier identifying for target // certificate, instead, it only compares the CRL issuer and // the target certificate subject. If the certificate of the // delegated CRL issuer is a self-issued certificate, the // builder is unable to find the proper CRL issuer by issuer // name only, there is a potential dead loop on finding the // proper issuer. It is of great help to narrow the target // scope down to aware of authority key identifiers in the // selector, for the purposes of breaking the dead loop. AuthorityKeyIdentifierExtension akidext = crlImpl.getAuthKeyIdExtension(); if (akidext != null) { KeyIdentifier akid = (KeyIdentifier)akidext.get( AuthorityKeyIdentifierExtension.KEY_ID); if (akid != null) { DerOutputStream derout = new DerOutputStream(); derout.putOctetString(akid.getIdentifier()); certSel.setSubjectKeyIdentifier(derout.toByteArray()); } SerialNumber asn = (SerialNumber)akidext.get( AuthorityKeyIdentifierExtension.SERIAL_NUMBER); if (asn != null) { certSel.setSerialNumber(asn.getNumber()); } // the subject criterion will be set by builder automatically. } // by far, we have validated the previous certificate, we can // trust it during validating the CRL issuer. // Except the performance improvement, another benefit is to break // the dead loop while looking for the issuer back and forth // between the delegated self-issued certificate and its issuer. Set<TrustAnchor> newTrustAnchors = new HashSet<>(trustAnchors); if (prevKey != null) { // Add the previous certificate as a trust anchor. X500Principal principal = certImpl.getIssuerX500Principal(); TrustAnchor temporary = new TrustAnchor(principal, prevKey, null); newTrustAnchors.add(temporary); } PKIXBuilderParameters params = null; try { params = new PKIXBuilderParameters(newTrustAnchors, certSel); } catch (InvalidAlgorithmParameterException iape) { throw new CRLException(iape); } params.setCertStores(certStores); params.setSigProvider(provider); params.setDate(validity); try { CertPathBuilder builder = CertPathBuilder.getInstance("PKIX"); PKIXCertPathBuilderResult result = (PKIXCertPathBuilderResult) builder.build(params); prevKey = result.getPublicKey(); } catch (GeneralSecurityException e) { throw new CRLException(e); } } // check the crl signature algorithm try { AlgorithmChecker.check(prevKey, crl); } catch (CertPathValidatorException cpve) { if (debug != null) { debug.println("CRL signature algorithm check failed: " + cpve); } return false; } // validate the signature on the CRL try { crl.verify(prevKey, provider); } catch (GeneralSecurityException e) { if (debug != null) { debug.println("CRL signature failed to verify"); } return false; } // reject CRL if any unresolved critical extensions remain in the CRL. Set<String> unresCritExts = crl.getCriticalExtensionOIDs(); // remove any that we have processed if (unresCritExts != null) { unresCritExts.remove(IssuingDistributionPoint_Id.toString()); if (!unresCritExts.isEmpty()) { if (debug != null) { debug.println("Unrecognized critical extension(s) in CRL: " + unresCritExts); for (String ext : unresCritExts) { debug.println(ext); } } return false; } } // update reasonsMask for (int i = 0; i < interimReasonsMask.length; i++) { if (!reasonsMask[i] && interimReasonsMask[i]) { reasonsMask[i] = true; } } return true; } /** * Append relative name to the issuer name and return a new * GeneralNames object. */ private static GeneralNames getFullNames(X500Name issuer, RDN rdn) throws IOException { List<RDN> rdns = new ArrayList<>(issuer.rdns()); rdns.add(rdn); X500Name fullName = new X500Name(rdns.toArray(new RDN[0])); GeneralNames fullNames = new GeneralNames(); fullNames.add(new GeneralName(fullName)); return fullNames; } /** * Verifies whether a CRL is issued by a certain certificate * * @param cert the certificate * @param crl the CRL to be verified * @param provider the name of the signature provider */ private static boolean issues(X509CertImpl cert, X509CRLImpl crl, String provider) throws IOException { boolean matched = false; AdaptableX509CertSelector issuerSelector = new AdaptableX509CertSelector(); // check certificate's key usage boolean[] usages = cert.getKeyUsage(); if (usages != null) { usages[6] = true; // cRLSign issuerSelector.setKeyUsage(usages); } // check certificate's subject X500Principal crlIssuer = crl.getIssuerX500Principal(); issuerSelector.setSubject(crlIssuer); /* * Facilitate certification path construction with authority * key identifier and subject key identifier. * * In practice, conforming CAs MUST use the key identifier method, * and MUST include authority key identifier extension in all CRLs * issued. [section 5.2.1, RFC 2459] */ AuthorityKeyIdentifierExtension crlAKID = crl.getAuthKeyIdExtension(); if (crlAKID != null) { issuerSelector.parseAuthorityKeyIdentifierExtension(crlAKID); } matched = issuerSelector.match(cert); // if AKID is unreliable, verify the CRL signature with the cert if (matched && (crlAKID == null || cert.getAuthorityKeyIdentifierExtension() == null)) { try { crl.verify(cert.getPublicKey(), provider); matched = true; } catch (GeneralSecurityException e) { matched = false; } } return matched; } }