Java tutorial
/* * Copyright (c) 2008-2012, Martijn Brinkers, Djigzo. * * This file is part of Djigzo email encryption. * * Djigzo is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License * version 3, 19 November 2007 as published by the Free Software * Foundation. * * Djigzo 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public * License along with Djigzo. If not, see <http://www.gnu.org/licenses/> * * Additional permission under GNU AGPL version 3 section 7 * * If you modify this Program, or any covered work, by linking or * combining it with aspectjrt.jar, aspectjweaver.jar, tyrex-1.0.3.jar, * freemarker.jar, dom4j.jar, mx4j-jmx.jar, mx4j-tools.jar, * spice-classman-1.0.jar, spice-loggerstore-0.5.jar, spice-salt-0.8.jar, * spice-xmlpolicy-1.0.jar, saaj-api-1.3.jar, saaj-impl-1.3.jar, * wsdl4j-1.6.1.jar (or modified versions of these libraries), * containing parts covered by the terms of Eclipse Public License, * tyrex license, freemarker license, dom4j license, mx4j license, * Spice Software License, Common Development and Distribution License * (CDDL), Common Public License (CPL) the licensors of this Program grant * you additional permission to convey the resulting work. */ package mitm.common.security.crl; import java.io.IOException; import java.math.BigInteger; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.PublicKey; import java.security.SignatureException; import java.security.cert.CRL; import java.security.cert.CRLException; import java.security.cert.CertPath; import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.TrustAnchor; import java.security.cert.X509CRL; import java.security.cert.X509CRLEntry; import java.security.cert.X509CRLSelector; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; import mitm.common.security.NoSuchProviderRuntimeException; import mitm.common.security.SecurityFactory; import mitm.common.security.SecurityFactoryFactory; import mitm.common.security.asn1.ASN1Utils; import mitm.common.security.certificate.KeyUsageType; import mitm.common.security.certificate.X509CertificateInspector; import mitm.common.security.crlstore.BasicCRLStore; import mitm.common.security.crlstore.CRLStoreException; import mitm.common.util.Check; import mitm.common.util.CloseableIterator; import mitm.common.util.CloseableIteratorException; import mitm.common.util.LogUtils; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang.text.StrBuilder; import org.bouncycastle.asn1.ASN1Encodable; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1Encoding; import org.bouncycastle.asn1.DERSequence; import org.bouncycastle.asn1.x500.X500Name; import org.bouncycastle.asn1.x509.AuthorityKeyIdentifier; import org.bouncycastle.asn1.x509.BasicConstraints; import org.bouncycastle.asn1.x509.CRLDistPoint; import org.bouncycastle.asn1.x509.DistributionPoint; import org.bouncycastle.asn1.x509.DistributionPointName; import org.bouncycastle.asn1.x509.GeneralName; import org.bouncycastle.asn1.x509.GeneralNames; import org.bouncycastle.asn1.x509.IssuingDistributionPoint; import org.bouncycastle.asn1.x509.ReasonFlags; import org.bouncycastle.asn1.x509.X509Extension; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /* * TODO: we do not yet support indirect CRLs because in such a case the issuer of the * CRL can be some other CA than the issuer of the certificate. When we will support * indirect CRLs we need to modify this check. * * according to RFC 3280 5.1.1.3 it is possible to use a different CA for CRL * signing than for certificate signing. Supporting separate CAs for certificate * signing and CRL signing requires that we build a path for the CRL. * According to RFC 3280 supporting this is a should and we will therefore not * support this yet. Windows does not support CRLs signed by other CAs then * the certificate issuer * (see http://www.microsoft.com/technet/security/guidance/cryptographyetc/tshtcrl.mspx) * */ public class PKIXRevocationChecker implements RevocationChecker { private final static Logger logger = LoggerFactory.getLogger(PKIXRevocationChecker.class); /* * Status results for Delta CRL checking */ private enum DeltaCRLStatus { OK, UNKNOWN, UNSUPPORTED_CRITICAL_EXTENSION }; /* * Collection of CRL stores is synchronized to make it thread safe. */ private final Set<BasicCRLStore> crlStores = Collections.synchronizedSet(new HashSet<BasicCRLStore>()); private final SecurityFactory securityFactory; /* * all revocation reasons mask. Used to check that all reasons have been covered */ final int allReasons = ReasonFlags.aACompromise | ReasonFlags.affiliationChanged | ReasonFlags.cACompromise | ReasonFlags.certificateHold | ReasonFlags.cessationOfOperation | ReasonFlags.keyCompromise | ReasonFlags.privilegeWithdrawn | ReasonFlags.unused | ReasonFlags.superseded; public PKIXRevocationChecker(BasicCRLStore... stores) { securityFactory = SecurityFactoryFactory.getSecurityFactory(); for (BasicCRLStore store : stores) { crlStores.add(store); } } /* * Returns the revocation result for the given certPath */ @Override public RevocationResult getRevocationStatus(CertPath certPath, TrustAnchor trustAnchor, Date now) throws CRLException { Check.notNull(certPath, "certPath"); Check.notNull(trustAnchor, "trustAnchor"); List<? extends Certificate> certificates = certPath.getCertificates(); RevocationResult revocationResult = new RevocationResultImpl(certificates.size()); /* * Step through all the certificates in the path and check the revocation status of all * the certificates in the path. */ for (int i = 0; i < certificates.size(); i++) { X509Certificate certificate = toX509Certificate(certificates.get(i)); PublicKey issuerPublicKey; X500Principal issuer; X509Certificate issuerCertificate; /* * we need to get the issuer of the current certificate * check if there is a next certificate in the path or that we must use the TrustAnchor */ if ((i + 1) == certificates.size()) { /* this was the last entry from the path so we must use the trust anchor */ if (trustAnchor.getTrustedCert() != null) { issuerCertificate = toX509Certificate(trustAnchor.getTrustedCert()); issuerPublicKey = issuerCertificate.getPublicKey(); issuer = issuerCertificate.getSubjectX500Principal(); } else { /* the TrustAnchor does not contain a certificate but only an issuer and public key */ issuerCertificate = null; issuerPublicKey = trustAnchor.getCAPublicKey(); issuer = trustAnchor.getCA(); } } else { /* get next entry from path ie. the issuer of the current certificate */ issuerCertificate = toX509Certificate(certificates.get(i + 1)); issuerPublicKey = issuerCertificate.getPublicKey(); issuer = issuerCertificate.getSubjectX500Principal(); } /* * sanity check to make sure the CertPath is ordered from end -> final CA * ie that the next certificate signed the previous certificate */ verifyCertificate(certificate, issuerPublicKey); /* * Sanity check. The issuer principal field of the certificate currently checked should * normally be equal to the issuer principal. */ if (!certificate.getIssuerX500Principal().equals(issuer)) { logger.warn("Certificate issuer field is not equal to issuer."); } if (issuerCertificate != null) { Set<KeyUsageType> keyUsage = X509CertificateInspector.getKeyUsage(issuerCertificate); /* * check if issuer is allowed to issue CRLs (only when we have an issuerCertificate, and * a key usage extension) */ if (keyUsage != null && !keyUsage.contains(KeyUsageType.CRLSIGN)) { logger.debug("Issuer is not allowed to issue CRLs."); /* * We will return UNKNOWN status. */ RevocationDetailImpl detail = new RevocationDetailImpl(RevocationStatus.UNKNOWN); revocationResult.getDetails()[i] = detail; /* there is no need to continue because issuer is not allowed to issue CRLs */ break; } } X509CRLSelector crlSelector = new X509CRLSelector(); /* create a selector to find all relevant CRLs that were issued to the same issuer as the certificate */ crlSelector.addIssuer(issuer); try { List<X509CRL> crls = findCRLs(certificate, crlSelector, issuerPublicKey, now); RevocationDetail detail = getRevocationDetail(crls, certificate, issuerCertificate, issuerPublicKey, now); revocationResult.getDetails()[i] = detail; if (detail.getStatus() == RevocationStatus.REVOKED) { logger.warn("Certificate is revoked."); if (logger.isDebugEnabled()) { logger.debug("Revoked certificate: " + certificate); } /* there is no need to continue because the CRL is revoked */ break; } } catch (NoSuchProviderException e) { throw new NoSuchProviderRuntimeException(e); } } if (logger.isDebugEnabled()) { logger.debug("Revocation status for CertPath " + certPath + " and TrustAnchor " + trustAnchor + " is " + revocationResult); } return revocationResult; } private void verifyCertificate(X509Certificate certificate, PublicKey issuerPublicKey) throws CRLException { try { certificate.verify(issuerPublicKey, securityFactory.getNonSensitiveProvider()); } catch (InvalidKeyException e) { throw new CRLException("Certificate verification failed.", e); } catch (CertificateException e) { throw new CRLException("Certificate verification failed.", e); } catch (NoSuchAlgorithmException e) { throw new CRLException("Certificate verification failed.", e); } catch (NoSuchProviderException e) { throw new CRLException("Certificate verification failed.", e); } catch (SignatureException e) { throw new CRLException("Certificate verification failed.", e); } } /* * Convert certificate to X509Certificate. */ private X509Certificate toX509Certificate(Certificate certificate) { if (!(certificate instanceof X509Certificate)) { throw new IllegalArgumentException("Only X509Certificates are supported."); } return (X509Certificate) certificate; } /* * Checks whether the CRL has any unsupported extensions. */ private boolean hasUnsupportedCriticalExtensions(X509CRL crl) { Set<String> criticalExtensions = crl.getCriticalExtensionOIDs(); if (criticalExtensions != null) { criticalExtensions.remove(X509Extension.issuingDistributionPoint.getId()); criticalExtensions.remove(X509Extension.deltaCRLIndicator.getId()); criticalExtensions.remove(X509Extension.cRLNumber.getId()); /* * Some issuers (Verisign) add a critcal Authority Key Identifier to the CRL. * * RFC 3280 explicitly says: * * 4.2.1.1 Authority Key Identifier * .... * This extension MUST NOT be marked critical. * * We will therefore ignore this extension if it's critical * */ criticalExtensions.remove(X509Extension.authorityKeyIdentifier.getId()); } return criticalExtensions != null && criticalExtensions.size() > 0; } /* * Checks if the stores contain a base CRL for the given delta CRL and if the scope of the * delta CRL is equal to the scope of the base CRL. * * For more info see: http://www.ietf.org/rfc/rfc3280.txt. 5.2.4 Delta CRL Indicator */ private DeltaCRLStatus getDeltaCRLStatus(X509Certificate targetCertificate, X509CRL deltaCRL, PublicKey issuerPublicKey, Date now) throws NoSuchProviderException { DeltaCRLStatus status = DeltaCRLStatus.UNKNOWN; BigInteger baseCRLNumber; try { baseCRLNumber = X509CRLInspector.getDeltaIndicator(deltaCRL); } catch (IOException e) { logger.error("Error getting base CRL number", e); return DeltaCRLStatus.UNKNOWN; } X509CRLSelector crlSelector = new X509CRLSelector(); /* We need to find a valid base CRL with the same issuer as the delta CRL */ crlSelector.addIssuer(deltaCRL.getIssuerX500Principal()); /* * we need to find a baseCRL with at least a CRL number specified by the DeltaCRLIndicator in * the delta CRL */ crlSelector.setMinCRLNumber(baseCRLNumber); BigInteger deltaCRLNumber = null; try { deltaCRLNumber = X509CRLInspector.getCRLNumber(deltaCRL); } catch (IOException e) { logger.error("Error getting CRLNumber extension from the delta CRL.", e); } if (deltaCRLNumber != null) { /* * the base CRL we need to find should have a CRL number less than the delta CRL * otherwise it cannot be a base for this delta CRL */ crlSelector.setMaxCRLNumber(deltaCRLNumber.subtract(BigInteger.valueOf(1))); List<X509CRL> crls = findCRLs(targetCertificate, crlSelector, issuerPublicKey, now); for (X509CRL baseCRL : crls) { try { if (checkDeltaCRL_6_3_3_b(targetCertificate, deltaCRL, baseCRL)) { status = DeltaCRLStatus.OK; break; } } catch (IOException e) { logger.error("Error executing checkDeltaCRL_6_3_3_b.", e); continue; } if (hasUnsupportedCriticalExtensions(baseCRL)) { logger.warn("The base CRL has unsupported critical extensions."); status = DeltaCRLStatus.UNSUPPORTED_CRITICAL_EXTENSION; continue; } } } return status; } /* * Converts the relative name to crl issuer to a full name by appending the relative * distinguished name contained in distributionPointName to the issuer. */ private X500Name getFullName(X500Principal issuer, DistributionPointName distributionPointName) throws IOException { ASN1Encodable rdn = distributionPointName.getName(); X500Name name = null; if (rdn != null) { ASN1EncodableVector v = ASN1Utils.toASN1EncodableVector(issuer); v.add(rdn); name = X500Name.getInstance(new DERSequence(v).getEncoded(ASN1Encoding.DER)); } return name; } /* * Returns true if name matches any of the generalNames */ private boolean hasMatchingName(X500Name name, GeneralName[] generalNames) { if (name == null || generalNames == null) { return false; } for (GeneralName generalName : generalNames) { /* * we only need to compare directoryNames */ if (generalName.getTagNo() == GeneralName.directoryName) { if (name.equals(X500Name.getInstance(generalName.getName()))) { return true; } } } return false; } /* * Checks if there is a name match between the two DistributionPointNames. */ private boolean hasMatchingName(DistributionPointName dpn1, DistributionPointName dpn2, X500Principal issuer) throws IOException { if (dpn1 == null && dpn2 == null) { return true; } if (dpn1 == null || dpn2 == null) { return false; } GeneralName[] generalNames1 = null; GeneralName[] generalNames2 = null; X500Name name1 = null; X500Name name2 = null; if (dpn1.getType() == DistributionPointName.NAME_RELATIVE_TO_CRL_ISSUER) { name1 = getFullName(issuer, dpn1); } else { generalNames1 = GeneralNames.getInstance(dpn1.getName()).getNames(); } if (dpn2.getType() == DistributionPointName.NAME_RELATIVE_TO_CRL_ISSUER) { name2 = getFullName(issuer, dpn2); } else { generalNames2 = GeneralNames.getInstance(dpn2.getName()).getNames(); } if (generalNames1 != null && generalNames2 != null) { return CollectionUtils.containsAny(Arrays.asList(generalNames1), Arrays.asList(generalNames2)); } if (name1 != null && name2 != null) { return name1.equals(name2); } return name1 != null ? hasMatchingName(name1, generalNames2) : hasMatchingName(name2, generalNames1); } /* * Checks whether the CRL is valid for the certificate */ private boolean acceptCRL_6_3_3_b(X509Certificate targetCertificate, X509CRL crl) throws IOException { boolean match = false; if (X509CRLInspector.isDeltaCRL(crl)) { /* CRL is not complete because it's a delta CRL */ return false; } if (!crl.getIssuerX500Principal().equals(targetCertificate.getIssuerX500Principal())) { logger.debug("CRL issuer and certificate issuer do not match."); return false; } IssuingDistributionPoint idp = X509CRLInspector.getIssuingDistributionPoint(crl); /* if there is no IssuingDistributionPoint there is always a match */ if (idp == null) { return true; } DistributionPointName idpn = idp.getDistributionPoint(); CRLDistPoint crlDistPoint = X509CertificateInspector.getCRLDistibutionPoints(targetCertificate); DistributionPoint[] dps = null; if (crlDistPoint != null) { dps = crlDistPoint.getDistributionPoints(); } if (dps != null) { for (DistributionPoint dp : dps) { if (dp == null) { logger.debug("Distributionpoint is null."); continue; } if (dp.getCRLIssuer() != null) { /* we do not support indirect CRLs */ logger.debug("CRL issuer should only be used for indirect CRLs."); continue; } DistributionPointName dpn = dp.getDistributionPoint(); if (idp != null) { if (idpn != null && dpn != null) { X500Principal issuer = targetCertificate.getIssuerX500Principal(); if (hasMatchingName(idpn, dpn, issuer)) { match = true; break; } } } } if (!match) { logger.debug("The CRL did not contain matching DistributionPoint names."); } } else { match = (idpn == null); } BasicConstraints basicConstraints = X509CertificateInspector.getBasicConstraints(targetCertificate); if (idp != null) { /* if basicConstraints is null assume it's a user certificate */ if (idp.onlyContainsCACerts() && ((basicConstraints != null && !basicConstraints.isCA()) | basicConstraints == null)) { logger.debug("Certificate is a user certificate but CRL only contains CA certificate."); match = false; } if (idp.onlyContainsUserCerts() && basicConstraints != null && basicConstraints.isCA()) { logger.debug("Certificate is a CA but CRL only contains user certificates."); match = false; } if (idp.onlyContainsAttributeCerts()) { logger.debug("Certificate only contains attribute certs."); match = false; } } return match; } /* * Checks whether the delta CRL and base CRL are valid */ private boolean checkDeltaCRL_6_3_3_b(X509Certificate targetCertificate, X509CRL deltaCRL, X509CRL baseCRL) throws IOException { if (!X509CRLInspector.isDeltaCRL(deltaCRL)) { logger.debug("CRL is not a delta CRL."); return false; } if (X509CRLInspector.isDeltaCRL(baseCRL)) { logger.debug("CRL is not a base CRL it's a delta CRL."); return false; } if (!deltaCRL.getIssuerX500Principal().equals(baseCRL.getIssuerX500Principal())) { logger.debug("Delta CRL issuer does not match Base CRL issuer."); return false; } IssuingDistributionPoint deltaIDP = X509CRLInspector.getIssuingDistributionPoint(deltaCRL); IssuingDistributionPoint baseIDP = X509CRLInspector.getIssuingDistributionPoint(baseCRL); if (baseIDP != null) { if (!baseIDP.equals(deltaIDP)) { logger.debug("The Base CRL has a non matching IssuingDistributionPoint."); return false; } } else { if (deltaIDP != null) { logger.debug("The Delta CRL has a non matching IssuingDistributionPoint."); return false; } } AuthorityKeyIdentifier baseAKI = X509CRLInspector.getAuthorityKeyIdentifier(baseCRL); AuthorityKeyIdentifier deltaAKI = X509CRLInspector.getAuthorityKeyIdentifier(deltaCRL); if (baseAKI != null) { if (!baseAKI.equals(deltaAKI)) { logger.debug("Base AuthorityKeyIdentifier does not match Delta AuthorityKeyIdentifier."); return false; } } else { if (deltaAKI != null) { logger.debug("Delta AuthorityKeyIdentifier does not match Base AuthorityKeyIdentifier."); return false; } } return true; } /* * Returns a mask containing the reasons covered by the given CRL. */ private int getInterimReasonsMask(X509Certificate targetCertificate, X509CRL crl) throws IOException { IssuingDistributionPoint idp = X509CRLInspector.getIssuingDistributionPoint(crl); CRLDistPoint crlDistPoint = X509CertificateInspector.getCRLDistibutionPoints(targetCertificate); DistributionPoint[] dps = null; if (crlDistPoint != null) { dps = crlDistPoint.getDistributionPoints(); } int interimMask = 0; if (idp != null && idp.getOnlySomeReasons() != null) { ReasonFlags irf = idp.getOnlySomeReasons(); if (dps != null) { for (DistributionPoint dp : dps) { if (dp == null) { logger.debug("Distributionpoint is null."); continue; } /* 6.3.3 (d)(1) */ if (dp.getReasons() != null) { ReasonFlags drf = dp.getReasons(); int intersection = irf.intValue() & drf.intValue(); interimMask = interimMask | intersection; } /* 6.3.3 (d)(2) */ else { interimMask = interimMask | irf.intValue(); } } } else { /* 6.3.3 (d)(2) */ interimMask = interimMask | irf.intValue(); } } /* 6.3.3 (d)(3) */ else { if (dps != null) { for (DistributionPoint dp : dps) { if (dp == null) { logger.debug("Distributionpoint is null."); continue; } if (dp.getReasons() != null) { ReasonFlags drf = dp.getReasons(); interimMask = interimMask | drf.intValue(); } else { interimMask = interimMask | allReasons; } } } else { interimMask = interimMask | allReasons; } } return interimMask; } /* * Do some initial filtering like not accepting indirect CRLs. */ private boolean preFilter(X509Certificate targetCertificate, X509CRL crl) throws IOException { IssuingDistributionPoint idp = X509CRLInspector.getIssuingDistributionPoint(crl); if (idp != null) { if (idp.isIndirectCRL()) { logger.debug("CRL is indirect."); return false; } } if (!crl.getIssuerX500Principal().equals(targetCertificate.getIssuerX500Principal())) { logger.debug("CRL issuer and certificate issuer do not match."); return false; } return true; } /* * Filters out CRLs that should not be searched for because they are not correct or corrupt. */ private boolean acceptCRL(X509Certificate targetCertificate, X509CRL crl, PublicKey issuerPublicKey, Date now) { boolean accept = false; try { /* make sure the CRL is signed by the issuer. */ crl.verify(issuerPublicKey, securityFactory.getNonSensitiveProvider()); try { if (preFilter(targetCertificate, crl)) { accept = true; } } catch (IOException e) { logger.error("IO Error pre-filtering the CRL and certificate.", e); } } catch (SignatureException e) { LogUtils.logWarnStackTraceOnDebug(logger, "CRL could not be verified. Hash not correct", e); accept = false; } catch (Exception e) { LogUtils.logErrorStackTraceOnDebug(logger, "CRL could not be verified.", e); accept = false; } return accept; } /* * Returns a list of all the CRLs matching the selector and which are valid (ie not expired) for the * given certificate. The CRLs are verified to see if they are signed by the correct CA (identified * by the public key) */ private List<X509CRL> findCRLs(X509Certificate targetCertificate, X509CRLSelector crlSelector, PublicKey issuerPublicKey, Date now) throws NoSuchProviderException { List<X509CRL> crls = new LinkedList<X509CRL>(); /* step through all the stores and get all the relevant CRLs from the stores */ for (BasicCRLStore store : crlStores) { try { CloseableIterator<? extends CRL> crlIterator = store.getCRLIterator(crlSelector); try { while (crlIterator.hasNext()) { CRL crl = crlIterator.next(); if (!(crl instanceof X509CRL)) { logger.warn("Only X509CRLs are supported. Skipping this CRL."); continue; } X509CRL x509CRL = (X509CRL) crl; if (acceptCRL(targetCertificate, x509CRL, issuerPublicKey, now)) { crls.add(x509CRL); } } } finally { crlIterator.close(); } } catch (CRLStoreException e) { /* log and continue search */ logger.error("Error getting CRLs. Skipping this store.", e); continue; } catch (CloseableIteratorException e) { /* log and continue search */ logger.error("Error stepping through the CRL store. Skipping this store.", e); continue; } } return crls; } /* * Do revocation checking for the target Certificate */ private RevocationDetail getRevocationDetail(List<X509CRL> crls, X509Certificate targetCertificate, X509Certificate issuerCertificate, PublicKey issuerPublicKey, Date now) throws NoSuchProviderException { RevocationDetailImpl detail = new RevocationDetailImpl(RevocationStatus.UNKNOWN); boolean validCRLFound = false; int reasonMask = 0; for (X509CRL crl : crls) { BigInteger serialNumber = targetCertificate.getSerialNumber(); X509CRLEntry crlEntry = crl.getRevokedCertificate(serialNumber); Date revocationDate = null; if (crlEntry != null) { revocationDate = crlEntry.getRevocationDate(); if (revocationDate == null || !now.before(revocationDate)) { /* * X.509 7.3 NOTE 4 When an implementation processing a certificate revocation list does not * recognize a critical extension in the crlEntryExtensions field, it shall assume that, * at a minimum, the identified certificate has been revoked and is no longer valid and * perform additional actions concerning that revoked certificate as dictated by local policy. * * We do not need to check for unsupported critical extension because if we do not support them * we should assume that the certificate is revoked. */ // TODO: add support for onHold/removeFromCRL Integer reasonCode = null; try { reasonCode = X509CRLEntryInspector.getReasonCode(crlEntry); } catch (IOException e) { logger.error("Error retrieving reasonCode.", e); } detail = (reasonCode != null ? new RevocationDetailImpl(RevocationStatus.REVOKED, reasonCode) : new RevocationDetailImpl(RevocationStatus.REVOKED)); /* there is no need to continue because certificate is revoked */ break; } else { if (now.before(revocationDate)) { logger.info("Certificate is revoked in the future."); } } } if (hasUnsupportedCriticalExtensions(crl)) { logger.debug("The CRL has unsupported critical extensions."); detail = new RevocationDetailImpl(RevocationStatus.UNSUPPORTED_CRITICAL_EXTENSION); continue; } /* * check that the start time the CRL is valid is before the time the certificate is * no longer valid. In other words, that the expiration date of the certificate is * later than the date the CRL was issued. It is possible that the certificate was * at some point revoked but the CA removed it because the certificate is no longer * valid */ if (crl.getThisUpdate() != null && targetCertificate.getNotAfter().before(crl.getThisUpdate())) { logger.info("Certificate has expired before the CRL was valid."); continue; } try { if (X509CRLInspector.isDeltaCRL(crl)) { DeltaCRLStatus deltaStatus = getDeltaCRLStatus(targetCertificate, crl, issuerPublicKey, now); if (deltaStatus == DeltaCRLStatus.UNSUPPORTED_CRITICAL_EXTENSION) { detail = new RevocationDetailImpl(RevocationStatus.UNSUPPORTED_CRITICAL_EXTENSION); continue; } else if (deltaStatus == DeltaCRLStatus.UNKNOWN) { continue; } } else { if (!acceptCRL_6_3_3_b(targetCertificate, crl)) { logger.debug("CRL not valid according to acceptCRL_6_3_3_b."); continue; } } } catch (IOException e) { logger.error("Error inspecting CRL.", e); continue; } if (crl.getNextUpdate() != null && now.after(crl.getNextUpdate())) { /* * an CRL cannot really expire but, when we want at least to log that the * nextUpdate is overdue */ logger.debug("The CRL next update is overdue."); /* we need to set the nextUpdate if this is a newer CRL */ if (detail.getStatus() != RevocationStatus.EXPIRED || detail.getNextUpdate() == null) { detail = new RevocationDetailImpl(RevocationStatus.EXPIRED, crl.getNextUpdate()); } else { if (crl.getNextUpdate().after(detail.getNextUpdate())) { /* the nextUpdate of the current CRL is later so it's longer valid */ detail = new RevocationDetailImpl(RevocationStatus.EXPIRED, crl.getNextUpdate()); } } continue; } try { reasonMask = reasonMask | getInterimReasonsMask(targetCertificate, crl); /* a valid crl was found. Continue search. */ validCRLFound = true; } catch (IOException e) { logger.error("Error getting interim mask.", e); } } /* * if one the CRLs was good and the certificate was not revoked we will set the * status to NOT_REVOKED */ if (validCRLFound && detail.getStatus() != RevocationStatus.REVOKED) { /* check if all reasons are covered */ if (reasonMask == allReasons) { detail = new RevocationDetailImpl(RevocationStatus.NOT_REVOKED); } else { logger.debug("Not all reasons were covered."); detail = new RevocationDetailImpl(RevocationStatus.UNKNOWN); } } return detail; } /* * Implementation class keeping track of the revocation details of each certificate in * the path. */ protected static class RevocationResultImpl implements RevocationResult { private final RevocationDetail[] details; public RevocationResultImpl(int detailsSize) { details = new RevocationDetail[detailsSize]; for (int i = 0; i < details.length; i++) { details[i] = new RevocationDetailImpl(); } } /* * Returns a positive number for a revocation detail based on the importance of the revocation reason. * 0 is least important (ie. not revoked) and the highest number is most important (revoked). This * method is used to create an order on the importance. */ private int getImportanceLevel(RevocationDetail detail) { switch (detail.getStatus()) { case NOT_REVOKED: return 0; case EXPIRED: return 1; case UNKNOWN: return 2; case UNSUPPORTED_CRITICAL_EXTENSION: return 3; case REVOKED: return 4; default: logger.warn("Unknown revocation status."); return Integer.MAX_VALUE; } } /* * Returns the accumulated detail. The returned result will be the most important result * from one of the details ie. if a certificate in the chain is revoked the result will * be revoked etc. The ordering of the revocation result is determined by getImportanceLevel. */ private RevocationDetail getOveralDetail() { RevocationDetail mostImportantDetail = null; int maxLevel = -1; /* * step through all entries and update the current based upon the level */ for (RevocationDetail detail : details) { int level = getImportanceLevel(detail); if (level > maxLevel) { /* this revocation status is more important than the current status */ maxLevel = level; mostImportantDetail = detail; } } if (mostImportantDetail == null) { mostImportantDetail = new RevocationDetailImpl(RevocationStatus.UNKNOWN); } return mostImportantDetail; } /* * Returns the accumulated status. If all certificates are NOT_REVOKED the * result will be NOT_REVOKED. If one certificate has another status that * status will be returned */ @Override public RevocationStatus getStatus() { return getOveralDetail().getStatus(); } @Override public RevocationReason getReason() { return getOveralDetail().getReason(); } @Override public RevocationDetail[] getDetails() { return details; } @Override public String toString() { StrBuilder sb = new StrBuilder(1024); sb.appendAll(details); return sb.toString(); } } /* * Revocation detail for one certificate in the chain */ protected static class RevocationDetailImpl implements RevocationDetail { private final RevocationStatus status; private RevocationReason reason; private Date nextUpdate; public RevocationDetailImpl(RevocationStatus status, int reasonTag) { Check.notNull(status, "status"); this.status = status; this.reason = RevocationReason.fromTag(reasonTag); } public RevocationDetailImpl(RevocationStatus status, Date nextUpdate) { Check.notNull(status, "status"); this.status = status; this.reason = null; this.nextUpdate = nextUpdate; } public RevocationDetailImpl(RevocationStatus status) { Check.notNull(status, "status"); this.status = status; this.reason = null; } public RevocationDetailImpl() { this.status = RevocationStatus.UNKNOWN; this.reason = null; } @Override public RevocationStatus getStatus() { return status; } @Override public RevocationReason getReason() { return reason; } @Override public Date getNextUpdate() { return nextUpdate; } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append("Status: " + status.toString()); if (reason != null) { sb.append("; Reason: " + reason.getFriendlyName()); } if (nextUpdate != null) { sb.append("; nextUpdate: " + nextUpdate); } return sb.toString(); } } }