All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.europa.esig.dss.validation.SignatureValidationContext Maven / Gradle / Ivy

Go to download

DSS Document contains the code for the creation and validation of XAdES, CAdES, PAdES and ASiC signatures._

There is a newer version: 6.0.d4j.2
Show newest version
/**
 * 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.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Set;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.europa.esig.dss.CertificateReorderer;
import eu.europa.esig.dss.alert.status.Status;
import eu.europa.esig.dss.enumerations.CertificateSourceType;
import eu.europa.esig.dss.enumerations.RevocationReason;
import eu.europa.esig.dss.model.x509.CertificateToken;
import eu.europa.esig.dss.model.x509.Token;
import eu.europa.esig.dss.model.x509.X500PrincipalHelper;
import eu.europa.esig.dss.model.x509.revocation.Revocation;
import eu.europa.esig.dss.model.x509.revocation.crl.CRL;
import eu.europa.esig.dss.model.x509.revocation.ocsp.OCSP;
import eu.europa.esig.dss.spi.DSSASN1Utils;
import eu.europa.esig.dss.spi.client.http.DataLoader;
import eu.europa.esig.dss.spi.x509.AlternateUrlsSourceAdapter;
import eu.europa.esig.dss.spi.x509.CandidatesForSigningCertificate;
import eu.europa.esig.dss.spi.x509.CertificateRef;
import eu.europa.esig.dss.spi.x509.CertificateSource;
import eu.europa.esig.dss.spi.x509.CertificateValidity;
import eu.europa.esig.dss.spi.x509.CommonTrustedCertificateSource;
import eu.europa.esig.dss.spi.x509.ListCertificateSource;
import eu.europa.esig.dss.spi.x509.ResponderId;
import eu.europa.esig.dss.spi.x509.revocation.RevocationCertificateSource;
import eu.europa.esig.dss.spi.x509.revocation.RevocationSource;
import eu.europa.esig.dss.spi.x509.revocation.RevocationSourceAlternateUrlsSupport;
import eu.europa.esig.dss.spi.x509.revocation.RevocationToken;
import eu.europa.esig.dss.spi.x509.revocation.ocsp.OCSPToken;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.validation.timestamp.TimestampToken;
import eu.europa.esig.dss.validation.timestamp.TimestampedReference;

/**
 * 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 LOG = LoggerFactory.getLogger(SignatureValidationContext.class);

	private final Set processedCertificates = new HashSet<>();
	private final Set> processedRevocations = new HashSet<>();
	private final Set processedTimestamps = new HashSet<>();

	private CertificateVerifier certificateVerifier;

	/**
	 * The data loader used to access AIA certificate source.
	 */
	private DataLoader dataLoader;

	private final Map tokensToProcess = new HashMap<>();

	private final Map lastTimestampCertChainDates = new HashMap<>();

	private final Map> poeTimes = new HashMap<>();
	
	/* The map contains all the certificate chains that has been used into the signature. Links the signing certificate and its chain. */
	private Map> orderedCertificateChains;

	// External OCSP source.
	private RevocationSource ocspSource;

	// External CRL source.
	private RevocationSource crlSource;

	// External trusted certificate sources
	private ListCertificateSource trustedCertSources;

	// External adjunct certificate sources
	private ListCertificateSource adjunctCertSources;

	// CRLs from the signature.
	private ListRevocationSource signatureCRLSource;

	// OCSP from the signature.
	private ListRevocationSource signatureOCSPSource;

	// Certificates from the signature.
	private ListCertificateSource signatureCertificateSource;
	
	// Certificates collected from AIA
	private ListCertificateSource aiaCertificateSources = new ListCertificateSource();

	// Certificates collected from revocation tokens
	private ListCertificateSource revocationCertificateSources = new ListCertificateSource();

	/**
	 * This variable set the behavior to follow for revocation retrieving in case of
	 * untrusted certificate chains.
	 */
	private boolean checkRevocationForUntrustedChains;

	/**
	 * 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.
	 */
	public SignatureValidationContext() {
	}

	/**
	 * @param certificateVerifier
	 *            The certificates verifier (eg: using the TSL as list of trusted certificates).
	 */
	@Override
	public void initialize(final CertificateVerifier certificateVerifier) {
		Objects.requireNonNull(certificateVerifier);

		this.certificateVerifier = certificateVerifier;
		this.crlSource = certificateVerifier.getCrlSource();
		this.ocspSource = certificateVerifier.getOcspSource();
		this.dataLoader = certificateVerifier.getDataLoader();
		this.signatureCRLSource = certificateVerifier.getSignatureCRLSource();
		this.signatureOCSPSource = certificateVerifier.getSignatureOCSPSource();
		this.signatureCertificateSource = certificateVerifier.getSignatureCertificateSource();
		this.adjunctCertSources = certificateVerifier.getAdjunctCertSources();
		this.trustedCertSources = certificateVerifier.getTrustedCertSources();
		this.checkRevocationForUntrustedChains = certificateVerifier.isCheckRevocationForUntrustedChains();
	}

	@Override
	public Date getCurrentTime() {
		return currentTime;
	}

	@Override
	public void setCurrentTime(final Date currentTime) {
		Objects.requireNonNull(currentTime);
		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 entry : tokensToProcess.entrySet()) {
				if (entry.getValue() == null) {
					entry.setValue(true);
					return entry.getKey();
				}
			}
			return null;
		}
	}

	/**
	 * This method returns a timestamp token to verify. If there is no more tokens to verify null is returned.
	 *
	 * @return token to verify or null
	 */
	private TimestampToken getNotYetVerifiedTimestamp() {
		synchronized (tokensToProcess) {
			for (final Entry entry : tokensToProcess.entrySet()) {
				if (entry.getValue() == null && entry.getKey() instanceof TimestampToken) {
					entry.setValue(true);
					return (TimestampToken) entry.getKey();
				}
			}
			return null;
		}
	}
	
	private Map> getOrderedCertificateChains() {
		if (orderedCertificateChains == null) {
			CertificateReorderer order = new CertificateReorderer(processedCertificates);
			orderedCertificateChains = order.getOrderedCertificateChains();
		}
		return orderedCertificateChains;
	}

	/**
	 * This method builds the complete certificate chain from the given token.
	 *
	 * @param token
	 *              the token for which the certificate chain must be obtained.
	 * @return the built certificate chain
	 */
	private List getCertChain(final Token token) {
		List chain = new LinkedList<>();
		Token issuerCertificateToken = token;
		do {
			chain.add(issuerCertificateToken);
			issuerCertificateToken = getIssuer(issuerCertificateToken);
		} while (issuerCertificateToken != null && !chain.contains(issuerCertificateToken));
		return chain;
	}

	private Token getIssuer(final Token token) {
		ListCertificateSource allCertificateSources = getAllCertificateSources();

		Set candidates = getIssuersFromSources(token, allCertificateSources);
		CertificateToken issuerCertificateToken = getTokenIssuerFromCandidates(token, candidates);

		if ((issuerCertificateToken == null) && (token instanceof CertificateToken) && dataLoader != null) {
			AIACertificateSource aiaSource = new AIACertificateSource((CertificateToken) token, dataLoader);
			aiaCertificateSources.add(aiaSource);
			issuerCertificateToken = aiaSource.getIssuerFromAIA();
		}
		
		if ((issuerCertificateToken == null) && (token instanceof OCSPToken)) {
			issuerCertificateToken = getOCSPIssuer((OCSPToken) token, allCertificateSources);
		}

		if ((issuerCertificateToken == null) && (token instanceof TimestampToken)) {
			issuerCertificateToken = getTSACertificate((TimestampToken) token, allCertificateSources);
		}

		if (issuerCertificateToken instanceof CertificateToken) {
			addCertificateTokenForVerification(issuerCertificateToken);
		}

		return issuerCertificateToken;
	}

	private ListCertificateSource getAllCertificateSources() {
		ListCertificateSource allCertificateSources = new ListCertificateSource();
		allCertificateSources.addAll(signatureCertificateSource);
		allCertificateSources.addAll(revocationCertificateSources);
		allCertificateSources.addAll(aiaCertificateSources);
		allCertificateSources.addAll(adjunctCertSources);
		allCertificateSources.addAll(trustedCertSources);
		return allCertificateSources;
	}

	private Set getIssuersFromSources(Token token, ListCertificateSource allCertificateSources) {
		if (token.getPublicKeyOfTheSigner() != null) {
			return allCertificateSources.getByPublicKey(token.getPublicKeyOfTheSigner());
		} else if (token.getIssuerX500Principal() != null) {
			return allCertificateSources.getBySubject(new X500PrincipalHelper(token.getIssuerX500Principal()));
		}
		return Collections.emptySet();
	}

	private CertificateToken getOCSPIssuer(OCSPToken token, ListCertificateSource allCertificateSources) {
		Set signingCertificateRefs = token.getCertificateSource().getAllCertificateRefs();
		if (Utils.collectionSize(signingCertificateRefs) == 1) {
			CertificateRef signingCertificateRef = signingCertificateRefs.iterator().next();
			ResponderId responderId = signingCertificateRef.getResponderId();
			if (responderId != null) {
				Set issuerCandidates = new HashSet<>();
				if (responderId.getSki() != null) {
					issuerCandidates.addAll(allCertificateSources.getBySki(responderId.getSki()));
				}
				if (responderId.getX500Principal() != null) {
					issuerCandidates.addAll(allCertificateSources.getBySubject(new X500PrincipalHelper(responderId.getX500Principal())));
				}
				return getTokenIssuerFromCandidates(token, issuerCandidates);
			}

		}
		LOG.warn("Signing certificate is not found for an OCSPToken with id '{}'.", token.getDSSIdAsString());
		return null;
	}

	private CertificateToken getTSACertificate(TimestampToken timestamp, ListCertificateSource allCertificateSources) {
		CandidatesForSigningCertificate candidatesForSigningCertificate = timestamp.getCandidatesForSigningCertificate();
		CertificateValidity theBestCandidate = candidatesForSigningCertificate.getTheBestCandidate();
		if (theBestCandidate != null) {
			Set issuerCandidates = new HashSet<>();
			CertificateToken timestampSigner = theBestCandidate.getCertificateToken();
			if (timestampSigner == null) {
				issuerCandidates.addAll(allCertificateSources.getByCertificateIdentifier(theBestCandidate.getSignerInfo()));
			} else {
				issuerCandidates.add(timestampSigner);
			}
			return getTokenIssuerFromCandidates(timestamp, issuerCandidates);
		}
		return null;
	}

	private CertificateToken getTokenIssuerFromCandidates(Token token, Collection candidates) {
		List issuers = new ArrayList<>();
		for (CertificateToken candidate : candidates) {
			if (token.isSignedBy(candidate)) {
				issuers.add(candidate);
				if (candidate.isValidOn(token.getCreationDate())) {
					return candidate;
				}
			}
		}
		if (Utils.isCollectionNotEmpty(issuers)) {
			LOG.warn("No issuer found for the token creation date. The process continues with an issuer which has the same public key.");
			return issuers.iterator().next();
		}
		return null;
	}

	/**
	 * Adds a new token to the list of tokens 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 = LOG.isTraceEnabled();
		if (traceEnabled) {
			LOG.trace("addTokenForVerification: trying to acquire synchronized block");
		}

		synchronized (tokensToProcess) {
			try {
				if (tokensToProcess.containsKey(token)) {
					if (traceEnabled) {
						LOG.trace("Token was already in the list {}:{}", token.getClass().getSimpleName(), token.getAbbreviation());
					}
					return false;
				}

				tokensToProcess.put(token, null);
				if (traceEnabled) {
					LOG.trace("+ New {} to check: {}", token.getClass().getSimpleName(), token.getAbbreviation());
				}
				return true;
			} finally {
				if (traceEnabled) {
					LOG.trace("addTokenForVerification: almost left synchronized block");
				}
			}
		}
	}

	@Override
	public void addRevocationTokenForVerification(RevocationToken revocationToken) {
		if (addTokenForVerification(revocationToken)) {

			// only certificate sources for OCSP tokens must be processed
			RevocationCertificateSource revocationCertificateSource = revocationToken.getCertificateSource();
			if (revocationCertificateSource != null) {
				revocationCertificateSources.add(revocationCertificateSource);
				for (CertificateToken certificateToken : revocationCertificateSource.getCertificates()) {
					addCertificateTokenForVerification(certificateToken);
				}
			}

			final boolean added = processedRevocations.add(revocationToken);
			if (LOG.isTraceEnabled()) {
				if (added) {
					LOG.trace("RevocationToken added to processedRevocations: {} ", revocationToken);
				} else {
					LOG.trace("RevocationToken already present processedRevocations: {} ", revocationToken);
				}
			}
		}
	}

	@Override
	public void addCertificateTokenForVerification(final CertificateToken certificateToken) {
		if (addTokenForVerification(certificateToken)) {
			final boolean added = processedCertificates.add(certificateToken);
			if (LOG.isTraceEnabled()) {
				if (added) {
					LOG.trace("CertificateToken added to processedCertificates: {} ", certificateToken);
				} else {
					LOG.trace("CertificateToken already present processedCertificates: {} ", certificateToken);
				}
			}
		}
	}

	@Override
	public void addTimestampTokenForVerification(final TimestampToken timestampToken) {
		if (addTokenForVerification(timestampToken)) {

			// Inject all certificate chain (needed in case of missing AIA on the TSA with
			// intermediate CAs)
			for (CertificateToken certificateToken : timestampToken.getCertificates()) {
				addCertificateTokenForVerification(certificateToken);
			}

			final boolean added = processedTimestamps.add(timestampToken);
			if (LOG.isTraceEnabled()) {
				if (added) {
					LOG.trace("TimestampToken added to processedTimestamps: {} ", processedTimestamps);
				} else {
					LOG.trace("TimestampToken already present processedTimestamps: {} ", processedTimestamps);
				}
			}
		}
	}

	private void registerUsageDate(TimestampToken timestampToken) {
		CertificateToken tsaCertificate = getTSACertificate(timestampToken, getAllCertificateSources());
		if (tsaCertificate == null) {
			LOG.warn("No Timestamp Certificate found. Chain is skipped.");
			return;
		}
		
		Map> certificateChains = getOrderedCertificateChains();
		List tsaCertificateChain = certificateChains.get(tsaCertificate);
		if (tsaCertificateChain == null) {
			tsaCertificateChain = toCertificateTokenChain(getCertChain(tsaCertificate));
			certificateChains.put(tsaCertificate, tsaCertificateChain);
		}
		Date usageDate = timestampToken.getCreationDate();
		for (CertificateToken cert : tsaCertificateChain) {
			if (isSelfSignedOrTrusted(cert)) {
				break;
			}
			Date lastUsage = lastTimestampCertChainDates.get(cert);
			if (lastUsage == null || lastUsage.before(usageDate)) {
				lastTimestampCertChainDates.put(cert, usageDate);
			}
		}
		for (TimestampedReference timestampedReference : timestampToken.getTimestampedReferences()) {
			List bestSignatureTimeList = poeTimes.get(timestampedReference.getObjectId());
			if (Utils.isCollectionEmpty(bestSignatureTimeList)) {
				bestSignatureTimeList = new ArrayList();
				poeTimes.put(timestampedReference.getObjectId(), bestSignatureTimeList);
			}
			bestSignatureTimeList.add(usageDate);
		}
	}
	
	private List toCertificateTokenChain(List tokens) {
		List chain = new LinkedList<>();
		for (Token token : tokens) {
			if (token instanceof CertificateToken) {
				chain.add((CertificateToken) token);
			}
		}
		return chain;
	}

	@Override
	public void validate() {
		TimestampToken timestampToken = getNotYetVerifiedTimestamp();
		while (timestampToken != null) {
			getCertChain(timestampToken);
			registerUsageDate(timestampToken);
			timestampToken = getNotYetVerifiedTimestamp();
			
		}
		
		Token token = getNotYetVerifiedToken();
		while (token != null) {
			// extract the certificate chain and add missing tokens for verification
			List certChain = getCertChain(token);
			if (token instanceof CertificateToken) {
				getRevocationData((CertificateToken) token, certChain);
			}
			token = getNotYetVerifiedToken();
			
		}
	}

	/**
	 * 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
	 *                  the current token
	 * @param certChain
	 *                  the complete chain
	 * @return
	 */
	private List getRevocationData(final CertificateToken certToken, List certChain) {

		if (LOG.isTraceEnabled()) {
			LOG.trace("Checking revocation data for : {}", certToken.getDSSIdAsString());
		}

		if (isRevocationDataNotRequired(certToken)) {
			LOG.debug("Revocation data is not required for certificate : {}", certToken.getDSSIdAsString());
			return Collections.emptyList();
		}

		CertificateToken issuerToken = (CertificateToken) getIssuer(certToken);
		if (issuerToken == null) {
			LOG.warn("Issuer not found for certificate {}", certToken.getDSSIdAsString());
			return Collections.emptyList();
		}

		List revocations = new ArrayList<>();

		// ALL Embedded revocation data
		if (signatureCRLSource != null) {
			List> revocationTokens = signatureCRLSource.getRevocationTokens(certToken, issuerToken);
			for (RevocationToken revocationToken : revocationTokens) {
				revocations.add(revocationToken);
				addRevocationTokenForVerification(revocationToken);
			}
		}

		if (signatureOCSPSource != null) {
			List> revocationTokens = signatureOCSPSource.getRevocationTokens(certToken, issuerToken);
			for (RevocationToken revocationToken : revocationTokens) {
				revocations.add(revocationToken);
				addRevocationTokenForVerification(revocationToken);
			}
		}
		
		if (Utils.isCollectionEmpty(revocations) || isRevocationDataRefreshNeeded(certToken, revocations)) {
			LOG.debug("The signature does not contain relative revocation data.");
			if (checkRevocationForUntrustedChains || containsTrustAnchor(certChain)) {
				LOG.trace("Revocation update is in progress for certificate : {}", certToken.getDSSIdAsString());
				CertificateToken trustAnchor = (CertificateToken) getFirstTrustAnchor(certChain);

				// Online resources (OCSP and CRL if OCSP doesn't reply)
				OCSPAndCRLRevocationSource onlineVerifier = null;
				if (!trustedCertSources.isEmpty() && (trustAnchor != null)) {
					LOG.trace("Initializing a revocation verifier for a trusted chain...");
					onlineVerifier = instantiateWithTrustServices(trustAnchor);
				} else {
					LOG.trace("Initializing a revocation verifier for not trusted chain...");
					onlineVerifier = new OCSPAndCRLRevocationSource(crlSource, ocspSource);
				}

				final RevocationToken onlineRevocationToken = onlineVerifier.getRevocationToken(certToken, issuerToken);
				// CRL can already exist in the signature
				if (onlineRevocationToken != null && !revocations.contains(onlineRevocationToken)) {
					LOG.debug("Obtained a new revocation data : {}, for certificate : {}", onlineRevocationToken.getDSSIdAsString(), certToken.getDSSIdAsString());
					revocations.add(onlineRevocationToken);
					addRevocationTokenForVerification(onlineRevocationToken);
				}
				
			} else {
				LOG.warn("External revocation check is skipped for untrusted certificate : {}", certToken.getDSSIdAsString());
			}
		}
		
		if (revocations.isEmpty()) {
			LOG.warn("No revocation found for the certificate {}", certToken.getDSSIdAsString());
		}

		return revocations;
	}

	private boolean containsTrustAnchor(List certChain) {
		return getFirstTrustAnchor(certChain) != null;
	}

	private Token getFirstTrustAnchor(List certChain) {
		for (Token token : certChain) {
			if (isTrusted(token)) {
				return token;
			}
		}
		return null;
	}
	
	@SuppressWarnings({ "rawtypes", "unchecked" })
	private OCSPAndCRLRevocationSource instantiateWithTrustServices(CertificateToken trustAnchor) {
		RevocationSource currentOCSPSource = null;
		List alternativeOCSPUrls = getAlternativeOCSPUrls(trustAnchor);
		if (Utils.isCollectionNotEmpty(alternativeOCSPUrls) && ocspSource instanceof RevocationSourceAlternateUrlsSupport) {
			currentOCSPSource = new AlternateUrlsSourceAdapter((RevocationSourceAlternateUrlsSupport) ocspSource, alternativeOCSPUrls);
		} else {
			currentOCSPSource = ocspSource;
		}

		RevocationSource currentCRLSource = null;
		List alternativeCRLUrls = getAlternativeCRLUrls(trustAnchor);
		if (Utils.isCollectionNotEmpty(alternativeCRLUrls) && crlSource instanceof RevocationSourceAlternateUrlsSupport) {
			currentCRLSource = new AlternateUrlsSourceAdapter((RevocationSourceAlternateUrlsSupport) crlSource, alternativeCRLUrls);
		} else {
			currentCRLSource = crlSource;
		}

		return new OCSPAndCRLRevocationSource(currentCRLSource, currentOCSPSource);
	}

	private List getAlternativeOCSPUrls(CertificateToken trustAnchor) {
		List alternativeOCSPUrls = new ArrayList<>();
		for (CertificateSource certificateSource : trustedCertSources.getSources()) {
			if (certificateSource instanceof CommonTrustedCertificateSource) {
				CommonTrustedCertificateSource trustedCertSource = (CommonTrustedCertificateSource) certificateSource;
				alternativeOCSPUrls.addAll(trustedCertSource.getAlternativeOCSPUrls(trustAnchor));
			}
		}
		return alternativeOCSPUrls;
	}

	private List getAlternativeCRLUrls(CertificateToken trustAnchor) {
		List alternativeCRLUrls = new ArrayList<>();
		for (CertificateSource certificateSource : trustedCertSources.getSources()) {
			if (certificateSource instanceof CommonTrustedCertificateSource) {
				CommonTrustedCertificateSource trustedCertSource = (CommonTrustedCertificateSource) certificateSource;
				alternativeCRLUrls.addAll(trustedCertSource.getAlternativeCRLUrls(trustAnchor));
			}
		}
		return alternativeCRLUrls;
	}

	@Override
	public boolean checkAllRequiredRevocationDataPresent() {
		List errors = new ArrayList<>();
		Map> orderedCertificateChains = getOrderedCertificateChains();
		for (List orderedCertChain : orderedCertificateChains.values()) {
			checkRevocationForCertificateChainAgainstBestSignatureTime(orderedCertChain, null, errors);
		}
		if (!errors.isEmpty()) {
			Status status = new Status("Revocation data is missing for one or more certificate(s).", errors);
			certificateVerifier.getAlertOnMissingRevocationData().alert(status);
		}
		return errors.isEmpty();
	}
	
	private void checkRevocationForCertificateChainAgainstBestSignatureTime(List certificates, Date bestSignatureTime, List errors) {
		for (CertificateToken certificateToken : certificates) {
			if (isSelfSignedOrTrusted(certificateToken)) {
				// break on the first trusted entry
				break;
			} else if (isOCSPNoCheckExtension(certificateToken)) {
				// skip the revocation check for OCSP certs if no check is specified
				continue;
			}
			
			boolean found = false;
			Date earliestNextUpdate = null; // used for informational purpose only
			for (RevocationToken revocationToken : processedRevocations) {
				
				if (Utils.areStringsEqual(certificateToken.getDSSIdAsString(), revocationToken.getRelatedCertificateID())) {
					if (bestSignatureTime == null || revocationToken.getThisUpdate().after(bestSignatureTime)) {
						found = true;
						break;
						
					} else {
						if (revocationToken.getNextUpdate() != null && 
								(earliestNextUpdate == null || revocationToken.getNextUpdate().before(earliestNextUpdate))) {
							earliestNextUpdate = revocationToken.getNextUpdate();
						}
						
					}
				}
			}
			
			if (!found) {
				if (bestSignatureTime == null) {
					// simple revocation presence check
					errors.add(String.format("No revocation data found for certificate : %s", certificateToken.getDSSIdAsString()));
				} else if (earliestNextUpdate != null) {
					errors.add(String.format(
							"No revocation data found after the best signature time [%s] "
							+ "for the certificate : %s. \n The nextUpdate available after : [%s]",
							bestSignatureTime, certificateToken.getDSSIdAsString(), earliestNextUpdate));
				} else {
					errors.add(String.format("No revocation data found after the best signature time [%s] for the certificate : %s", bestSignatureTime,
							certificateToken.getDSSIdAsString()));
				}
			}
		}
	}

	@Override
	public boolean checkAllPOECoveredByRevocationData() {
		List errors = new ArrayList<>();
		for (Entry entry : lastTimestampCertChainDates.entrySet()) {
			Date lastUsage = entry.getValue();
			CertificateToken token = entry.getKey();
			if (!isRevocationDataNotRequired(token)) {
				boolean foundValidRevocationDataAfterLastUsage = false;
				Date nextUpdate = null;
				for (RevocationToken revocationToken : processedRevocations) {
					if (Utils.areStringsEqual(token.getDSSIdAsString(), revocationToken.getRelatedCertificateID())) {
						Date productionDate = revocationToken.getProductionDate();
						if (productionDate.after(lastUsage)) {
							foundValidRevocationDataAfterLastUsage = true;
							break;
						}

						Date currentNextUpdate = revocationToken.getNextUpdate();
						if (nextUpdate == null || (currentNextUpdate != null && nextUpdate.before(currentNextUpdate))) {
							nextUpdate = currentNextUpdate;
						}
					}
				}
				if (!foundValidRevocationDataAfterLastUsage) {
					errors.add(String.format("POE '%s' not covered by a valid revocation data (nextUpdate : %s)", token.getDSSIdAsString(), nextUpdate));
				}
			}
		}
		if (!errors.isEmpty()) {
			Status status = new Status("Revocation data is missing for one or more POE(s).", errors);
			certificateVerifier.getAlertOnUncoveredPOE().alert(status);
		}
		return errors.isEmpty();
	}

	@Override
	public boolean checkAllTimestampsValid() {
		Set invalidTimestampIds = new HashSet<>();
		for (TimestampToken timestampToken : processedTimestamps) {
			if (!timestampToken.isSignatureValid() || !timestampToken.isMessageImprintDataFound() || !timestampToken.isMessageImprintDataIntact()) {
				invalidTimestampIds.add(timestampToken.getDSSIdAsString());
			}
		}
		if (!invalidTimestampIds.isEmpty()) {
			Status status = new Status("Broken timestamp(s) detected.", invalidTimestampIds);
			certificateVerifier.getAlertOnInvalidTimestamp().alert(status);
		}
		return invalidTimestampIds.isEmpty();
	}

	@Override
	public boolean checkAllCertificatesValid() {
		Set invalidCertificateIds = new HashSet<>();
		for (CertificateToken certificateToken : processedCertificates) {
			if (!isRevocationDataNotRequired(certificateToken)) {
				for (RevocationToken revocationToken : processedRevocations) {
					if (Utils.areStringsEqual(certificateToken.getDSSIdAsString(), revocationToken.getRelatedCertificateID())
							&& !revocationToken.getStatus().isGood()) {
						invalidCertificateIds.add(certificateToken.getDSSIdAsString());
					}
				}
			}
		}
		if (!invalidCertificateIds.isEmpty()) {
			Status status = new Status("Revoked/Suspended certificate(s) detected.", invalidCertificateIds);
			certificateVerifier.getAlertOnRevokedCertificate().alert(status);
		}
		return invalidCertificateIds.isEmpty();
	}

	private boolean isRevocationDataNotRequired(CertificateToken certToken) {
		return isSelfSignedOrTrusted(certToken) || isOCSPNoCheckExtension(certToken);
	}
	
	private boolean isSelfSignedOrTrusted(CertificateToken certToken) {
		return certToken.isSelfSigned() || isTrusted(certToken);
	}
	
	private boolean isOCSPNoCheckExtension(CertificateToken certToken) {
		return DSSASN1Utils.hasIdPkixOcspNoCheckExtension(certToken);
	}

	private boolean isRevocationDataRefreshNeeded(CertificateToken certToken, List revocations) {
		Date refreshNeededAfterTime = lastTimestampCertChainDates.get(certToken); // get last usage dates for the same timestamp certificate chain
		if (refreshNeededAfterTime == null) {
			refreshNeededAfterTime = getLowestPOETime(certToken.getDSSIdAsString()); // the best signature time for other tokens (i.e. B-level and revocation data)
		}
		if (refreshNeededAfterTime != null) {
			boolean freshRevocationDataFound = false;
			for (RevocationToken revocationToken : revocations) {
				if (refreshNeededAfterTime.before(revocationToken.getProductionDate()) && (RevocationReason.CERTIFICATE_HOLD != revocationToken.getReason() &&
						isConsistent(revocationToken))) {
					freshRevocationDataFound = true;
					break;
				}
			}
			if (!freshRevocationDataFound) {
				LOG.debug("Revocation data refresh is needed");
				return true;
			}
		}
		return false;
	}
	
	private Date getLowestPOETime(String tokenId) {
		Date lowestPOE = null;
		List bestSignatureTimeList = poeTimes.get(tokenId);
		if (Utils.isCollectionNotEmpty(bestSignatureTimeList)) {
			for (Date poeTime : bestSignatureTimeList) {
				if (lowestPOE == null || poeTime.before(lowestPOE)) {
					lowestPOE = poeTime;
				}
			}
		}
		return lowestPOE;
	}
	
	private boolean isConsistent(RevocationToken revocation) {
		List certificateTokenChain = toCertificateTokenChain(getCertChain(revocation));
		if (Utils.isCollectionEmpty(certificateTokenChain)) {
			LOG.debug("The revocation {} is not consistent! Issuer CertificateToken is not found.", revocation.getDSSIdAsString());
			return false;
		}
		
		if (revocation.getNextUpdate() != null) {
			return hasPOEAfterProductionAndBeforeNextUpdate(revocation);
		} else {
			// if the next update time is not defined, check the validity of the issuer's certificate
			// useful for short-life certificates (i.e. ocsp responser)
			return hasPOEInTheValidityRange(certificateTokenChain.iterator().next());
		}
	}
	
	private boolean hasPOEAfterProductionAndBeforeNextUpdate(RevocationToken revocation) {
		if (isConsistentOnTime(revocation, currentTime)) {
			return true;
		}
		List poeTimeList = poeTimes.get(revocation.getDSSIdAsString());
		if (Utils.isCollectionNotEmpty(poeTimeList)) {
			for (Date poeTime : poeTimeList) {
				if (isConsistentOnTime(revocation, poeTime)) {
					return true;
				}
			}
		}
		return false;
	}
	
	private boolean hasPOEInTheValidityRange(CertificateToken certificateToken) {
		// the certificate is valid in the current time
		if (certificateToken.isValidOn(currentTime)) {
			return true;
		}
		List poeTimeList = poeTimes.get(certificateToken.getDSSIdAsString());
		if (Utils.isCollectionNotEmpty(poeTimeList)) {
			for (Date poeTime : poeTimeList) {
				if (certificateToken.isValidOn(poeTime)) {
					return true;
				}
				// continue
			}
		}
		return false;
	}
	
	private boolean isConsistentOnTime(RevocationToken revocationToken, Date date) {
		Date productionDate = revocationToken.getProductionDate();
		Date nextUpdate = revocationToken.getNextUpdate();
		return date.compareTo(productionDate) >= 0 && date.compareTo(nextUpdate) <= 0;
	}

	@Override
	public boolean checkAtLeastOneRevocationDataPresentAfterBestSignatureTime(CertificateToken signingCertificate) {
		List errors = new ArrayList<>();
		Map> orderedCertificateChains = getOrderedCertificateChains();
		for (Map.Entry> entry : orderedCertificateChains.entrySet()) {
			CertificateToken firstChainCertificate = entry.getKey();
			Date bestSignatureTime = firstChainCertificate.equals(signingCertificate) ? getEarliestTimestampTime()
					: lastTimestampCertChainDates.get(firstChainCertificate);
			checkRevocationForCertificateChainAgainstBestSignatureTime(entry.getValue(), bestSignatureTime, errors);
		}
		if (!errors.isEmpty()) {
			Status status = new Status("Fresh revocation data is missing for one or more certificate(s).", errors);
			certificateVerifier.getAlertOnNoRevocationAfterBestSignatureTime().alert(status);
		}
		return errors.isEmpty();
	}
	
	private Date getEarliestTimestampTime() {
		Date earliestDate = null;
		for (TimestampToken timestamp : getProcessedTimestamps()) {
			if (timestamp.getTimeStampType().coversSignature()) {
				Date timestampTime = timestamp.getCreationDate();
				if (earliestDate == null || timestampTime.before(earliestDate)) {
					earliestDate = timestampTime;
				}
			}
		}
		return earliestDate;
	}

	@Override
	public Set getProcessedCertificates() {
		return Collections.unmodifiableSet(processedCertificates);
	}

	@Override
	public Map> getCertificateSourceTypes() {
		ListCertificateSource allCertificateSources = getAllCertificateSources();
		Map> result = new HashMap<>();
		for (CertificateToken certificateToken : getProcessedCertificates()) {
			result.put(certificateToken, allCertificateSources.getCertificateSource(certificateToken));
		}
		return result;
	}

	@Override
	public Set> getProcessedRevocations() {
		return Collections.unmodifiableSet(processedRevocations);
	}

	@Override
	public Set getProcessedTimestamps() {
		return Collections.unmodifiableSet(processedTimestamps);
	}

	private boolean isTrusted(Token token) {
		return token instanceof CertificateToken && trustedCertSources.isTrusted((CertificateToken) token);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy