eu.europa.esig.dss.spi.validation.RevocationDataVerifier 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.DigestAlgorithm;
import eu.europa.esig.dss.enumerations.EncryptionAlgorithm;
import eu.europa.esig.dss.enumerations.RevocationType;
import eu.europa.esig.dss.enumerations.SignatureAlgorithm;
import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.model.x509.CertificateToken;
import eu.europa.esig.dss.model.x509.extension.CertificateExtension;
import eu.europa.esig.dss.model.x509.extension.CertificateExtensions;
import eu.europa.esig.dss.model.x509.extension.CertificatePolicies;
import eu.europa.esig.dss.model.x509.extension.CertificatePolicy;
import eu.europa.esig.dss.spi.CertificateExtensionsUtils;
import eu.europa.esig.dss.spi.DSSPKUtils;
import eu.europa.esig.dss.spi.DSSRevocationUtils;
import eu.europa.esig.dss.spi.OID;
import eu.europa.esig.dss.spi.x509.CertificateSource;
import eu.europa.esig.dss.spi.x509.revocation.RevocationToken;
import eu.europa.esig.dss.utils.Utils;
import org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers;
import org.bouncycastle.asn1.x509.Extension;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.EnumMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;
/**
* This class is used to verify acceptance of a revocation data for the following validation process,
* whether the revocation data has been extracted from a document or obtained from an online source.
* The class verifies the consistency of the given revocation information and
* applicability of the used cryptographic constraints used to create this token.
*
* NOTE: It is not recommended to use a single instance of {@code RevocationDataVerifier}
* within different {@code CertificateVerifier}s, as it may lead to concurrency issues during the execution
* in multi-threaded environments.
* Please use a new {@code RevocationDataVerifier} per each {@code CertificateVerifier}.
*
*/
public class RevocationDataVerifier {
private static final Logger LOG = LoggerFactory.getLogger(RevocationDataVerifier.class);
/** Default collection of acceptable digest algorithms */
private static final Collection DEFAULT_DIGEST_ALGORITHMS;
/** Default map of acceptable encryption algorithms and their corresponding key length */
private static final Map DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP;
/** Default collection of certificate extension OIDs for revocation data check skip */
private static final Collection DEFAULT_REVOCATION_SKIP_CERTIFICATE_EXTENSIONS;
/** Default maximum revocation freshness */
private static final Long DEFAULT_MAXIMUM_REVOCATION_FRESHNESS = 0L;
static {
DEFAULT_DIGEST_ALGORITHMS = Arrays.asList(
DigestAlgorithm.SHA224, DigestAlgorithm.SHA256, DigestAlgorithm.SHA384, DigestAlgorithm.SHA512,
DigestAlgorithm.SHA3_256, DigestAlgorithm.SHA3_384, DigestAlgorithm.SHA3_512);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP = new EnumMap<>(EncryptionAlgorithm.class);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP.put(EncryptionAlgorithm.DSA, 2048);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP.put(EncryptionAlgorithm.RSA, 1900);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP.put(EncryptionAlgorithm.RSASSA_PSS, 1900);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP.put(EncryptionAlgorithm.ECDSA, 256);
DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP.put(EncryptionAlgorithm.PLAIN_ECDSA, 256);
DEFAULT_REVOCATION_SKIP_CERTIFICATE_EXTENSIONS = Arrays.asList(
OID.id_etsi_ext_valassured_ST_certs.getId(), OCSPObjectIdentifiers.id_pkix_ocsp_nocheck.getId(),
Extension.noRevAvail.getId()
);
}
/**
* A collection of revocation data to be processed. This is a local variable used to be defined
* by a SignatureValidationContext, calling the class
*/
private Collection> processedRevocations;
/**
* A collection of Digest Algorithms to accept from CRL/OCSP responders.
* Note : revocation tokens created with digest algorithms other than listed in this collection will be skipped.
* Default : collection of algorithms is synchronized with ETSI 119 312 V1.4.2
*/
private Collection acceptableDigestAlgorithms;
/**
* Map of acceptable Encryption Algorithms with a corresponding minimal acceptable key length for each algorithm.
* Note : revocation tokens created with encryption algorithms other than listed in this map or
* with a key size smaller than defined in the map will be skipped.
* Default : collection of algorithms is synchronized with ETSI 119 312 V1.4.2
*/
private Map acceptableEncryptionAlgorithmKeyLength;
/**
* Collection of certificate extension identifiers indicating the revocation check is not required for those certificates
* Default : valassured-ST-certs (OID: "0.4.0.194121.2.1") and ocsp_noCheck (OID: "1.3.6.1.5.5.7.48.1.5")
*/
private Collection revocationSkipCertificateExtensions;
/**
* Collection of certificate policy identifiers indicating the revocation check is not required for those certificates
* Default : empty list
*/
private Collection revocationSkipCertificatePolicies;
/**
* Defines maximum allowed revocation freshness for signature's certificate chain
* Default : 0 (revocation must be issued after the best-signature-time)
*/
private Long signatureMaximumRevocationFreshness;
/**
* Defines maximum allowed revocation freshness for timestamp's certificate chain
* Default : 0 (revocation must be issued after the lowest time-stamp's POE)
* Note : revocation data shall be issued after the last usage time of the certificate
*/
private Long timestampMaximumRevocationFreshness;
/**
* Defines maximum allowed revocation freshness for revocation's certificate chain
* Default : 0 (revocation must be issued after the lowest revocation's POE)
*/
private Long revocationMaximumRevocationFreshness;
/**
* When set to TRUE and no revocation maximum freshness is defined for the given context,
* enforces revocation freshness check using a difference between revocation's
* nextUpdate and thisUpdate as the maximum acceptable revocation freshness.
*/
private boolean checkRevocationFreshnessNextUpdate;
/**
* This variable indicates whether timestamp certificates without revocation data should be accepted
*/
private boolean acceptTimestampCertificatesWithoutRevocation;
/**
* This variable indicates whether revocation certificates without revocation data should be accepted
*/
private boolean acceptRevocationCertificatesWithoutRevocation;
/**
* Verifies whether a given certificate token is a trust anchor at the control time
*/
private TrustAnchorVerifier trustAnchorVerifier;
/**
* Default constructor
*/
protected RevocationDataVerifier() {
// empty
}
/**
* Creates an empty instance of RevocationDataVerifier.
* All constraints should be configured manually.
*
* @return {@link RevocationDataVerifier}
*/
public static RevocationDataVerifier createEmptyRevocationDataVerifier() {
return new RevocationDataVerifier();
}
/**
* This method is used to instantiate a new {@code RevocationDataVerifier}, using the default validation constraints
* (synchronized with default validation policy).
*
* @return {@link RevocationDataVerifier}
*/
public static RevocationDataVerifier createDefaultRevocationDataVerifier() {
try {
final RevocationDataVerifier revocationDataVerifier = new RevocationDataVerifier();
revocationDataVerifier.setAcceptableDigestAlgorithms(DEFAULT_DIGEST_ALGORITHMS);
revocationDataVerifier.setAcceptableEncryptionAlgorithmKeyLength(DEFAULT_ENCRYPTION_ALGORITHMS_KEY_LENGTH_MAP);
revocationDataVerifier.setRevocationSkipCertificateExtensions(DEFAULT_REVOCATION_SKIP_CERTIFICATE_EXTENSIONS);
// #revocationSkipCertificatePolicies are empty
revocationDataVerifier.setSignatureMaximumRevocationFreshness(DEFAULT_MAXIMUM_REVOCATION_FRESHNESS);
revocationDataVerifier.setTimestampMaximumRevocationFreshness(DEFAULT_MAXIMUM_REVOCATION_FRESHNESS);
revocationDataVerifier.setRevocationMaximumRevocationFreshness(DEFAULT_MAXIMUM_REVOCATION_FRESHNESS);
// #checkRevocationFreshnessNextUpdate is false
// #acceptRevocationIssuersWithoutRevocation is false
return revocationDataVerifier;
} catch (Exception e) {
throw new DSSException(String.format(
"Unable to instantiate default RevocationDataVerifier. Reason : %s", e.getMessage()), e);
}
}
/**
* Gets trusted certificate source, when present
*
* @return {@link CertificateSource}
* @deprecated since DSS 6.2. Please use {@code #getTrustAnchorVerifier#getTrustedCertificateSource} method instead
*/
@Deprecated
public CertificateSource getTrustedCertificateSource() {
return getTrustAnchorVerifier().getTrustedCertificateSource();
}
/**
* Sets a trusted certificate source in order to accept trusted revocation data issuer certificates.
* Note : This method is used internally during a {@code eu.europa.esig.dss.validation.SignatureValidationContext}
* initialization, in order to provide the same trusted source as the one used within
* a {@code eu.europa.esig.dss.validation.CertificateVerifier}.
*
* @param trustedCertificateSource {@link CertificateSource}
* @deprecated since DSS 6.2. Please provide trusted certificate source within
* {@code TrustAnchorVerifier#setTrustedCertificateSource}, which can be set using
* {@code #setTrustAnchorVerifier} method
*/
@Deprecated
protected void setTrustedCertificateSource(CertificateSource trustedCertificateSource) {
TrustAnchorVerifier currentTrustAnchorVerifier = getTrustAnchorVerifier();
if (currentTrustAnchorVerifier == null) {
throw new NullPointerException("TrustAnchorVerifier is not defined! " +
"Please set TrustAnchorVerifier in order to provide a trustedCertificateSource.");
}
currentTrustAnchorVerifier.setTrustedCertificateSource(trustedCertificateSource);
}
/**
* Gets a collection of processed revocations, when present.
* This method is used internally during a {@code eu.europa.esig.dss.validation.SignatureValidationContext} execution,
* to verify presence of the collection of processed revocation data
*
* @return a collection of {@link RevocationToken}s
*/
protected Collection> getProcessedRevocations() {
return processedRevocations;
}
/**
* This method sets a collection of processed revocation tokens, for validation of timestamp's certificate chain.
* Note : This method is used internally during a {@code eu.europa.esig.dss.validation.SignatureValidationContext}
* initialization, in order to provide the same revocation data as the one used within
* the certificate validation process.
* @param processedRevocations a collection of {@link RevocationToken}s
*/
protected void setProcessedRevocations(Collection> processedRevocations) {
this.processedRevocations = processedRevocations;
}
/**
* Sets a collection of Digest Algorithms for acceptance.
* If a revocation token is signed with an algorithm other than listed in the collection, the token will be skipped.
* Default : collection of algorithms is synchronized with ETSI 119 312 V1.4.2
*
* @param acceptableDigestAlgorithms a collection if {@link DigestAlgorithm}s
*/
public void setAcceptableDigestAlgorithms(Collection acceptableDigestAlgorithms) {
Objects.requireNonNull(acceptableDigestAlgorithms, "Collection of DigestAlgorithms for acceptance cannot be null!");
this.acceptableDigestAlgorithms = acceptableDigestAlgorithms;
}
/**
* Sets a map of acceptable Encryption Algorithms and their corresponding minimal key length values.
* If a revocation token is signed with an algorithm other than listed in the collection or with a smaller key size,
* than the token will be skipped.
* Default : collection of algorithms is synchronized with ETSI 119 312 V1.4.2
*
* @param acceptableEncryptionAlgorithmKeyLength a map of {@link EncryptionAlgorithm}s and
* their corresponding minimal supported key lengths
*/
public void setAcceptableEncryptionAlgorithmKeyLength(Map acceptableEncryptionAlgorithmKeyLength) {
Objects.requireNonNull(acceptableEncryptionAlgorithmKeyLength, "Map of Encryption Algorithms for acceptance cannot be null!");
this.acceptableEncryptionAlgorithmKeyLength = acceptableEncryptionAlgorithmKeyLength;
}
/**
* Sets a collection of certificate extension OIDs indicating the revocation check shall be skipped
* for the given certificate
* Default : valassured-ST-certs (OID: "0.4.0.194121.2.1") and ocsp_noCheck (OID: "1.3.6.1.5.5.7.48.1.5")
* (extracted from validation policy)
*
* @param revocationSkipCertificateExtensions a collection of {@link String}s certificate extension OIDs
*/
public void setRevocationSkipCertificateExtensions(Collection revocationSkipCertificateExtensions) {
this.revocationSkipCertificateExtensions = revocationSkipCertificateExtensions;
}
/**
* Sets a collection of certificate policy OIDs indicating the revocation check shall be skipped for the given certificate
* Default : empty list (extracted from validation policy)
*
* @param revocationSkipCertificatePolicies a collection of {@link String}s certificate policy OIDs
*/
public void setRevocationSkipCertificatePolicies(Collection revocationSkipCertificatePolicies) {
this.revocationSkipCertificatePolicies = revocationSkipCertificatePolicies;
}
/**
* Sets maximum accepted freshness for revocation data issued for signature's certificate chain certificates.
* NULL value is used to disable the check.
* Default : 0 (revocation data shall be issued after the best-signature-time)
*
* @param signatureMaximumRevocationFreshness {@link Long} in milliseconds to evaluate revocation freshness,
*/
public void setSignatureMaximumRevocationFreshness(Long signatureMaximumRevocationFreshness) {
this.signatureMaximumRevocationFreshness = signatureMaximumRevocationFreshness;
}
/**
* Sets maximum accepted freshness for revocation data issued for time-stamp's certificate chain certificates.
* NULL value is used to disable the check.
* Default : 0 (revocation data shall be issued after the time-stamp's lowest POE)
* Note : algorithm always ensures that there is a revocation data issued after
* the usage time of the time-stamp's certificate
*
* @param timestampMaximumRevocationFreshness {@link Long} in milliseconds
*/
public void setTimestampMaximumRevocationFreshness(Long timestampMaximumRevocationFreshness) {
this.timestampMaximumRevocationFreshness = timestampMaximumRevocationFreshness;
}
/**
* Sets maximum accepted freshness for revocation data issued for revocation data's
* certificate chain certificates (CRL or OCSP).
* NULL value is used to disable the check.
* Default : 0 (revocation data shall be issued after the best-signature-time)
* Note : the signature or timestamp constraint takes precedence in case of conflict
*
* @param revocationMaximumRevocationFreshness {@link Long} in milliseconds
*/
public void setRevocationMaximumRevocationFreshness(Long revocationMaximumRevocationFreshness) {
this.revocationMaximumRevocationFreshness = revocationMaximumRevocationFreshness;
}
/**
* Sets whether the difference between revocation's nextUpdate and thisUpdate fields shall be taken
* as a maximum acceptable revocation freshness in case no maximum revocation freshness constraint
* is defined for the given context
* Default : FALSE (no revocation freshness check is performed when maximum revocation freshness is not defined)
*
* @param checkRevocationFreshnessNextUpdate whether revocation freshness should be checked against nextUpdate field
*/
public void setCheckRevocationFreshnessNextUpdate(boolean checkRevocationFreshnessNextUpdate) {
this.checkRevocationFreshnessNextUpdate = checkRevocationFreshnessNextUpdate;
}
/**
* This method sets whether a timestamp certificate without a valid revocation data should be accepted by the verifier
*
* @param acceptTimestampCertificatesWithoutRevocation whether a timestamp certificate without revocation data should be accepted
*/
public void setAcceptTimestampCertificatesWithoutRevocation(boolean acceptTimestampCertificatesWithoutRevocation) {
this.acceptTimestampCertificatesWithoutRevocation = acceptTimestampCertificatesWithoutRevocation;
}
/**
* This method sets whether a revocation certificate without a valid revocation data should be accepted by the verifier
*
* @param acceptRevocationCertificatesWithoutRevocation whether a revocation certificate without revocation data should be accepted
*/
public void setAcceptRevocationCertificatesWithoutRevocation(boolean acceptRevocationCertificatesWithoutRevocation) {
this.acceptRevocationCertificatesWithoutRevocation = acceptRevocationCertificatesWithoutRevocation;
}
/**
* Gets a trust anchor verifier. This method is used internally within {@code eu.europa.esig.dss.validation.SignatureValidationContext}
* to identify whether the configuration is already present and a {@code trustAnchorVerifier} should be set.
*
* @return {@link TrustAnchorVerifier}
*/
public TrustAnchorVerifier getTrustAnchorVerifier() {
return trustAnchorVerifier;
}
/**
* Sets whether a certificate token can be considered as a trust anchor at the given control time
* Note : This method is used internally during a {@code eu.europa.esig.dss.validation.SignatureValidationContext}
* initialization, when not defined explicitly, in order to provide the same configuration as the one used within
* a {@code eu.europa.esig.dss.validation.CertificateVerifier}.
*
* @param trustAnchorVerifier {@link TrustAnchorVerifier}
*/
public void setTrustAnchorVerifier(TrustAnchorVerifier trustAnchorVerifier) {
this.trustAnchorVerifier = trustAnchorVerifier;
}
/**
* This method verifies the validity of the given {@code RevocationToken} using the embedded
* issuer certificate token at the current time
*
* @param revocationToken {@link RevocationToken}
* @return TRUE if the revocation data is acceptable to continue the validation process, FALSE otherwise
*/
public boolean isAcceptable(RevocationToken> revocationToken) {
return isAcceptable(revocationToken, new Date());
}
/**
* This method verifies the validity of the given {@code RevocationToken} at the given {@code controlTime}
* using the embedded issuer certificate token
*
* @param revocationToken {@link RevocationToken}
* @param controlTime {@link Date}
* @return TRUE if the revocation data is acceptable to continue the validation process, FALSE otherwise
*/
public boolean isAcceptable(RevocationToken> revocationToken, Date controlTime) {
return isAcceptable(revocationToken, revocationToken.getIssuerCertificateToken(), controlTime);
}
/**
* This method verifies the validity of the given {@code RevocationToken} at the current time
*
* @param revocationToken {@link RevocationToken}
* @param issuerCertificateToken {@link CertificateToken} issued the current revocation
* @return TRUE if the revocation data is acceptable to continue the validation process, FALSE otherwise
*/
public boolean isAcceptable(RevocationToken> revocationToken, CertificateToken issuerCertificateToken) {
return isAcceptable(revocationToken, issuerCertificateToken, new Date());
}
/**
* This method verifies the validity of the given {@code RevocationToken} at {@code controlTime}
*
* @param revocationToken {@link RevocationToken}
* @param issuerCertificateToken {@link CertificateToken} issued the current revocation
* @param controlTime {@link Date}
* @return TRUE if the revocation data is acceptable to continue the validation process, FALSE otherwise
*/
public boolean isAcceptable(RevocationToken> revocationToken, CertificateToken issuerCertificateToken, Date controlTime) {
return isAcceptable(revocationToken, issuerCertificateToken, Collections.emptyList(), controlTime);
}
/**
* This method verifies the validity of the given {@code RevocationToken} at {@code controlTime}
*
* @param revocationToken {@link RevocationToken}
* @param issuerCertificateToken {@link CertificateToken} issued the current revocation
* @param certificateChain a list of {@link CertificateToken}s, representing a certificate chain of the issuer
* @param controlTime {@link Date}
* @return TRUE if the revocation data is acceptable to continue the validation process, FALSE otherwise
*/
public boolean isAcceptable(RevocationToken> revocationToken, CertificateToken issuerCertificateToken,
List certificateChain, Date controlTime) {
return isRevocationTokenValid(revocationToken) && isRevocationDataComplete(revocationToken)
&& isGoodIssuer(revocationToken, issuerCertificateToken, controlTime)
&& isCertificateChainValid(certificateChain, controlTime, Context.REVOCATION) && isConsistent(revocationToken)
&& isAcceptableSignatureAlgorithm(revocationToken, issuerCertificateToken);
}
/**
* Verifies whether the revocation token is cryptographically valid
*
* @param revocationToken {@link RevocationToken} to be verified
* @return TRUE if the revocation token is valid, FALSE otherwise
*/
protected boolean isRevocationTokenValid(RevocationToken> revocationToken) {
if (!revocationToken.isValid()) {
LOG.warn("The revocation token '{}' is not valid : {}!", revocationToken.getDSSIdAsString(), revocationToken.getInvalidityReason());
return false;
}
return true;
}
/**
* Verifies whether the revocation token contains all required data
*
* @param revocationToken {@link RevocationToken} to be verifies
* @return TRUE if the revocation token is complete, FALSE otherwise
*/
protected boolean isRevocationDataComplete(RevocationToken> revocationToken) {
if (revocationToken.getRelatedCertificate() == null) {
LOG.warn("The revocation '{}' does not have a related certificate!", revocationToken.getDSSIdAsString());
return false;
}
if (revocationToken.getStatus() == null) {
LOG.warn("The obtained revocation token '{}' does not contain the certificate status!", revocationToken.getDSSIdAsString());
return false;
}
if (revocationToken.getThisUpdate() == null) {
LOG.warn("The obtained revocation token '{}' does not contain thisUpdate field!", revocationToken.getDSSIdAsString());
return false;
}
return true;
}
/**
* Verifies validity if the {@code issuerCertificateToken} of {@code revocationToken}
*
* @param revocationToken {@link RevocationToken} concerned revocation token
* @param issuerCertificateToken {@link CertificateToken} issued the revocation token
* @param controlTime {@link Date} validation time
* @return TRUE if the issuer certificate token is valid at the control time, FALSE otherwise
*/
protected boolean isGoodIssuer(RevocationToken> revocationToken, CertificateToken issuerCertificateToken, Date controlTime) {
if (issuerCertificateToken == null) {
LOG.warn("The issuer certificate is not found for the obtained revocation '{}'!", revocationToken.getDSSIdAsString());
return false;
}
if (RevocationType.OCSP.equals(revocationToken.getRevocationType()) &&
!DSSRevocationUtils.checkIssuerValidAtRevocationProductionTime(revocationToken, issuerCertificateToken)) {
LOG.warn("The revocation token '{}' has been produced outside the issuer certificate's validity range!",
revocationToken.getDSSIdAsString());
return false;
}
if (!isCertificateValid(issuerCertificateToken, controlTime)) {
return false;
}
return true;
}
/**
* Verifies whether the revocation token is consistent
*
* @param revocation {@link RevocationToken} to be verified
* @return TRUE if the revocation token is consistent, FALSE otherwise
*/
protected boolean isConsistent(RevocationToken> revocation) {
final CertificateToken certToken = revocation.getRelatedCertificate();
if (!isRevocationIssuedAfterCertificateNotBefore(revocation, certToken)) {
LOG.warn("The revocation '{}' has been produced before the start of the validity of the certificate '{}'!",
revocation.getDSSIdAsString(), certToken.getDSSIdAsString());
return false;
}
if (!doesRevocationKnowCertificate(revocation, certToken)) {
LOG.warn("The revocation '{}' was not issued during the validity period of the certificate! Certificate: {}",
revocation.getDSSIdAsString(), certToken.getDSSIdAsString());
return false;
}
LOG.debug("The revocation '{}' is consistent. Certificate: {}", revocation.getDSSIdAsString(), certToken.getDSSIdAsString());
return true;
}
private boolean isRevocationIssuedAfterCertificateNotBefore(RevocationToken> revocationToken, CertificateToken certificateToken) {
return certificateToken.getNotBefore().compareTo(revocationToken.getThisUpdate()) <= 0;
}
private boolean doesRevocationKnowCertificate(RevocationToken> revocationToken, CertificateToken certificateToken) {
return revocationInformationAssured(revocationToken, certificateToken) || certHashMatch(revocationToken);
}
private boolean revocationInformationAssured(RevocationToken> revocationToken, CertificateToken certificateToken) {
Date notAfterRevoc = revocationToken.getThisUpdate();
Date certNotAfter = certificateToken.getNotAfter();
Date expiredCertsOnCRL = revocationToken.getExpiredCertsOnCRL();
if (expiredCertsOnCRL != null) {
notAfterRevoc = expiredCertsOnCRL;
}
Date archiveCutOff = revocationToken.getArchiveCutOff();
if (archiveCutOff != null) {
notAfterRevoc = archiveCutOff;
}
return certNotAfter.compareTo(notAfterRevoc) >= 0;
}
private boolean certHashMatch(RevocationToken> revocationToken) {
return revocationToken.isCertHashPresent() && revocationToken.isCertHashMatch();
}
/**
* Verifies validity of the used signature algorithm on revocation data creation is still valid according
* to the specified cryptographic constraints.
*
* @param revocationToken {@link RevocationToken} to be verified
* @param issuerCertificateToken {@link CertificateToken} issued the revocation token
* @return TRUE if the signature algorithm used on revocation token creation, FALSE otherwise
*/
protected boolean isAcceptableSignatureAlgorithm(RevocationToken> revocationToken, CertificateToken issuerCertificateToken) {
if (Utils.isCollectionEmpty(acceptableDigestAlgorithms)) {
LOG.info("No acceptable digest algorithms defined!");
return false;
}
if (Utils.isMapEmpty(acceptableEncryptionAlgorithmKeyLength)) {
LOG.info("No acceptable encryption algorithms defined!");
return false;
}
SignatureAlgorithm signatureAlgorithm = revocationToken.getSignatureAlgorithm();
if (signatureAlgorithm == null) {
LOG.warn("Signature algorithm was not identified for an obtained revocation token '{}'!",
revocationToken.getDSSIdAsString());
return false;
}
if (!acceptableDigestAlgorithms.contains(signatureAlgorithm.getDigestAlgorithm())) {
LOG.warn("The used DigestAlgorithm {} is not acceptable for revocation token '{}'!",
signatureAlgorithm.getDigestAlgorithm(), revocationToken.getDSSIdAsString());
return false;
}
Integer encryptionAlgorithmMinKeySize = acceptableEncryptionAlgorithmKeyLength.get(signatureAlgorithm.getEncryptionAlgorithm());
if (encryptionAlgorithmMinKeySize == null) {
LOG.warn("The EncryptionAlgorithm {} is not acceptable for revocation token '{}'!",
signatureAlgorithm.getEncryptionAlgorithm(), revocationToken.getDSSIdAsString());
return false;
}
int publicKeySize = issuerCertificateToken != null ? DSSPKUtils.getPublicKeySize(issuerCertificateToken.getPublicKey()) : -1;
if (publicKeySize <= 0) {
LOG.warn("Key size used to sign revocation token '{}' cannot be identified!",
revocationToken.getDSSIdAsString());
return false;
}
if (publicKeySize < encryptionAlgorithmMinKeySize) {
LOG.warn("The key size '{}' used to sign revocation token '{}' is smaller than minimal acceptable value '{}'!",
publicKeySize, revocationToken.getDSSIdAsString(), encryptionAlgorithmMinKeySize);
return false;
}
return true;
}
/**
* Checks and returns whether the revocation check shall be skipped for the given certificate at the current time
*
* @param certificateToken {@link CertificateToken} to check
* @return TRUE if the revocation check shall be skipped, FALSE otherwise
*/
public boolean isRevocationDataSkip(CertificateToken certificateToken) {
return isRevocationDataSkip(certificateToken, new Date());
}
/**
* Checks and returns whether the revocation check shall be skipped for the given certificate at the {@code controlTime}
*
* @param certificateToken {@link CertificateToken} to check
* @param controlTime {@link Date} the validation time
* @return TRUE if the revocation check shall be skipped, FALSE otherwise
*/
public boolean isRevocationDataSkip(CertificateToken certificateToken, Date controlTime) {
if (isTrustedAtTime(certificateToken, controlTime)) {
return true;
}
if (certificateToken.isSelfSigned()) {
return true;
}
if (Utils.isCollectionEmpty(revocationSkipCertificateExtensions)) {
return false;
}
CertificateExtensions certificateExtensions = CertificateExtensionsUtils.getCertificateExtensions(certificateToken);
List allCertificateExtensions = certificateExtensions.getAllCertificateExtensions();
if (Utils.isCollectionNotEmpty(allCertificateExtensions) &&
Utils.containsAny(allCertificateExtensions.stream().map(CertificateExtension::getOid).collect(Collectors.toSet()),
revocationSkipCertificateExtensions)) {
return true;
}
if (Utils.isCollectionEmpty(revocationSkipCertificatePolicies)) {
return false;
}
CertificatePolicies certificatePolicies = certificateExtensions.getCertificatePolicies();
if (certificatePolicies != null && Utils.isCollectionNotEmpty(certificatePolicies.getPolicyList()) &&
Utils.containsAny(certificatePolicies.getPolicyList().stream().map(CertificatePolicy::getOid).collect(Collectors.toSet()),
revocationSkipCertificatePolicies)) {
return true;
}
return false;
}
/**
* This method verifies whether the {@code certificateToken} is trusted at {@code controlTime}
*
* @param certificateToken {@link CertificateToken} to check
* @param controlTime {@link Date} the validation time
* @return TRUE if the certificate is trusted at the given time, FALSE otherwise
*/
protected boolean isTrustedAtTime(CertificateToken certificateToken, Date controlTime) {
final TrustAnchorVerifier currentTrustAnchorVerifier = getTrustAnchorVerifier();
if (currentTrustAnchorVerifier == null) {
LOG.warn("TrustAnchorVerifier is not defined! None of the certificates will be considered as a trust anchor.");
return false;
}
return currentTrustAnchorVerifier.isTrustedAtTime(certificateToken, controlTime, Context.REVOCATION);
}
/**
* This method verifies if the {@code revocationToken} considered within {@code context}
* is fresh enough relatively to the given {@code validationTime}
*
* @param revocationToken {@link RevocationToken} to be validated
* @param validationTime {@link Date} the target time after which revocation token is expected to be refreshed
* @param context {@link Context} of the current revocation token's validation process
* @return TRUE if the revocation token is considered fresh enough, FALSE otherwise
*/
public boolean isRevocationDataFresh(RevocationToken> revocationToken, Date validationTime, Context context) {
Long maximumRevocationFreshness = getMaximumRevocationFreshness(context);
if (maximumRevocationFreshness == null) {
return isRevocationThisUpdateAfterValidationTimeNullConstraint(revocationToken, validationTime);
}
return isRevocationThisUpdateAfterValidationTime(revocationToken, validationTime, maximumRevocationFreshness);
}
/**
* This method verifies whether the revocation's thisUpdate time is after the {@code validationTime} minus
* the acceptable {@code maximumRevocationFreshness}
*
* @param revocationToken {@link RevocationToken} to be validated
* @param validationTime {@link Date}
* @param maximumRevocationFreshness long
* @return TRUE if the revocation's thisUpdate is after the validation time minus
* the maximum acceptable revocation freshness, FALSE otherwise
*/
protected boolean isRevocationThisUpdateAfterValidationTime(RevocationToken> revocationToken, Date validationTime,
long maximumRevocationFreshness) {
long validationDateTime = validationTime.getTime();
long limit = validationDateTime - maximumRevocationFreshness;
Date thisUpdate = revocationToken.getThisUpdate();
return thisUpdate != null && thisUpdate.after(new Date(limit));
}
/**
* This method verifies whether the revocation's thisUpdate time is after the {@code validationTime} minus
* the difference between nextUpdate and thisUpdate field values
*
* @param revocationToken {@link RevocationToken} to be validated
* @param validationTime {@link Date}
* @return TRUE if the revocation freshness check succeeds against revocation's nextUpdate, FALSE otherwise
*/
protected boolean isRevocationThisUpdateAfterValidationTimeNullConstraint(RevocationToken> revocationToken,
Date validationTime) {
if (!checkRevocationFreshnessNextUpdate) {
// no check to be performed
return true;
}
Date nextUpdate = revocationToken.getNextUpdate();
if (nextUpdate == null) {
if (LOG.isDebugEnabled()) {
LOG.debug("No NextUpdate for revocation data with id '{}'. Revocation Freshness check failed.",
revocationToken.getDSSIdAsString());
}
return false;
}
long limit = getDifference(nextUpdate, revocationToken.getThisUpdate());
return isRevocationThisUpdateAfterValidationTime(revocationToken, validationTime, limit);
}
private long getDifference(Date nextUpdate, Date thisUpdate) {
long nextUpdateTime = nextUpdate == null ? 0 : nextUpdate.getTime();
long thisUpdateTime = thisUpdate == null ? 0 : thisUpdate.getTime();
return nextUpdateTime - thisUpdateTime;
}
private Long getMaximumRevocationFreshness(Context context) {
switch (context) {
case SIGNATURE:
case COUNTER_SIGNATURE:
case CERTIFICATE:
return signatureMaximumRevocationFreshness;
case TIMESTAMP:
case EVIDENCE_RECORD:
return timestampMaximumRevocationFreshness;
case REVOCATION:
return revocationMaximumRevocationFreshness;
default:
throw new UnsupportedOperationException(
String.format("The provided validation context '%s' is not supported!", context));
}
}
/**
* This method verifies whether a certificate was not revoked at {@code controlTime}
*
* @param revocationToken {@link RevocationToken} to check
* @param controlTime {@link Date} time to check at
* @return TRUE if the certificate was not revoked at control time, FALSE otherwise
*/
public boolean checkCertificateNotRevoked(RevocationToken> revocationToken, Date controlTime) {
return revocationToken.getStatus().isKnown() &&
(!revocationToken.getStatus().isRevoked() || controlTime.before(revocationToken.getRevocationDate()));
}
/**
* Verifies whether the {@code controlTime} is within revocation data's thisUpdate and nextUpdate times
*
* @param revocationToken {@link RevocationToken} to validate
* @param date {@link Date} validation time
* @return TRUE if the control time is within thisUpdate and nextUpdate times, FALSE otherwise
*/
public boolean isAfterThisUpdateAndBeforeNextUpdate(RevocationToken> revocationToken, Date date) {
Date thisUpdate = revocationToken.getThisUpdate();
Date nextUpdate = revocationToken.getNextUpdate();
return thisUpdate != null && date.compareTo(thisUpdate) >= 0 && (nextUpdate == null || date.compareTo(nextUpdate) <= 0);
}
/**
* This method verifies whether the certificate chain is valid at control time
*
* @param certificateTokenChain a list of {@link CertificateToken}s
* @param controlTime {@link Date} validation time
* @param context {@link Context} validation context
* @return TRUE if the certificate chain is valid at control time, FALSE otherwise
*/
public boolean isCertificateChainValid(List certificateTokenChain, Date controlTime, Context context) {
if (isAcceptCertificatesWithoutRevocation(context)) {
return true;
}
for (CertificateToken certificateToken : certificateTokenChain) {
if (certificateToken.isSelfSigned() || isTrustedAtTime(certificateToken, controlTime)) {
break;
}
if (!certificateToken.isValid()) {
LOG.warn("The certificate '{}' is cryptographically invalid!", certificateToken.getDSSIdAsString());
return false;
}
if (!isCertificateValid(certificateToken, controlTime)) {
return false;
}
}
return true;
}
private boolean isAcceptCertificatesWithoutRevocation(Context context) {
return (Context.TIMESTAMP == context && acceptTimestampCertificatesWithoutRevocation) ||
(Context.REVOCATION == context && acceptRevocationCertificatesWithoutRevocation);
}
/**
* Verifies if the certificate is valid
*
* @param certificateToken {@link CertificateToken}
* @param controlTime {@link Date}
* @return TRUE if the certificate token is valid, FALSE otherwise
*/
protected boolean isCertificateValid(CertificateToken certificateToken, Date controlTime) {
if (!isRevocationDataSkip(certificateToken, controlTime)) {
if (!hasRevocationAccessPoints(certificateToken)) {
LOG.warn("The certificate '{}' requires a revocation data, " +
"which is not acceptable due its configuration (no revocation access location points)!",
certificateToken.getDSSIdAsString());
return false;
}
if (!isCertificateNotRevoked(certificateToken, controlTime)) {
LOG.warn("The certificate '{}' does not contain a valid revocation data information!",
certificateToken.getDSSIdAsString());
return false;
}
}
return true;
}
private boolean hasRevocationAccessPoints(final CertificateToken certificateToken) {
return Utils.isCollectionNotEmpty(CertificateExtensionsUtils.getCRLAccessUrls(certificateToken)) ||
Utils.isCollectionNotEmpty(CertificateExtensionsUtils.getOCSPAccessUrls(certificateToken));
}
/**
* This method verifies whether a certificate token is not revoked at control time
*
* @param certificateToken {@link CertificateToken} to validated
* @param controlTime {@link Date} validation time
* @return TRUE if the certificate token is valid at control time, FALSE otherwise
*/
protected boolean isCertificateNotRevoked(CertificateToken certificateToken, Date controlTime) {
List> revocationData = getRelatedRevocationTokens(certificateToken);
if (Utils.isCollectionNotEmpty(revocationData)) {
for (RevocationToken> revocationToken : revocationData) {
if (isAcceptable(revocationToken, controlTime) && checkCertificateNotRevoked(revocationToken, controlTime)) {
return true;
}
}
}
LOG.warn("The certificate '{}' is not known to be not revoked!", certificateToken.getDSSIdAsString());
return false;
}
private List> getRelatedRevocationTokens(CertificateToken certificateToken) {
if (Utils.isCollectionEmpty(processedRevocations)) {
return Collections.emptyList();
}
List> result = new ArrayList<>();
for (RevocationToken> revocationToken : processedRevocations) {
if (Utils.areStringsEqual(certificateToken.getDSSIdAsString(), revocationToken.getRelatedCertificateId())) {
result.add(revocationToken);
}
}
return result;
}
}