/** * DSS - Digital Signature Services * Copyright (C) 2015 European Commission, provided under the CEF programme * * This file is part of the "DSS - Digital Signature Services" project. * * This library 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. * * This library 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 this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ package eu.europa.esig.dss.validation; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; import javax.security.auth.x500.X500Principal; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.esig.dss.DSSASN1Utils; import eu.europa.esig.dss.DSSException; import eu.europa.esig.dss.DSSUtils; import eu.europa.esig.dss.client.http.DataLoader; import eu.europa.esig.dss.x509.CertificatePool; import eu.europa.esig.dss.x509.CertificateSourceType; import eu.europa.esig.dss.x509.CertificateToken; import eu.europa.esig.dss.x509.RevocationToken; import eu.europa.esig.dss.x509.Token; import eu.europa.esig.dss.x509.crl.CRLSource; import eu.europa.esig.dss.x509.ocsp.OCSPSource; /** * During the validation of a signature, the software retrieves different X509 artifacts like Certificate, CRL and OCSP * Response. The SignatureValidationContext is a "cache" for * one validation request that contains every object retrieved so far. * */ public class SignatureValidationContext implements ValidationContext { private static final Logger logger = LoggerFactory.getLogger(SignatureValidationContext.class); private final Set<CertificateToken> processedCertificates = new HashSet<CertificateToken>(); private final Set<RevocationToken> processedRevocations = new HashSet<RevocationToken>(); private final Set<TimestampToken> processedTimestamps = new HashSet<TimestampToken>(); /** * The data loader used to access AIA certificate source. */ private DataLoader dataLoader; /** * The certificate pool which encapsulates all certificates used during the validation process and extracted from * all used sources */ protected CertificatePool validationCertificatePool; private final Map<Token, Boolean> tokensToProcess = new HashMap<Token, Boolean>(); // External OCSP source. private OCSPSource ocspSource; // External CRL source. private CRLSource crlSource; // CRLs from the signature. private CRLSource signatureCRLSource; // OCSP from the signature. private OCSPSource signatureOCSPSource; // The digest value of the certification path references and the revocation status references. private List<TimestampReference> timestampedReferences; /** * This is the time at what the validation is carried out. It is used only for test purpose. */ protected Date currentTime = new Date(); /** * This constructor is used during the signature creation process. The certificate pool is created within initialize * method. */ public SignatureValidationContext() { } /** * This constructor is used when a signature need to be validated. * * @param validationCertificatePool * The pool of certificates used during the validation process */ public SignatureValidationContext(final CertificatePool validationCertificatePool) { if (validationCertificatePool == null) { throw new NullPointerException(); } this.validationCertificatePool = validationCertificatePool; } /** * @param certificateVerifier * The certificates verifier (eg: using the TSL as list of trusted certificates). */ @Override public void initialize(final CertificateVerifier certificateVerifier) { if (certificateVerifier == null) { throw new NullPointerException(); } if (validationCertificatePool == null) { validationCertificatePool = certificateVerifier.createValidationPool(); } this.crlSource = certificateVerifier.getCrlSource(); this.ocspSource = certificateVerifier.getOcspSource(); this.dataLoader = certificateVerifier.getDataLoader(); this.signatureCRLSource = certificateVerifier.getSignatureCRLSource(); this.signatureOCSPSource = certificateVerifier.getSignatureOCSPSource(); } @Override public Date getCurrentTime() { return currentTime; } @Override public void setCurrentTime(final Date currentTime) { if (currentTime == null) { throw new NullPointerException(); } this.currentTime = currentTime; } /** * This method returns a token to verify. If there is no more tokens to verify null is returned. * * @return token to verify or null */ private Token getNotYetVerifiedToken() { synchronized (tokensToProcess) { for (final Entry<Token, Boolean> entry : tokensToProcess.entrySet()) { if (entry.getValue() == null) { entry.setValue(true); return entry.getKey(); } } return null; } } /** * This method returns the issuer certificate (the certificate which was used to sign the token) of the given token. * * @param token * the token for which the issuer must be obtained. * @return the issuer certificate token of the given token or null if not found. * @throws eu.europa.esig.dss.DSSException */ private CertificateToken getIssuerCertificate(final Token token) throws DSSException { if (token.isTrusted()) { // When the token is trusted the check of the issuer token is not needed so null is returned. Only a // certificate token can be trusted. return null; } if (token.getIssuerToken() != null) { /** * The signer's certificate have been found already. This can happen in the case of:<br> * - multiple signatures that use the same certificate,<br> * - OCSPRespTokens (the issuer certificate is known from the beginning) */ return token.getIssuerToken(); } final X500Principal issuerX500Principal = token.getIssuerX500Principal(); CertificateToken issuerCertificateToken = getIssuerFromPool(token, issuerX500Principal); if ((issuerCertificateToken == null) && (token instanceof CertificateToken)) { issuerCertificateToken = getIssuerFromAIA((CertificateToken) token); } if (issuerCertificateToken == null) { token.extraInfo().infoTheSigningCertNotFound(); } if ((issuerCertificateToken != null) && !issuerCertificateToken.isTrusted() && !issuerCertificateToken.isSelfSigned()) { // The full chain is retrieved for each certificate getIssuerCertificate(issuerCertificateToken); } return issuerCertificateToken; } /** * Get the issuer's certificate from Authority Information Access through id-ad-caIssuers extension. * * @param token * {@code CertificateToken} for which the issuer is sought. * @return {@code CertificateToken} representing the issuer certificate or null. */ private CertificateToken getIssuerFromAIA(final CertificateToken token) { final CertificateToken issuerCert; try { logger.info("Retrieving {} certificate's issuer using AIA.", token.getAbbreviation()); issuerCert = DSSUtils.loadIssuerCertificate(token, dataLoader); if (issuerCert != null) { final CertificateToken issuerCertToken = validationCertificatePool.getInstance(issuerCert, CertificateSourceType.AIA); if (token.isSignedBy(issuerCertToken)) { return issuerCertToken; } logger.info("The retrieved certificate using AIA does not sign the certificate {}.", token.getAbbreviation()); } else { logger.info("The issuer certificate cannot be loaded using AIA."); } } catch (DSSException e) { logger.error(e.getMessage()); } return null; } /** * This function retrieves the issuer certificate from the validation pool (this pool should contain trusted * certificates). The check is made if the token is well signed by * the retrieved certificate. * * @param token * token for which the issuer have to be found * @param issuerX500Principal * issuer's subject distinguished name * @return the corresponding {@code CertificateToken} or null if not found */ private CertificateToken getIssuerFromPool(final Token token, final X500Principal issuerX500Principal) { final List<CertificateToken> issuerCertList = validationCertificatePool.get(issuerX500Principal); for (final CertificateToken issuerCertToken : issuerCertList) { // We keep the first issuer that signs the certificate if (token.isSignedBy(issuerCertToken)) { return issuerCertToken; } } return null; } /** * Adds a new token to the list of tokes to verify only if it was not already verified. * * @param token * token to verify * @return true if the token was not yet verified, false otherwise. */ private boolean addTokenForVerification(final Token token) { if (token == null) { return false; } final boolean traceEnabled = logger.isTraceEnabled(); if (traceEnabled) { logger.trace("addTokenForVerification: trying to acquire synchronized block"); } synchronized (tokensToProcess) { try { if (tokensToProcess.containsKey(token)) { if (traceEnabled) { logger.trace("Token was already in the list {}:{}", new Object[] { token.getClass().getSimpleName(), token.getAbbreviation() }); } return false; } tokensToProcess.put(token, null); if (traceEnabled) { logger.trace("+ New {} to check: {}", new Object[] { token.getClass().getSimpleName(), token.getAbbreviation() }); } return true; } finally { if (traceEnabled) { logger.trace("addTokenForVerification: almost left synchronized block"); } } } } @Override public void addRevocationTokensForVerification(final List<RevocationToken> revocationTokens) { for (RevocationToken revocationToken : revocationTokens) { if (addTokenForVerification(revocationToken)) { final boolean added = processedRevocations.add(revocationToken); if (logger.isTraceEnabled()) { if (added) { logger.trace("RevocationToken added to processedRevocations: {} ", revocationToken); } else { logger.trace("RevocationToken already present processedRevocations: {} ", revocationToken); } } } } } @Override public void addCertificateTokenForVerification(final CertificateToken certificateToken) { if (addTokenForVerification(certificateToken)) { final boolean added = processedCertificates.add(certificateToken); if (logger.isTraceEnabled()) { if (added) { logger.trace("CertificateToken added to processedRevocations: {} ", certificateToken); } else { logger.trace("CertificateToken already present processedRevocations: {} ", certificateToken); } } } } @Override public void addTimestampTokenForVerification(final TimestampToken timestampToken) { if (addTokenForVerification(timestampToken)) { final boolean added = processedTimestamps.add(timestampToken); if (logger.isTraceEnabled()) { if (added) { logger.trace("TimestampToken added to processedRevocations: {} ", processedTimestamps); } else { logger.trace("TimestampToken already present processedRevocations: {} ", processedTimestamps); } } } } @Override public void validate() throws DSSException { Token token = null; do { token = getNotYetVerifiedToken(); if (token != null) { /** * Gets the issuer certificate of the Token and checks its signature */ final CertificateToken issuerCertToken = getIssuerCertificate(token); if (issuerCertToken != null) { addCertificateTokenForVerification(issuerCertToken); } if (token instanceof CertificateToken) { final List<RevocationToken> revocationTokens = getRevocationData((CertificateToken) token); addRevocationTokensForVerification(revocationTokens); } } } while (token != null); } /** * Retrieves the revocation data from signature (if exists) or from the online sources. The issuer certificate must * be provided, the underlining library (bouncy castle) needs * it to build the request. * * @param certToken * @return */ private List<RevocationToken> getRevocationData(final CertificateToken certToken) { if (logger.isTraceEnabled()) { logger.trace("Checking revocation data for: " + certToken.getDSSIdAsString()); } if (certToken.isSelfSigned() || certToken.isTrusted() || (certToken.getIssuerToken() == null)) { // It is not possible to check the revocation data without its signing certificate; // This check is not needed for the trust anchor. return Collections.emptyList(); } if (DSSASN1Utils.hasIdPkixOcspNoCheckExtension(certToken)) { certToken.extraInfo().infoOCSPNoCheckPresent(); return Collections.emptyList(); } List<RevocationToken> revocations = new ArrayList<RevocationToken>(); // ALL Embedded revocation data OCSPAndCRLCertificateVerifier offlineVerifier = new OCSPAndCRLCertificateVerifier(signatureCRLSource, signatureOCSPSource, validationCertificatePool); RevocationToken ocspToken = offlineVerifier.checkOCSP(certToken); if (ocspToken != null) { revocations.add(ocspToken); } RevocationToken crlToken = offlineVerifier.checkCRL(certToken); if (crlToken != null) { revocations.add(crlToken); } // Online resources (OCSP and CRL if OCSP doesn't reply) final OCSPAndCRLCertificateVerifier onlineVerifier = new OCSPAndCRLCertificateVerifier(crlSource, ocspSource, validationCertificatePool); final RevocationToken onlineRevocationToken = onlineVerifier.check(certToken); // CRL can already exist in the signature if (onlineRevocationToken != null && !revocations.contains(onlineRevocationToken)) { revocations.add(onlineRevocationToken); } return revocations; } @Override public Set<CertificateToken> getProcessedCertificates() { return Collections.unmodifiableSet(processedCertificates); } @Override public Set<RevocationToken> getProcessedRevocations() { return Collections.unmodifiableSet(processedRevocations); } @Override public Set<TimestampToken> getProcessedTimestamps() { return Collections.unmodifiableSet(processedTimestamps); } /** * Returns certificate and revocation references. * * @return */ public List<TimestampReference> getTimestampedReferences() { return timestampedReferences; } /** * This method returns the human readable representation of the ValidationContext. * * @param indentStr * @return */ public String toString(String indentStr) { try { final StringBuilder builder = new StringBuilder(); builder.append(indentStr).append("ValidationContext[").append('\n'); indentStr += "\t"; // builder.append(indentStr).append("Validation time:").append(validationDate).append('\n'); builder.append(indentStr).append("Certificates[").append('\n'); indentStr += "\t"; for (CertificateToken certToken : processedCertificates) { builder.append(certToken.toString(indentStr)); } indentStr = indentStr.substring(1); builder.append(indentStr).append("],\n"); indentStr = indentStr.substring(1); builder.append(indentStr).append("],\n"); return builder.toString(); } catch (Exception e) { return super.toString(); } } @Override public String toString() { return toString(""); } }