/* * SD-DSS - Digital Signature Services * * Copyright (C) 2015 ARHS SpikeSeed S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-spikeseed.com * * Developed by: 2015 ARHS SpikeSeed S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-spikeseed.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" is free software: you can redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the * License, or (at your option) any later version. * * DSS 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.validation102853.crl; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; import java.security.NoSuchProviderException; import java.security.SignatureException; import java.security.cert.CRLException; import java.security.cert.X509CRL; import java.util.ArrayList; import java.util.List; import java.util.Set; import javax.security.auth.x500.X500Principal; import org.bouncycastle.asn1.ASN1OctetString; import org.bouncycastle.asn1.ASN1Primitive; import org.bouncycastle.asn1.ASN1Sequence; import org.bouncycastle.asn1.DERIA5String; import org.bouncycastle.asn1.DERTaggedObject; 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.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.ec.markt.dss.DSSASN1Utils; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.validation102853.CertificateToken; import eu.europa.ec.markt.dss.validation102853.RevocationToken; import eu.europa.ec.markt.dss.validation102853.loader.Protocol; import sun.security.x509.PKIXExtensions; import static org.bouncycastle.asn1.x509.DistributionPointName.FULL_NAME; import static org.bouncycastle.asn1.x509.GeneralName.uniformResourceIdentifier; /** * This is the representation of simple (common) CRL source, this is the base class for all real implementations. * <p/> * * @author Robert Bielecki * @version $Revision: 1016 $ - $Date: 2011-06-17 15:30:45 +0200 (Fri, 17 Jun 2011) $ */ public abstract class CommonCRLSource implements CRLSource { private static final Logger LOG = LoggerFactory.getLogger(CommonCRLSource.class); /** * Gives back the {@code List} of CRL URI meta-data found within the given X509 certificate. * * @param certificateToken the X509 certificate * @param preferredProtocol * @param preferredProtocol indicates the preferred protocol to use to retrieve the revocation data * @return the {@code List} of CRL URI, or {@code null} if the extension is not present * @throws DSSException in the case of any {@code Exception} */ public List<String> getCrlUrl(final CertificateToken certificateToken, final Protocol preferredProtocol) throws DSSException { final byte[] crlDistributionPointsBytes = certificateToken.getCRLDistributionPoints(); if (null == crlDistributionPointsBytes) { if (LOG.isTraceEnabled()) { LOG.trace("CRL's URL(s) for {} : there is no distribution point(s) extension!", certificateToken.getAbbreviation()); } return null; } try { final List<String> urls = new ArrayList<String>(); final ASN1Sequence asn1Sequence = DSSASN1Utils.getAsn1SequenceFromDerOctetString(crlDistributionPointsBytes); final CRLDistPoint distPoint = CRLDistPoint.getInstance(asn1Sequence); final DistributionPoint[] distributionPoints = distPoint.getDistributionPoints(); for (final DistributionPoint distributionPoint : distributionPoints) { final DistributionPointName distributionPointName = distributionPoint.getDistributionPoint(); if (FULL_NAME != distributionPointName.getType()) { LOG.warn("'nameRelativeToCRLIssuer': not supported!"); continue; } final GeneralNames generalNames = (GeneralNames) distributionPointName.getName(); final GeneralName[] names = generalNames.getNames(); for (final GeneralName name : names) { if (uniformResourceIdentifier != name.getTagNo()) { LOG.warn("Not a uniform resource identifier!"); continue; } ASN1Primitive asn1Primitive = name.toASN1Primitive(); if (asn1Primitive instanceof DERTaggedObject) { final DERTaggedObject taggedObject = (DERTaggedObject) asn1Primitive; asn1Primitive = taggedObject.getObject(); } final DERIA5String derStr = DERIA5String.getInstance(asn1Primitive); final String urlStr = derStr.getString(); urls.add(urlStr); } } prioritize(urls, preferredProtocol); if (LOG.isTraceEnabled()) { LOG.trace("CRL's URL for {} : {}", certificateToken.getAbbreviation(), urls); } return urls; } catch (Exception e) { if (e instanceof DSSException) { throw (DSSException) e; } throw new DSSException(e); } } /** * if {@code preferredProtocol} is set then the list of urls is prioritize. * NOTE: This is not standard conformant! However in the major number of cases LDAP is much slower then HTTP! * * @param urls {@code List} of urls to prioritize * @param preferredProtocol indicates the preferred protocol to use to retrieve the revocation data */ private void prioritize(final List<String> urls, final Protocol preferredProtocol) { if (preferredProtocol != null) { final List<String> priorityUrls = new ArrayList<String>(); for (final String url : urls) { if (preferredProtocol.isTheSame(url)) { priorityUrls.add(url); } } urls.removeAll(priorityUrls); for (int ii = priorityUrls.size() - 1; ii >= 0; ii--) { urls.add(0, priorityUrls.get(ii)); } } } /** * This method verifies: the signature of the CRL, the key usage of its signing certificate and the coherence between the subject names of the CRL signing certificate and the * issuer name of the certificate for which the verification of the revocation data is carried out. A dedicated object based on {@code CRLValidity} is created and accordingly * updated. * * @param x509CRL {@code X509CRL} to be verified (cannot be null) * @param issuerToken {@code CertificateToken} used to sign the {@code X509CRL} (cannot be null) @return {@code CRLValidity} * @param dpUrlStringList {@code List} of {@code String} representation of the DP's url */ protected CRLValidity isValidCRL(final X509CRL x509CRL, final CertificateToken issuerToken, final List<String> dpUrlStringList) { final CRLValidity crlValidity = new CRLValidity(); crlValidity.x509CRL = x509CRL; final X500Principal x509CRLIssuerX500Principal = DSSUtils.getX500Principal(x509CRL.getIssuerX500Principal()); final X500Principal issuerTokenSubjectX500Principal = DSSUtils.getX500Principal(issuerToken.getSubjectX500Principal()); if (x509CRLIssuerX500Principal.equals(issuerTokenSubjectX500Principal)) { crlValidity.issuerX509PrincipalMatches = true; } checkCriticalExtensions(x509CRL, dpUrlStringList, crlValidity); checkSignatureValue(x509CRL, issuerToken, crlValidity); if (crlValidity.signatureIntact) { crlValidity.crlSignKeyUsage = issuerToken.hasCRLSignKeyUsage(); } return crlValidity; } private void checkSignatureValue(final X509CRL x509CRL, final CertificateToken issuerToken, final CRLValidity crlValidity) { try { x509CRL.verify(issuerToken.getPublicKey()); crlValidity.signatureIntact = true; crlValidity.issuerToken = issuerToken; } catch (InvalidKeyException e) { crlValidity.signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage(); } catch (CRLException e) { crlValidity.signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage(); } catch (NoSuchAlgorithmException e) { crlValidity.signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage(); } catch (SignatureException e) { crlValidity.signatureInvalidityReason = e.getClass().getSimpleName() + " - " + e.getMessage(); } catch (NoSuchProviderException e) { throw new DSSException(e); } } private void checkCriticalExtensions(final X509CRL x509CRL, final List<String> dpUrlStringList, final CRLValidity crlValidity) { final Set<String> criticalExtensionOIDs = x509CRL.getCriticalExtensionOIDs(); if (criticalExtensionOIDs == null || criticalExtensionOIDs.size() == 0) { crlValidity.unknownCriticalExtension = false; return; } final String issuingDistributionPointOid = PKIXExtensions.IssuingDistributionPoint_Id.toString(); for (final String criticalExtensionOID : criticalExtensionOIDs) { if (issuingDistributionPointOid.equals(criticalExtensionOID)) { final byte[] extensionValue = x509CRL.getExtensionValue(issuingDistributionPointOid); final ASN1OctetString asn1OctetStringExtensionValue = ASN1OctetString.getInstance(extensionValue); final IssuingDistributionPoint issuingDistributionPoint = IssuingDistributionPoint.getInstance(asn1OctetStringExtensionValue.getOctets()); final boolean onlyAttributeCerts = issuingDistributionPoint.onlyContainsAttributeCerts(); final boolean onlyCaCerts = issuingDistributionPoint.onlyContainsCACerts(); final boolean onlyUserCerts = issuingDistributionPoint.onlyContainsUserCerts(); final boolean indirectCrl = issuingDistributionPoint.isIndirectCRL(); final ReasonFlags reasonFlags = issuingDistributionPoint.getOnlySomeReasons(); final DistributionPointName distributionPointName = issuingDistributionPoint.getDistributionPoint(); boolean urlFound = false; if (FULL_NAME == distributionPointName.getType()) { final GeneralNames generalNames = (GeneralNames) distributionPointName.getName(); if (generalNames != null) { final GeneralName[] names = generalNames.getNames(); if (names != null && names.length > 0) { for (final GeneralName generalName : names) { if (uniformResourceIdentifier == generalName.getTagNo()) { final String name = generalName.getName().toString(); if (DSSUtils.isNotEmpty(dpUrlStringList) && dpUrlStringList.contains(name)) { urlFound = true; } } } } } } if (!(onlyAttributeCerts && onlyCaCerts && onlyUserCerts && indirectCrl) && reasonFlags == null && urlFound) { crlValidity.unknownCriticalExtension = false; } continue; } crlValidity.unknownCriticalExtension = true; } } @Override public boolean isFresh(final RevocationToken revocationToken) { return false; } }