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

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

/**
 * 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.spi.validation; import eu.europa.esig.dss.enumerations.Context; import eu.europa.esig.dss.enumerations.RevocationReason; import eu.europa.esig.dss.model.identifier.EntityIdentifier; 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.crl.CRL; import eu.europa.esig.dss.model.x509.revocation.ocsp.OCSP; import eu.europa.esig.dss.spi.DSSUtils; import eu.europa.esig.dss.spi.signature.AdvancedSignature; import eu.europa.esig.dss.spi.validation.status.RevocationFreshnessStatus; import eu.europa.esig.dss.spi.validation.status.SignatureStatus; import eu.europa.esig.dss.spi.validation.status.TokenStatus; 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.CertificateReorderer; import eu.europa.esig.dss.spi.x509.CertificateSource; import eu.europa.esig.dss.spi.x509.CertificateValidity; import eu.europa.esig.dss.spi.x509.ListCertificateSource; import eu.europa.esig.dss.spi.x509.ResponderId; import eu.europa.esig.dss.spi.x509.TokenIssuerSelector; import eu.europa.esig.dss.spi.x509.TrustedCertificateSource; import eu.europa.esig.dss.spi.x509.aia.AIACertificateSource; import eu.europa.esig.dss.spi.x509.aia.AIASource; import eu.europa.esig.dss.spi.x509.evidencerecord.EvidenceRecord; import eu.europa.esig.dss.spi.x509.revocation.ListRevocationSource; import eu.europa.esig.dss.spi.x509.revocation.OfflineRevocationSource; 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.crl.CRLToken; import eu.europa.esig.dss.spi.x509.revocation.ocsp.OCSPToken; import eu.europa.esig.dss.spi.x509.tsp.TimestampToken; import eu.europa.esig.dss.spi.x509.tsp.TimestampTokenComparator; import eu.europa.esig.dss.spi.x509.tsp.TimestampedReference; import eu.europa.esig.dss.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; 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.Iterator; import java.util.LinkedHashSet; 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; /** * 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); /** * A set of signatures to process */ private final Set processedSignatures = new LinkedHashSet<>(); /** * A set of certificates to process */ private final Set processedCertificates = new LinkedHashSet<>(); /** * A set of revocation data to process */ private final Set> processedRevocations = new LinkedHashSet<>(); /** * A set of timestamps to process */ private final Set processedTimestamps = new LinkedHashSet<>(); /** * A set of evidence records to process */ private final Set processedEvidenceRecords = new LinkedHashSet<>(); /** * The CertificateVerifier to use */ private CertificateVerifier certificateVerifier; /** Map of tokens defining if they have been processed yet */ private final Map tokensToProcess = new HashMap<>(); /** A map between certificate tokens and corresponding signatures (b-level creation) */ private final Map> certificateSignaturesUsage = new HashMap<>(); /** The usages of a timestamp's certificate tokens */ private final Map> timestampCertChainDates = new HashMap<>(); /** A map of token IDs and their corresponding POE times */ private final Map> poeTimes = new HashMap<>(); /** Cached map of tokens and their {@code CertificateToken} issuers */ private final Map tokenIssuerMap = new HashMap<>(); /** Cached map of parent {@code CertificateToken}'s and their corresponding issued certificates */ private final Map> certificateChildrenMap = new HashMap<>(); /** Certificates from the document */ private final ListCertificateSource documentCertificateSource = new ListCertificateSource(); /** CRLs from the document */ private final ListRevocationSource documentCRLSource = new ListRevocationSource<>(); /** OCSP from the document */ private final ListRevocationSource documentOCSPSource = new ListRevocationSource<>(); /** Certificates collected from AIA */ private final ListCertificateSource aiaCertificateSources = new ListCertificateSource(); /** Certificates collected from revocation tokens */ private final ListCertificateSource revocationCertificateSources = new ListCertificateSource(); /** Used to access certificate by AIA */ private AIASource aiaSource; /** External OCSP source */ private RevocationSource remoteOCSPSource; /** External CRL source */ private RevocationSource remoteCRLSource; /** Used to build a strategy deciding how to retrieve a revocation data (e.g. CRL or OCSP) */ private RevocationDataLoadingStrategyFactory revocationDataLoadingStrategyFactory; /** This class is used to verify the validity (i.e. consistency) of a revocation data */ private RevocationDataVerifier revocationDataVerifier; /** Defines whether a revocation data still shall be returned, when validation of obtained revocation tokens failed */ private boolean revocationFallback; /** This class is used to verify validity of a {@code TimestampToken} */ private TimestampTokenVerifier timestampTokenVerifier; /** This class is used to verify whether a certificate is a trust anchor */ private TrustAnchorVerifier trustAnchorVerifier; /** External trusted certificate sources */ private ListCertificateSource trustedCertSources; /** External adjunct certificate sources */ private ListCertificateSource adjunctCertSources; /** * 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. */ protected Date currentTime; /** * Default constructor instantiating object with null or empty values and current time */ public SignatureValidationContext() { this(new Date()); } /** * Constructor instantiating object with null or empty values and provided time * * @param validationTime {@link Date} validation time to be used during the execution */ public SignatureValidationContext(Date validationTime) { this.currentTime = validationTime; } /** * @param certificateVerifier * The certificate verifier (eg: using the TSL as list of trusted certificates). */ @Override public void initialize(final CertificateVerifier certificateVerifier) { Objects.requireNonNull(certificateVerifier, "CertificateVerifier cannot be null!"); this.certificateVerifier = certificateVerifier; this.remoteCRLSource = certificateVerifier.getCrlSource(); this.remoteOCSPSource = certificateVerifier.getOcspSource(); this.aiaSource = certificateVerifier.getAIASource(); this.adjunctCertSources = certificateVerifier.getAdjunctCertSources(); this.trustedCertSources = certificateVerifier.getTrustedCertSources(); this.checkRevocationForUntrustedChains = certificateVerifier.isCheckRevocationForUntrustedChains(); this.revocationDataLoadingStrategyFactory = certificateVerifier.getRevocationDataLoadingStrategyFactory(); this.revocationDataVerifier = certificateVerifier.getRevocationDataVerifier(); this.revocationFallback = certificateVerifier.isRevocationFallback(); this.timestampTokenVerifier = certificateVerifier.getTimestampTokenVerifier(); this.trustAnchorVerifier = certificateVerifier.getTrustAnchorVerifier(); } /** * Gets the {@code CertificateVerifier} instance * * @return {@link CertificateVerifier} */ protected CertificateVerifier getCertificateVerifier() { Objects.requireNonNull(certificateVerifier, "CertificateVerifier shall be initialized! Please use #initialize(CertificateVerifier) method."); return certificateVerifier; } /** * Returns an instance of {@code RevocationDataVerifier}. * Instantiates a default configuration from a default validation policy, if not defined. * * @return {@link RevocationDataVerifier} */ private RevocationDataVerifier getRevocationDataVerifier() { if (revocationDataVerifier == null) { revocationDataVerifier = RevocationDataVerifier.createDefaultRevocationDataVerifier(); } if (revocationDataVerifier.getTrustAnchorVerifier() == null) { revocationDataVerifier.setTrustAnchorVerifier(getTrustAnchorVerifier()); } if (revocationDataVerifier.getProcessedRevocations() == null) { revocationDataVerifier.setProcessedRevocations(processedRevocations); } return revocationDataVerifier; } private TimestampTokenVerifier getTimestampTokenVerifier() { if (timestampTokenVerifier == null) { timestampTokenVerifier = TimestampTokenVerifier.createDefaultTimestampTokenVerifier(); } if (timestampTokenVerifier.getTrustAnchorVerifier() == null) { timestampTokenVerifier.setTrustAnchorVerifier(getTrustAnchorVerifier()); } if (timestampTokenVerifier.getRevocationDataVerifier() == null) { timestampTokenVerifier.setRevocationDataVerifier(getRevocationDataVerifier()); } return timestampTokenVerifier; } private TrustAnchorVerifier getTrustAnchorVerifier() { if (trustAnchorVerifier == null) { trustAnchorVerifier = TrustAnchorVerifier.createDefaultTrustAnchorVerifier(); } if (trustAnchorVerifier.getTrustedCertificateSource() == null) { trustAnchorVerifier.setTrustedCertificateSource(trustedCertSources); } return trustAnchorVerifier; } @Override public void addSignatureForVerification(final AdvancedSignature signature) { if (signature == null) { return; } addDocumentCertificateSource(signature.getCertificateSource()); addDocumentCRLSource(signature.getCRLSource()); addDocumentOCSPSource(signature.getOCSPSource()); registerPOE(signature.getId(), currentTime); // Add resolved certificates CertificateToken signingCertificate = signature.getSigningCertificateToken(); if (signingCertificate != null) { addCertificateTokenForVerification(signingCertificate); } else { List certificateValidities = signature.getCandidatesForSigningCertificate().getCertificateValidityList(); if (Utils.isCollectionNotEmpty(certificateValidities)) { for (CertificateValidity certificateValidity : certificateValidities) { if (certificateValidity.isValid() && certificateValidity.getCertificateToken() != null) { addCertificateTokenForVerification(certificateValidity.getCertificateToken()); } } } } List timestamps = signature.getAllTimestamps(); prepareTimestamps(timestamps); List allEvidenceRecords = signature.getAllEvidenceRecords(); prepareEvidenceRecords(allEvidenceRecords); registerCertChainUsage(signature); // to be done after timestamp POE extraction final boolean added = processedSignatures.add(signature); if (LOG.isTraceEnabled()) { if (added) { LOG.trace("AdvancedSignature added to processedSignatures: {} ", processedSignatures); } else { LOG.trace("AdvancedSignature already present processedSignatures: {} ", processedSignatures); } } List counterSignatures = signature.getCounterSignatures(); prepareCounterSignatures(counterSignatures); } @Override public void addDocumentCertificateSource(CertificateSource certificateSource) { addCertificateSource(documentCertificateSource, certificateSource); } @Override public void addDocumentCertificateSource(ListCertificateSource listCertificateSource) { for (CertificateSource certificateSource : listCertificateSource.getSources()) { addDocumentCertificateSource(certificateSource); } } /** * Adds {@code certificateSourceToAdd} to the given {@code listCertificateSource} * * @param listCertificateSource {@link ListCertificateSource} to enrich * @param certificateSourceToAdd {@link CertificateSource} to add */ private void addCertificateSource(ListCertificateSource listCertificateSource, CertificateSource certificateSourceToAdd) { if (listCertificateSource.add(certificateSourceToAdd)) { // add all existing equivalent certificates for the validation ListCertificateSource allCertificateSources = getAllCertificateSources(); for (CertificateToken certificateToken : certificateSourceToAdd.getCertificates()) { addEquivalentCertificates(certificateToken, allCertificateSources); } } } private void addEquivalentCertificates(CertificateToken certificateToken, CertificateSource certificateSource) { final Set equivalentCertificates = certificateSource.getByEntityKey(certificateToken.getEntityKey()); for (CertificateToken equivalentCertificate : equivalentCertificates) { if (!certificateToken.getDSSIdAsString().equals(equivalentCertificate.getDSSIdAsString())) { addCertificateTokenForVerification(equivalentCertificate); } } } @Override public void addDocumentCRLSource(OfflineRevocationSource crlSource) { documentCRLSource.add(crlSource); } @Override public void addDocumentCRLSource(ListRevocationSource crlSource) { documentCRLSource.addAll(crlSource); } @Override public void addDocumentOCSPSource(OfflineRevocationSource ocspSource) { documentOCSPSource.add(ocspSource); } @Override public void addDocumentOCSPSource(ListRevocationSource ocspSource) { documentOCSPSource.addAll(ocspSource); } private void prepareTimestamps(final List timestampTokens) { if (Utils.isCollectionNotEmpty(timestampTokens)) { for (final TimestampToken timestampToken : timestampTokens) { addTimestampTokenForVerification(timestampToken); } } } private void prepareEvidenceRecords(final List evidenceRecords) { if (Utils.isCollectionNotEmpty(evidenceRecords)) { for (EvidenceRecord evidenceRecord : evidenceRecords) { addEvidenceRecordForVerification(evidenceRecord); } } } private void registerCertChainUsage(AdvancedSignature signature) { CertificateToken signingCertificate = signature.getSigningCertificateToken(); if (signingCertificate != null) { List certificateChain = toCertificateTokenChain(getCertChain(signingCertificate)); for (CertificateToken cert : certificateChain) { List certUsageSignatures = certificateSignaturesUsage.computeIfAbsent(cert, k -> new ArrayList<>()); if (!certUsageSignatures.contains(signature)) { certUsageSignatures.add(signature); } } } } private void prepareCounterSignatures(final List counterSignatures) { if (Utils.isCollectionNotEmpty(counterSignatures)) { for (AdvancedSignature counterSignature : counterSignatures) { addSignatureForVerification(counterSignature); } } } @Override public Date getCurrentTime() { return 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) { RevocationToken revocationToken = getNotYetVerifiedRevocationToken(); if (revocationToken != null) { return revocationToken; } for (final Entry entry : tokensToProcess.entrySet()) { if (entry.getValue() == null) { entry.setValue(true); return entry.getKey(); } } return null; } } /** * This method returns a revocation token to verify. If there is no more tokens to verify null is returned. * As revocation tokens may be added dynamically, the method is executed as part of {@code getNotYetVerifiedToken}. * It is called with a priority, in order to be able to identify a correct certificate chain. * * @return token to verify or null */ private RevocationToken getNotYetVerifiedRevocationToken() { if (Utils.isCollectionEmpty(processedRevocations)) { return null; } final List> revocationTokens = new ArrayList<>(processedRevocations); for (RevocationToken revocationToken : revocationTokens) { Boolean processed = tokensToProcess.get(revocationToken); if (!Boolean.TRUE.equals(processed)) { tokensToProcess.put(revocationToken, true); return revocationToken; } } 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) { if (Utils.isCollectionEmpty(processedTimestamps)) { return null; } final List sortedTimestampTokens = new ArrayList<>(processedTimestamps); sortedTimestampTokens.sort(new TimestampTokenComparator()); Collections.reverse(sortedTimestampTokens); // start processing from the freshest timestamp for (TimestampToken timestampToken : sortedTimestampTokens) { Boolean processed = tokensToProcess.get(timestampToken); if (!Boolean.TRUE.equals(processed)) { tokensToProcess.put(timestampToken, true); return timestampToken; } } return null; } } private Token getNotYetVerifiedTokenFromChain(List certChain) { synchronized (tokensToProcess) { for (Token token : certChain) { Boolean processed = tokensToProcess.get(token); if (!Boolean.TRUE.equals(processed)) { tokensToProcess.put(token, true); return token; } } return null; } } private Map> getOrderedCertificateChains() { final CertificateReorderer order = new CertificateReorderer(processedCertificates); return order.getOrderedCertificateChains(); } /** * 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) { final List chain = new LinkedList<>(); Token issuerCertificateToken = token; do { chain.add(issuerCertificateToken); issuerCertificateToken = getIssuer(issuerCertificateToken, getTokenCertificateSource(token)); } while (issuerCertificateToken != null && !chain.contains(issuerCertificateToken)); return chain; } private CertificateSource getTokenCertificateSource(final Token token) { if (token instanceof OCSPToken) { return ((OCSPToken) token).getCertificateSource(); } else if (token instanceof TimestampToken) { return ((TimestampToken) token).getCertificateSource(); } // other tokens do not have its own source return null; } /** * This method computes certificate chain for the given {@code token} without including the current {@code token} * to the chain, when it is not instance of {@code CertificateToken} * * @param token {@link Token} * @return a list of {@link CertificateToken}s */ private List getCertificateTokenChain(final Token token) { final List certificateChain = new LinkedList<>(); for (Token chainItem : getCertChain(token)) { if (chainItem instanceof CertificateToken) { certificateChain.add((CertificateToken) chainItem); } } return certificateChain; } private CertificateToken getIssuer(final Token token) { return getIssuer(token, null); } private CertificateToken getIssuer(final Token token, CertificateSource certificateSource) { // Return cached value CertificateToken issuerCertificateToken = getIssuerFromProcessedCertificates(token); if (issuerCertificateToken != null) { // ensure equivalent certificates are processed if (certificateSource != null) { addEquivalentCertificates(issuerCertificateToken, certificateSource); } return issuerCertificateToken; } // Find issuer candidates from a particular certificate source Set candidates = Collections.emptySet(); // Avoid repeating over stateless sources if (!tokenIssuerMap.containsKey(token)) { if (certificateSource == null) { // OCSP or Timestamp certificateSource = getTokenCertificateSource(token); } if (certificateSource != null) { candidates = getIssuersFromSource(token, certificateSource); } // Find issuer candidates from document sources if (Utils.isCollectionEmpty(candidates)) { candidates = getIssuersFromSources(token, documentCertificateSource); } } // Find issuer candidates from all sources ListCertificateSource allCertificateSources = getAllCertificateSources(); if (Utils.isCollectionEmpty(candidates)) { candidates = getIssuersFromSources(token, allCertificateSources); } // Find issuer from provided certificate tokens if (Utils.isCollectionEmpty(candidates)) { candidates = new HashSet<>(); candidates.addAll(processedCertificates); candidates.addAll(documentCertificateSource.getCertificates()); } candidates = ensureCandidatesFromProcessedCertificates(candidates); issuerCertificateToken = new TokenIssuerSelector(token, candidates).getIssuer(); // Request AIA only when no issuer has been found yet if (issuerCertificateToken == null && aiaSource != null && token instanceof CertificateToken && !tokenIssuerMap.containsKey(token)) { final AIACertificateSource aiaCertificateSource = new AIACertificateSource((CertificateToken) token, aiaSource); issuerCertificateToken = aiaCertificateSource.getIssuerFromAIA(); addCertificateSource(aiaCertificateSources, aiaCertificateSource); } if (issuerCertificateToken == null && token instanceof OCSPToken) { issuerCertificateToken = getOCSPIssuer((OCSPToken) token, allCertificateSources); } if (issuerCertificateToken == null && token instanceof TimestampToken) { issuerCertificateToken = getTSACertificate((TimestampToken) token, allCertificateSources); } if (issuerCertificateToken != null) { addCertificateTokenForVerification(issuerCertificateToken); } // Cache the result (successful or unsuccessful) addToCacheMap(token, issuerCertificateToken); return issuerCertificateToken; } /** * This method is used to ensure the processed certificates are used on validation, in order to avoid cases * when another certificate is being updated (not provided to the validation) * * @param candidates a set of issuer candidate {@link CertificateToken}s * @return a set of {@link CertificateToken}s */ private Set ensureCandidatesFromProcessedCertificates(Set candidates) { if (Utils.isCollectionEmpty(candidates)) { return Collections.emptySet(); } final Set result = new HashSet<>(); for (CertificateToken certificateToken : candidates) { for (CertificateToken processedCertificate : processedCertificates) { if (certificateToken.equals(processedCertificate)) { certificateToken = processedCertificate; break; } } result.add(certificateToken); } return result; } private void addToCacheMap(Token token, CertificateToken issuerCertificateToken) { tokenIssuerMap.put(token, issuerCertificateToken); if (token instanceof CertificateToken) { Set childrenCertificates = certificateChildrenMap.get(issuerCertificateToken); if (Utils.isCollectionEmpty(childrenCertificates)) { childrenCertificates = new HashSet<>(); certificateChildrenMap.put(issuerCertificateToken, childrenCertificates); } childrenCertificates.add((CertificateToken) token); } } private CertificateToken getIssuerFromProcessedCertificates(Token token) { CertificateToken issuerCertificateToken = tokenIssuerMap.get(token); // isSignedBy(...) check is required when a certificate is present in different sources // in order to instantiate a public key of the signer if (issuerCertificateToken != null && (token.getPublicKeyOfTheSigner() != null || token.isSignedBy(issuerCertificateToken))) { return issuerCertificateToken; } return null; } @Override public ListCertificateSource getAllCertificateSources() { ListCertificateSource allCertificateSources = new ListCertificateSource(); allCertificateSources.addAll(documentCertificateSource); allCertificateSources.addAll(revocationCertificateSources); allCertificateSources.addAll(aiaCertificateSources); allCertificateSources.addAll(adjunctCertSources); allCertificateSources.addAll(trustedCertSources); return allCertificateSources; } @Override public ListCertificateSource getDocumentCertificateSource() { return documentCertificateSource; } @Override public ListRevocationSource getDocumentCRLSource() { return documentCRLSource; } @Override public ListRevocationSource getDocumentOCSPSource() { return documentOCSPSource; } private Set getIssuersFromSources(Token token, ListCertificateSource allCertificateSources) { if (token.getIssuerEntityKey() != null) { EntityIdentifier entityKey = token.getIssuerEntityKey(); return allCertificateSources.getByEntityKey(entityKey); } else 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 Set getIssuersFromSource(Token token, CertificateSource certificateSource) { if (token.getIssuerEntityKey() != null) { EntityIdentifier entityKey = token.getIssuerEntityKey(); return certificateSource.getByEntityKey(entityKey); } else if (token.getPublicKeyOfTheSigner() != null) { return certificateSource.getByPublicKey(token.getPublicKeyOfTheSigner()); } else if (token.getIssuerX500Principal() != null) { return certificateSource.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 new TokenIssuerSelector(token, issuerCandidates).getIssuer(); } } 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.getBySignerIdentifier(theBestCandidate.getSignerInfo())); } else { issuerCandidates.add(timestampSigner); } return new TokenIssuerSelector(timestamp, issuerCandidates).getIssuer(); } 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); registerPOE(token.getDSSIdAsString(), currentTime); 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)) { RevocationCertificateSource revocationCertificateSource = revocationToken.getCertificateSource(); if (revocationCertificateSource != null) { addCertificateSource(revocationCertificateSources, revocationCertificateSource); } CertificateToken issuerCertificateToken = revocationToken.getIssuerCertificateToken(); if (issuerCertificateToken != null) { addCertificateTokenForVerification(issuerCertificateToken); } 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)) { addDocumentCertificateSource(timestampToken.getCertificateSource()); addDocumentCRLSource(timestampToken.getCRLSource()); addDocumentOCSPSource(timestampToken.getOCSPSource()); List certificateValidities = timestampToken.getCandidatesForSigningCertificate().getCertificateValidityList(); if (Utils.isCollectionNotEmpty(certificateValidities)) { for (CertificateValidity certificateValidity : certificateValidities) { if (certificateValidity.isValid() && certificateValidity.getCertificateToken() != null) { addCertificateTokenForVerification(certificateValidity.getCertificateToken()); } } } registerTimestampUsageDate(timestampToken); 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 registerTimestampUsageDate(TimestampToken timestampToken) { CertificateToken tsaCertificate = getTSACertificate(timestampToken, getAllCertificateSources()); if (tsaCertificate == null) { LOG.warn("No Timestamp Certificate found. Chain is skipped."); return; } List tsaCertificateChain = toCertificateTokenChain(getCertChain(timestampToken)); Date usageDate = timestampToken.getCreationDate(); for (CertificateToken cert : tsaCertificateChain) { List timestampCertChainUsageTimes = timestampCertChainDates.computeIfAbsent(cert, k -> new ArrayList<>()); if (!timestampCertChainUsageTimes.contains(usageDate)) { timestampCertChainUsageTimes.add(usageDate); } } } private void registerTimestampPOE(TimestampToken timestampToken) { if (isTimestampValid(timestampToken)) { LOG.debug("Extracting POE from timestamp : {}", timestampToken.getDSSIdAsString()); for (TimestampedReference timestampedReference : timestampToken.getTimestampedReferences()) { registerPOE(timestampedReference.getObjectId(), timestampToken); } } } /** * This method verifies whether a {@code timestampToken} is valid and * can be used as a valid POE for covered objects * * @param timestampToken {@link TimestampToken} to be checked * @return TRUE if the timestamp is valid, FALSE otherwise */ protected boolean isTimestampValid(TimestampToken timestampToken) { List certificateTokenChain = getCertificateTokenChain(timestampToken); Date lowestPOETime = getLowestPOETime(timestampToken); return getTimestampTokenVerifier().isAcceptable(timestampToken, certificateTokenChain, lowestPOETime); } private void registerPOE(String tokenId, TimestampToken timestampToken) { List poeTimeList = poeTimes.get(tokenId); if (Utils.isCollectionEmpty(poeTimeList)) { poeTimeList = new ArrayList<>(); poeTimes.put(tokenId, poeTimeList); } poeTimeList.add(new POE(timestampToken)); } private void registerPOE(String tokenId, Date poeTime) { List poeTimeList = poeTimes.get(tokenId); if (Utils.isCollectionEmpty(poeTimeList)) { poeTimeList = new ArrayList<>(); poeTimes.put(tokenId, poeTimeList); } poeTimeList.add(new POE(poeTime)); } 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 addEvidenceRecordForVerification(EvidenceRecord evidenceRecord) { addDocumentCertificateSource(evidenceRecord.getCertificateSource()); addDocumentCRLSource(evidenceRecord.getCRLSource()); addDocumentOCSPSource(evidenceRecord.getOCSPSource()); prepareTimestamps(evidenceRecord.getTimestamps()); final boolean added = processedEvidenceRecords.add(evidenceRecord); if (LOG.isTraceEnabled()) { if (added) { LOG.trace("EvidenceRecord added to processedEvidenceRecords: {} ", processedSignatures); } else { LOG.trace("EvidenceRecord already present processedEvidenceRecords: {} ", processedSignatures); } } } @Override public void validate() { TimestampToken timestampToken = getNotYetVerifiedTimestamp(); while (timestampToken != null) { validateTimestamp(timestampToken); timestampToken = getNotYetVerifiedTimestamp(); } Token token = getNotYetVerifiedToken(); while (token != null) { validateToken(token); token = getNotYetVerifiedToken(); } } private void validateTimestamp(TimestampToken timestampToken) { List certChain = getCertChain(timestampToken); Token token = getNotYetVerifiedTokenFromChain(certChain); while (token != null) { validateToken(token); token = getNotYetVerifiedTokenFromChain(certChain); } registerTimestampPOE(timestampToken); // POE is extracted after TST validation } private void validateToken(Token token) { // extract the certificate chain and add missing tokens for verification List certChain = getCertChain(token); if (Utils.collectionSize(certChain) > 1) { // ensure certificate chain is processed Token certChainToken = getNotYetVerifiedTokenFromChain(certChain); if (certChainToken != null) { validateToken(certChainToken); } } if (token instanceof CertificateToken) { getRevocationData((CertificateToken) token, certChain); } } /** * 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 a set of found {@link RevocationToken}s */ private Set> getRevocationData(final CertificateToken certToken, List certChain) { if (LOG.isTraceEnabled()) { LOG.trace("Checking revocation data for : {}", certToken.getDSSIdAsString()); } if (isRevocationDataNotRequired(certToken, getLowestPOETime(certToken))) { LOG.debug("Revocation data is not required for certificate : {}", certToken.getDSSIdAsString()); return Collections.emptySet(); } CertificateToken issuerToken = getIssuer(certToken); if (issuerToken == null) { LOG.warn("Issuer not found for certificate {}", certToken.getDSSIdAsString()); return Collections.emptySet(); } Set> revocations = new HashSet<>(); // ALL Embedded revocation data List> crlTokens = documentCRLSource.getRevocationTokens(certToken, issuerToken); for (RevocationToken revocationToken : crlTokens) { revocations.add(revocationToken); addRevocationTokenForVerification(revocationToken); } List> ocspTokens = documentOCSPSource.getRevocationTokens(certToken, issuerToken); for (RevocationToken revocationToken : ocspTokens) { revocations.add(revocationToken); addRevocationTokenForVerification(revocationToken); addDocumentCertificateSource(revocationToken.getCertificateSource()); // applicable only for OCSP } // add processed revocation tokens revocations.addAll(getRelatedRevocationTokens(certToken)); if ((remoteOCSPSource != null || remoteCRLSource != null) && (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); // Fetch OCSP or CRL from online sources final RevocationToken onlineRevocationToken = getRevocationToken(certToken, issuerToken, trustAnchor); // Check if the obtained revocation is not yet present if (onlineRevocationToken != null && !revocations.contains(onlineRevocationToken)) { LOG.debug("Obtained a new revocation data : {}, for certificate : {}", onlineRevocationToken.getDSSIdAsString(), certToken.getDSSIdAsString()); revocations.add(onlineRevocationToken); addRevocationTokenForVerification(onlineRevocationToken); linkRevocationToOtherCertificates(onlineRevocationToken, certToken, issuerToken); } } 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) { if (Utils.isCollectionNotEmpty(certChain)) { for (T token : certChain) { if (isTrustedAtUsageTime(token)) { return token; } } } return null; } private boolean containsTrustAnchorAtTime(List certChain, Date controlTime) { if (Utils.isCollectionNotEmpty(certChain)) { for (T token : certChain) { if (isTrustedAtTime(token, controlTime)) { return true; } } } return false; } private void linkRevocationToOtherCertificates(RevocationToken revocationToken, CertificateToken certificateToken, CertificateToken issuerCertificateToken) { // Only CRL may relate to multiple certificates if (revocationToken instanceof CRLToken) { CRLToken crlToken = (CRLToken) revocationToken; Set certificateTokens = certificateChildrenMap.get(issuerCertificateToken); for (CertificateToken childCertificate : certificateTokens) { if (certificateToken != childCertificate) { CRLToken newCRLToken = new CRLToken(childCertificate, crlToken.getCrlValidity()); newCRLToken.setExternalOrigin(crlToken.getExternalOrigin()); newCRLToken.setSourceURL(crlToken.getSourceURL()); addRevocationTokenForVerification(newCRLToken); } } } } private RevocationToken getRevocationToken(CertificateToken certificateToken, CertificateToken issuerCertificate, CertificateToken trustAnchor) { // configure the CompositeRevocationSource RevocationSource currentOCSPSource; RevocationSource currentCRLSource; if (!trustedCertSources.isEmpty() && trustAnchor != null) { LOG.trace("Initializing a revocation verifier for a trusted chain..."); currentOCSPSource = instantiateOCSPWithTrustServices(trustAnchor); currentCRLSource = instantiateCRLWithTrustServices(trustAnchor); } else { LOG.trace("Initializing a revocation verifier for not trusted chain..."); currentOCSPSource = remoteOCSPSource; currentCRLSource = remoteCRLSource; } // fetch the data final RevocationDataLoadingStrategy revocationDataLoadingStrategy = revocationDataLoadingStrategyFactory.create(); revocationDataLoadingStrategy.setCrlSource(currentCRLSource); revocationDataLoadingStrategy.setOcspSource(currentOCSPSource); revocationDataLoadingStrategy.setRevocationDataVerifier(getRevocationDataVerifier()); revocationDataLoadingStrategy.setFallbackEnabled(revocationFallback); return revocationDataLoadingStrategy.getRevocationToken(certificateToken, issuerCertificate); } private RevocationSource instantiateOCSPWithTrustServices(CertificateToken trustAnchor) { List alternativeOCSPUrls = getAlternativeOCSPUrls(trustAnchor); if (Utils.isCollectionNotEmpty(alternativeOCSPUrls) && remoteOCSPSource instanceof RevocationSourceAlternateUrlsSupport) { return new AlternateUrlsSourceAdapter<>((RevocationSourceAlternateUrlsSupport) remoteOCSPSource, alternativeOCSPUrls); } else { return remoteOCSPSource; } } private RevocationSource instantiateCRLWithTrustServices(CertificateToken trustAnchor) { List alternativeCRLUrls = getAlternativeCRLUrls(trustAnchor); if (Utils.isCollectionNotEmpty(alternativeCRLUrls) && remoteCRLSource instanceof RevocationSourceAlternateUrlsSupport) { return new AlternateUrlsSourceAdapter<>((RevocationSourceAlternateUrlsSupport) remoteCRLSource, alternativeCRLUrls); } else { return remoteCRLSource; } } private List getAlternativeOCSPUrls(CertificateToken trustAnchor) { List alternativeOCSPUrls = new ArrayList<>(); for (CertificateSource certificateSource : trustedCertSources.getSources()) { if (certificateSource instanceof TrustedCertificateSource) { TrustedCertificateSource trustedCertSource = (TrustedCertificateSource) 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 TrustedCertificateSource) { TrustedCertificateSource trustedCertSource = (TrustedCertificateSource) certificateSource; alternativeCRLUrls.addAll(trustedCertSource.getAlternativeCRLUrls(trustAnchor)); } } return alternativeCRLUrls; } @Override public boolean checkAllRequiredRevocationDataPresent() { return allRequiredRevocationDataPresent().isEmpty(); } /** * Returns the status of the required revocation data present check * * @return {@link TokenStatus} */ protected TokenStatus allRequiredRevocationDataPresent() { TokenStatus status = new TokenStatus(); Map> orderedCertificateChains = getOrderedCertificateChains(); for (List orderedCertChain : orderedCertificateChains.values()) { checkRevocationForCertificateChainAgainstBestSignatureTime(orderedCertChain, null, status); } boolean success = status.isEmpty(); if (!success) { status.setMessage("Revocation data is missing for one or more certificate(s)."); } return status; } private void checkRevocationForCertificateChainAgainstBestSignatureTime(List certificates, Date bestSignatureTime, TokenStatus status) { for (CertificateToken certificateToken : certificates) { if (isSelfSignedOrTrustedAtTime(certificateToken, bestSignatureTime)) { // break on the first trusted entry break; } else if (isRevocationDataNotRequired(certificateToken, bestSignatureTime)) { // skip the revocation check for OCSP certs if no check is specified continue; } boolean found = false; Date earliestNextUpdate = null; List> relatedRevocationTokens = getRelatedRevocationTokens(certificateToken); for (RevocationToken revocationToken : relatedRevocationTokens) { if (bestSignatureTime == null || bestSignatureTime.before(revocationToken.getThisUpdate())) { found = true; break; } else { if (revocationToken.getNextUpdate() != null && (earliestNextUpdate == null || earliestNextUpdate.after(revocationToken.getNextUpdate()))) { earliestNextUpdate = revocationToken.getNextUpdate(); } } } if (!found) { if (!getCertificateVerifier().isCheckRevocationForUntrustedChains() && !containsTrustAnchorAtTime(certificates, bestSignatureTime)) { status.addRelatedTokenAndErrorMessage(certificateToken, "Revocation data is skipped for untrusted certificate chain!"); } else if (Utils.isCollectionEmpty(relatedRevocationTokens) || bestSignatureTime == null) { // simple revocation presence check status.addRelatedTokenAndErrorMessage(certificateToken, "No revocation data found for certificate!"); } else if (earliestNextUpdate != null) { status.addRelatedTokenAndErrorMessage(certificateToken, String.format( "No revocation data found after the best signature time [%s]! " + "The nextUpdate available after : [%s]", DSSUtils.formatDateToRFC(bestSignatureTime), DSSUtils.formatDateToRFC(earliestNextUpdate))); } else { status.addRelatedTokenAndErrorMessage(certificateToken, String.format( "No revocation data found after the best signature time [%s]!", DSSUtils.formatDateToRFC(bestSignatureTime))); } if (status instanceof RevocationFreshnessStatus) { if (Utils.isCollectionNotEmpty(relatedRevocationTokens) && earliestNextUpdate == null) { Date lowestPOETime = getLowestPOETime(certificateToken); if (lowestPOETime != null) { earliestNextUpdate = new Date(lowestPOETime.getTime() + 1000); // last usage + 1s } } if (earliestNextUpdate != null) { ((RevocationFreshnessStatus) status).addTokenAndRevocationNextUpdateTime(certificateToken, earliestNextUpdate); } } } } } @Override public boolean checkAllPOECoveredByRevocationData() { return allPOECoveredByRevocationData().isEmpty(); } /** * Returns the status of the POE covered by revocation data check * * @return {@link RevocationFreshnessStatus} */ protected RevocationFreshnessStatus allPOECoveredByRevocationData() { RevocationFreshnessStatus status = new RevocationFreshnessStatus(); Map> orderedCertificateChains = getOrderedCertificateChains(); for (Map.Entry> entry : orderedCertificateChains.entrySet()) { CertificateToken firstChainCertificate = entry.getKey(); Date lastCertUsageDate = getLatestTimestampUsageDate(firstChainCertificate); if (lastCertUsageDate != null) { checkRevocationForCertificateChainAgainstBestSignatureTime(entry.getValue(), lastCertUsageDate, status); } } if (!status.isEmpty()) { status.setMessage("Revocation data is missing for one or more POE(s)."); } return status; } @Override public boolean checkAllTimestampsValid() { return allTimestampsValid().isEmpty(); } /** * Returns the status of the all timestamps valid check * * @return {@link TokenStatus} */ protected TokenStatus allTimestampsValid() { TokenStatus status = new TokenStatus(); for (TimestampToken timestampToken : processedTimestamps) { if (!timestampToken.isSignatureIntact() || !timestampToken.isMessageImprintDataFound() || !timestampToken.isMessageImprintDataIntact()) { status.addRelatedTokenAndErrorMessage(timestampToken, "Signature is not intact!"); } } if (!status.isEmpty()) { status.setMessage("Broken timestamp(s) detected."); } return status; } @Override public boolean checkCertificateNotRevoked(CertificateToken certificateToken) { return certificateNotRevoked(certificateToken).isEmpty(); } /** * Returns the status of the certificate not revoked check * * @param certificateToken {@code CertificateToken} certificate to be checked * @return {@link TokenStatus} */ protected TokenStatus certificateNotRevoked(CertificateToken certificateToken) { TokenStatus status = new TokenStatus(); checkCertificateIsNotRevokedRecursively(certificateToken, poeTimes.get(certificateToken.getDSSIdAsString()), status); if (!status.isEmpty()) { status.setMessage("Revoked/Suspended certificate(s) detected."); } return status; } @Override public boolean checkAllSignatureCertificatesNotRevoked() { return allSignatureCertificatesNotRevoked().isEmpty(); } /** * Returns the status of the all signature certificates not revoked check * * @return {@link TokenStatus} */ protected TokenStatus allSignatureCertificatesNotRevoked() { TokenStatus status = new TokenStatus(); for (AdvancedSignature signature : processedSignatures) { checkSignatureCertificatesNotRevoked(signature, status); } if (!status.isEmpty()) { status.setMessage("Revoked/Suspended certificate(s) detected."); } return status; } private void checkSignatureCertificatesNotRevoked(AdvancedSignature signature, TokenStatus status) { CertificateToken signingCertificate = signature.getSigningCertificateToken(); if (signingCertificate != null) { checkCertificateIsNotRevokedRecursively(signingCertificate, poeTimes.get(signature.getId()), status); } } private boolean checkCertificateIsNotRevokedRecursively(CertificateToken certificateToken, List poeTimes) { return checkCertificateIsNotRevokedRecursively(certificateToken, poeTimes, null); } private boolean checkCertificateIsNotRevokedRecursively(CertificateToken certificateToken, List poeTimes, TokenStatus status) { Date lowestPOETime = getLowestPOETime(poeTimes); if (isSelfSignedOrTrustedAtTime(certificateToken, lowestPOETime)) { return true; } else if (!isRevocationDataNotRequired(certificateToken, lowestPOETime)) { List> relatedRevocationTokens = getRelatedRevocationTokens(certificateToken); // check only available revocation data in order to not duplicate // the method {@code checkAllRequiredRevocationDataPresent()} if (Utils.isCollectionNotEmpty(relatedRevocationTokens)) { // check if there is a best-signature-time before the revocation date for (RevocationToken revocationToken : relatedRevocationTokens) { if (!getRevocationDataVerifier().checkCertificateNotRevoked(revocationToken, lowestPOETime)) { if (status != null) { status.addRelatedTokenAndErrorMessage(certificateToken, "Certificate is revoked/suspended!"); } return false; } } } } CertificateToken issuer = getIssuer(certificateToken); if (issuer != null) { return checkCertificateIsNotRevokedRecursively(issuer, poeTimes, status); } return true; } private boolean isRevocationDataNotRequired(CertificateToken certToken, Date controlTime) { return getRevocationDataVerifier().isRevocationDataSkip(certToken, controlTime); } private boolean isSelfSignedOrTrustedAtTime(CertificateToken certToken, Date controlTime) { return isSelfSigned(certToken) || isTrustedAtTime(certToken, controlTime); } private boolean isSelfSigned(CertificateToken certToken) { return certToken.isSelfSigned(); } private List> getRelatedRevocationTokens(CertificateToken certificateToken) { List> result = new ArrayList<>(); for (RevocationToken revocationToken : processedRevocations) { if (Utils.areStringsEqual(certificateToken.getDSSIdAsString(), revocationToken.getRelatedCertificateId())) { result.add(revocationToken); } } return result; } private boolean isRevocationDataRefreshNeeded(CertificateToken certToken, Collection> revocations) { Context context = null; // get best-signature-time for b-level certificate chain Date refreshNeededAfterTime = getLatestBestSignatureTime(certToken); if (refreshNeededAfterTime != null) { context = Context.SIGNATURE; } // get last usage dates for the same timestamp certificate chain Date lastTimestampUsageTime = getLatestTimestampUsageDate(certToken); if (lastTimestampUsageTime != null) { if (context == null) { context = Context.TIMESTAMP; } } // return best POE for other cases if (refreshNeededAfterTime == null) { // shall not return null refreshNeededAfterTime = getLowestPOETime(certToken); if (context == null) { context = Context.REVOCATION; } } boolean freshRevocationDataFound = false; for (RevocationToken revocationToken : revocations) { final List certificateTokenChain = toCertificateTokenChain(getCertChain(revocationToken)); if (Utils.isCollectionEmpty(certificateTokenChain)) { LOG.debug("Certificate chain is not found for a revocation data '{}'!", revocationToken.getDSSIdAsString()); continue; } final CertificateToken issuerCertificateToken = certificateTokenChain.iterator().next(); if (isRevocationFresh(revocationToken, refreshNeededAfterTime, context) && isRevocationIssuedAfterLastTimestampUsage(revocationToken, lastTimestampUsageTime, context) && RevocationReason.CERTIFICATE_HOLD != revocationToken.getReason() && isRevocationAcceptable(revocationToken, issuerCertificateToken, getLowestPOETime(issuerCertificateToken)) && hasValidPOE(revocationToken, certToken, issuerCertificateToken)) { freshRevocationDataFound = true; break; } } if (!freshRevocationDataFound) { LOG.debug("Revocation data refresh is needed"); return true; } return false; } private Date getLatestBestSignatureTime(CertificateToken certificateToken) { Date latestPOETime = null; for (Date bestSignatureTime : getBestSignatureTimes(certificateToken)) { if (latestPOETime == null || bestSignatureTime.after(latestPOETime)) { latestPOETime = bestSignatureTime; } } return latestPOETime; } private List getBestSignatureTimes(CertificateToken certificateToken) { List signatures = certificateSignaturesUsage.get(certificateToken); if (Utils.isCollectionEmpty(signatures)) { return Collections.emptyList(); } final List bestSignatureTimes = new ArrayList<>(); for (AdvancedSignature signature : signatures) { Date poeTime = getLowestPOETime(poeTimes.get(signature.getId())); if (poeTime != null) { bestSignatureTimes.add(poeTime); } } return bestSignatureTimes; } private Date getLatestTimestampUsageDate(CertificateToken certificateToken) { return getLatestTime(timestampCertChainDates.get(certificateToken)); } private Date getLatestTime(List dates) { if (Utils.isCollectionNotEmpty(dates)) { Date latestTime = null; for (Date date : dates) { if (latestTime == null || date.after(latestTime)) { latestTime = date; } } return latestTime; } return null; } private Date getLowestPOETime(Token token) { return getLowestPOETime(poeTimes.get(token.getDSSIdAsString())); } private Date getLowestPOETime(List poeList) { return getLowestPOE(poeList).getTime(); } private POE getLowestPOE(List poeList) { if (Utils.isCollectionEmpty(poeList)) { throw new IllegalStateException("POE shall be defined before accessing the 'poeTimes' list!"); } Iterator it = poeList.iterator(); POE lowestPOE = it.next(); while (it.hasNext()) { POE poe = it.next(); if (poe.getTime().before(lowestPOE.getTime())) { lowestPOE = poe; } } return lowestPOE; } private boolean isRevocationFresh(RevocationToken revocationToken, Date refreshNeededAfterTime, Context context) { return getRevocationDataVerifier().isRevocationDataFresh(revocationToken, refreshNeededAfterTime, context); } private boolean isRevocationIssuedAfterLastTimestampUsage(RevocationToken revocationToken, Date lastTimestampUsage, Context context) { if (lastTimestampUsage == null) { return true; } return getRevocationDataVerifier().isRevocationDataFresh(revocationToken, lastTimestampUsage, context); } private boolean isRevocationAcceptable(RevocationToken revocation, CertificateToken issuerCertificateToken, Date controlTime) { return getRevocationDataVerifier().isAcceptable(revocation, issuerCertificateToken, getCertificateTokenChain(issuerCertificateToken), controlTime); } private boolean hasValidPOE(RevocationToken revocation, CertificateToken relatedCertToken, CertificateToken issuerCertToken) { if (revocation.getNextUpdate() != null && !hasPOEAfterThisUpdateAndBeforeNextUpdate(revocation)) { LOG.debug("There is no POE for the revocation '{}' after its production time and before the nextUpdate! " + "Certificate: {}", revocation.getDSSIdAsString(), relatedCertToken.getDSSIdAsString()); return false; } // useful for short-life certificates (i.e. ocsp responder) if (issuerCertToken != null && !isTrustedAtUsageTime(issuerCertToken, Context.REVOCATION) && !hasPOEInTheValidityRange(issuerCertToken)) { LOG.debug("There is no POE for the revocation issuer '{}' for revocation '{}' within its validity range! " + "Certificate: {}", issuerCertToken.getDSSIdAsString(), revocation.getDSSIdAsString(), relatedCertToken.getDSSIdAsString()); return false; } LOG.debug("The revocation '{}' has a valid POE. Certificate: {}", revocation.getDSSIdAsString(), relatedCertToken.getDSSIdAsString()); return true; } private boolean hasPOEAfterThisUpdateAndBeforeNextUpdate(RevocationToken revocation) { List poeTimeList = poeTimes.get(revocation.getDSSIdAsString()); if (Utils.isCollectionNotEmpty(poeTimeList)) { for (POE poeTime : poeTimeList) { if (getRevocationDataVerifier().isAfterThisUpdateAndBeforeNextUpdate(revocation, poeTime.getTime())) { return true; } } } return false; } private boolean hasPOEInTheValidityRange(CertificateToken certificateToken) { List poeTimeList = poeTimes.get(certificateToken.getDSSIdAsString()); if (Utils.isCollectionNotEmpty(poeTimeList)) { for (POE poeTime : poeTimeList) { if (certificateToken.isValidOn(poeTime.getTime())) { return true; } // continue } } return false; } @Override public boolean checkAllSignatureCertificateHaveFreshRevocationData() { return allSignatureCertificateHaveFreshRevocationData().isEmpty(); } /** * Returns the status of the all signature certificates have fresh revocation data check * * @return {@link RevocationFreshnessStatus} */ protected RevocationFreshnessStatus allSignatureCertificateHaveFreshRevocationData() { RevocationFreshnessStatus status = new RevocationFreshnessStatus(); for (AdvancedSignature signature : processedSignatures) { checkAtLeastOneRevocationDataPresentAfterBestSignatureTime(signature, status); } if (!status.isEmpty()) { status.setMessage("Fresh revocation data is missing for one or more certificate(s)."); } return status; } private void checkAtLeastOneRevocationDataPresentAfterBestSignatureTime(AdvancedSignature signature, RevocationFreshnessStatus status) { CertificateToken signingCertificateToken = signature.getSigningCertificateToken(); Map> orderedCertificateChains = getOrderedCertificateChains(); for (Map.Entry> entry : orderedCertificateChains.entrySet()) { CertificateToken firstChainCertificate = entry.getKey(); if (firstChainCertificate.equals(signingCertificateToken)) { Date bestSignatureTime = getEarliestTimestampTime(); checkRevocationForCertificateChainAgainstBestSignatureTime(entry.getValue(), bestSignatureTime, status); } } } 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 boolean checkAllSignaturesNotExpired() { return allSignaturesNotExpired().isEmpty(); } /** * Returns the status of the all signatures not expired check * * @return {@link SignatureStatus} */ protected SignatureStatus allSignaturesNotExpired() { SignatureStatus status = new SignatureStatus(); for (AdvancedSignature signature : processedSignatures) { checkSignatureNotExpired(signature, status); } if (!status.isEmpty()) { status.setMessage("Expired signature found."); } return status; } private void checkSignatureNotExpired(AdvancedSignature signature, SignatureStatus status) { CertificateToken signingCertificate = signature.getSigningCertificateToken(); if (signingCertificate != null) { boolean signatureNotExpired = verifyCertificateTokenHasPOERecursively(signingCertificate, poeTimes.get(signature.getId())); if (!signatureNotExpired) { status.addRelatedTokenAndErrorMessage(signature, String.format("The signing certificate has expired " + "and there is no POE during its validity range : [%s - %s]!", DSSUtils.formatDateToRFC(signingCertificate.getNotBefore()), DSSUtils.formatDateToRFC(signingCertificate.getNotAfter()))); } } } private boolean verifyCertificateTokenHasPOERecursively(CertificateToken certificateToken, List poeTimeList) { if (Utils.isCollectionNotEmpty(poeTimeList)) { for (POE poeTime : poeTimeList) { if (certificateToken.isValidOn(poeTime.getTime()) && verifyPOE(poeTime)) { return true; } } } return false; } private boolean verifyPOE(POE poe) { TimestampToken timestampToken = poe.getTimestampToken(); if (timestampToken != null) { // check if the timestamp is valid at validation time CertificateToken issuerCertificateToken = getIssuer(timestampToken); List timestampPOEs = poeTimes.get(timestampToken.getDSSIdAsString()); return issuerCertificateToken != null && timestampToken.isValid() && verifyCertificateTokenHasPOERecursively(issuerCertificateToken, timestampPOEs) && checkCertificateIsNotRevokedRecursively(issuerCertificateToken, timestampPOEs); } // POE is provided return true; } @Override public Set getProcessedSignatures() { return Collections.unmodifiableSet(processedSignatures); } @Override public Set getProcessedCertificates() { return Collections.unmodifiableSet(processedCertificates); } @Override public Set> getProcessedRevocations() { return Collections.unmodifiableSet(processedRevocations); } @Override public Set getProcessedTimestamps() { return Collections.unmodifiableSet(processedTimestamps); } @Override public Set getProcessedEvidenceRecords() { return Collections.unmodifiableSet(processedEvidenceRecords); } private boolean isTrustedAtUsageTime(T token) { return isTrustedAtUsageTime(token, null); } private boolean isTrustedAtUsageTime(T token, Context context) { if (token instanceof CertificateToken) { List bestSignatureTimes = getBestSignatureTimes((CertificateToken) token); if (Utils.isCollectionNotEmpty(bestSignatureTimes)) { for (Date date : bestSignatureTimes) { if (isTrustedAtTime(token, date, context)) { return true; } } } else { Date lowestPOETime = getLowestPOETime(token); return isTrustedAtTime(token, lowestPOETime, context); } } return false; } private boolean isTrustedAtTime(T token, Date controlTime) { return isTrustedAtTime(token, controlTime, null); } private boolean isTrustedAtTime(T token, Date controlTime, Context context) { return token instanceof CertificateToken && getTrustAnchorVerifier().isTrustedAtTime((CertificateToken) token, controlTime, context); } @Override public ValidationData getValidationData(final AdvancedSignature signature) { return getValidationData(signature.getSigningCertificateToken()); } @Override public ValidationData getValidationData(final TimestampToken timestampToken) { return getValidationData(getIssuer(timestampToken)); } private ValidationData getValidationData(final CertificateToken certificateToken) { ValidationData validationData = new ValidationData(); if (certificateToken != null) { populateValidationDataRecursively(certificateToken, validationData); } return validationData; } private void populateValidationDataRecursively(final Token token, final ValidationData validationData) { boolean added = validationData.addToken(token); if (added) { if (token instanceof CertificateToken) { List> revocationTokens = getRelatedRevocationTokens((CertificateToken) token); for (RevocationToken revocationToken : revocationTokens) { populateValidationDataRecursively(revocationToken, validationData); } } CertificateToken issuerToken = getIssuer(token); if (issuerToken != null) { populateValidationDataRecursively(issuerToken, validationData); } } } /** * This class defines a POE provided to the validation process or obtained from processed timestamps */ private static class POE { /** The POE time */ private final Date time; /** The TimestampToken provided the POE, when present */ private TimestampToken timestampToken; /** * Default constructor to instantiate the object from a provided time * * @param time {@link Date} */ public POE(final Date time) { this.time = time; } /** * Constructor to instantiate the POE object from a TimestampToken * * @param timestampToken {@link TimestampToken} */ public POE(TimestampToken timestampToken) { this.timestampToken = timestampToken; this.time = timestampToken.getCreationDate(); } /** * Returns the POE time * * @return {@link Date} */ public Date getTime() { return time; } /** * Returns the TimestampToken used to create the POE, when present * * @return {@link TimestampToken} if it has been used for the POE, null otherwise */ public TimestampToken getTimestampToken() { return timestampToken; } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy