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

eu.europa.esig.dss.spi.x509.revocation.RepositoryRevocationSource 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.x509.revocation; import eu.europa.esig.dss.model.x509.CertificateToken; import eu.europa.esig.dss.model.x509.revocation.Revocation; import eu.europa.esig.dss.utils.Utils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.stream.Collectors; /** * Allows storing and retrieving of revocation data to/from a repository * (e.g. database) * * @param {@code CRL} or {@code OCSP} */ public abstract class RepositoryRevocationSource implements RevocationSource, MultipleRevocationSource { private static final Logger LOG = LoggerFactory.getLogger(RepositoryRevocationSource.class); private static final long serialVersionUID = 8116937707098957391L; /** * Data source used to access a revocation token that is not present in the repository */ protected RevocationSource proxiedSource; /** * Default cache delay in case of null nextUpdate in the revocation data */ private Long defaultNextUpdateDelay; /** * Maximum cache delay for the revocation data */ private Long maxNextUpdateDelay; /** * If true, removes revocation tokens from DB with nextUpdate before the current date */ private boolean removeExpired = true; /** * Default constructor instantiating object with null values */ protected RepositoryRevocationSource() { // empty } /** * Initialize a list of revocation token keys {@link String} from the given {@link CertificateToken} * * @param certificateToken {@link CertificateToken} * @return list of {@link String} revocation keys */ protected abstract List initRevocationTokenKeys(CertificateToken certificateToken); /** * Finds a list of RevocationTokens in the cache for the given {@code certificateToken} * with the corresponding {@code key} * * @param key the key {@link String} * @param certificateToken {@link CertificateToken} * @param issuerCertToken {@link CertificateToken} * @return a list of {@link RevocationToken} objects */ protected abstract List> findRevocations(final String key, final CertificateToken certificateToken, final CertificateToken issuerCertToken); /** * Inserts a new RevocationToken into the cache * * @param revocationKey {@link String} * @param token {@link RevocationToken} */ protected abstract void insertRevocation(final String revocationKey, final RevocationToken token); /** * Updates the RevocationToken into cache * * @param revocationKey {@link String} * @param token {@link RevocationToken} */ protected abstract void updateRevocation(final String revocationKey, final RevocationToken token); /** * Removes the RevocationToken from cache with the given key * * @param revocationKey {@link String} */ protected abstract void removeRevocation(final String revocationKey); /** * Sets the default next update delay for the cached files in seconds. If * more time has passed from the revocation token's thisUpdate and next update * time is not specified, then a fresh copy is downloaded and cached, otherwise * a cached copy is used. *

* {@code * If revocation.nextUpdate = null, then nextUpdate = revocation.thisUpdate + defaultNextUpdateDelay * } * * @param defaultNextUpdateDelay long value (seconds) */ public void setDefaultNextUpdateDelay(final Long defaultNextUpdateDelay) { this.defaultNextUpdateDelay = defaultNextUpdateDelay == null ? null : defaultNextUpdateDelay * 1000; // to milliseconds } /** * Sets the maximum allowed nextUpdate delay for cached files in seconds. * Allows to force refresh in case of long periods between revocation * publication (eg : 6 months for ARL). *

* {@code * If revocation.nextUpdate > revocation.thisUpdate + maxNextUpdateDelay, then nextUpdate = revocation.thisUpdate + maxNextUpdateDelay * } * * @param maxNextUpdateDelay long value (seconds) */ public void setMaxNextUpdateDelay(final Long maxNextUpdateDelay) { this.maxNextUpdateDelay = maxNextUpdateDelay == null ? null : maxNextUpdateDelay * 1000; // to milliseconds } /** * The proxied revocation source to be called if the data is not available in * the cache * * @param proxiedSource the proxiedSource to set */ public void setProxySource(final RevocationSource proxiedSource) { this.proxiedSource = proxiedSource; } /** * Sets whether the expired revocation data shall be removed from the cache *

* Default : TRUE (expired revocation data is being removed from the cache) * * @param removeExpired the removeExpired to set */ public void setRemoveExpired(boolean removeExpired) { this.removeExpired = removeExpired; } @Override public RevocationToken getRevocationToken(final CertificateToken certificateToken, final CertificateToken issuerCertificateToken) { return getRevocationToken(certificateToken, issuerCertificateToken, false); } /** * Retrieves a revocation token for the given {@link CertificateToken} * * @param certificateToken {@link CertificateToken} * @param issuerCertificateToken {@link CertificateToken} of the issuer of * certificateToken * @param forceRefresh if true, explicitly skips the cache * @return {@link RevocationToken} */ public RevocationToken getRevocationToken(final CertificateToken certificateToken, final CertificateToken issuerCertificateToken, boolean forceRefresh) { List> revocationTokens = getRevocationTokens(certificateToken, issuerCertificateToken, forceRefresh); if (Utils.isCollectionNotEmpty(revocationTokens)) { if (Utils.collectionSize(revocationTokens) == 1) { return revocationTokens.iterator().next(); } else { LOG.info("More than one revocation token has been found for certificate with Id '{}'. " + "Return the latest revocation data.", certificateToken.getDSSIdAsString()); return getLatestRevocationData(revocationTokens); } } return null; } @Override public List> getRevocationTokens(CertificateToken certificateToken, CertificateToken issuerCertificateToken) { return getRevocationTokens(certificateToken, issuerCertificateToken, false); } /** * Retrieves a list of revocation token for the given {@link CertificateToken} * * @param certificateToken {@link CertificateToken} * @param issuerCertificateToken {@link CertificateToken} of the issuer of * certificateToken * @param forceRefresh if true, explicitly skips the cache * @return a list of {@link RevocationToken}s */ public List> getRevocationTokens(final CertificateToken certificateToken, final CertificateToken issuerCertificateToken, boolean forceRefresh) { if (certificateToken == null || issuerCertificateToken == null) { LOG.warn("Certificate token or issuer's certificate token is null. Cannot get a revocation token!"); return Collections.emptyList(); } Collection keys = initRevocationTokenKeys(certificateToken); if (forceRefresh) { LOG.info("Cache is skipped to retrieve the revocation token for certificate with Id '{}'", certificateToken.getDSSIdAsString()); } else { final Map>> cachedRevocationTokensMap = extractRevocationFromCacheSource(certificateToken, issuerCertificateToken, keys); keys = cachedRevocationTokensMap.keySet(); // override with returned keys if (Utils.isMapNotEmpty(cachedRevocationTokensMap)) { // add all extracted revocation values to a single List return cachedRevocationTokensMap.values().stream().flatMap(Collection::stream).collect(Collectors.toList()); } } final RevocationToken revocationToken = extractAndInsertRevocationTokenFromProxiedSource( certificateToken, issuerCertificateToken, keys); if (revocationToken != null) { return Collections.singletonList(revocationToken); } return Collections.emptyList(); } /** * Returns a map of correspondence between requested revocation {@code keys} and extracted revocation data tokens. * The map contains entries only for keys with available and still fresh revocation data. * * @param certificateToken {@link CertificateToken} to extract the revocation token for * @param issuerCertificateToken {@link CertificateToken} of the issuer * @param keys a collection of {@link String} keys, * that can be used as unique identifications of the revocation entry * @return a map between {@link String} keys and list of {@link RevocationToken}s */ private Map>> extractRevocationFromCacheSource( final CertificateToken certificateToken, final CertificateToken issuerCertificateToken, Collection keys) { final Map>> result = new HashMap<>(); for (String key : keys) { final List> revocationTokens = findRevocations(key, certificateToken, issuerCertificateToken); if (Utils.isCollectionNotEmpty(revocationTokens)) { final List> freshRevocationData = revocationTokens.stream() .filter(r -> isNotExpired(r, issuerCertificateToken)).collect(Collectors.toList()); if (Utils.isCollectionNotEmpty(freshRevocationData)) { result.put(key, freshRevocationData); } else { LOG.debug("Revocation token is expired in the cache for certificate with Id '{}'", certificateToken.getDSSIdAsString()); if (removeExpired) { removeRevocation(key); } } } } if (Utils.isMapNotEmpty(result)) { LOG.info("Revocation token for certificate with Id '{}' has been loaded from the cache", certificateToken.getDSSIdAsString()); } return result; } private RevocationToken getLatestRevocationData(Collection> revocationTokens) { RevocationToken latestRevocationData = null; if (Utils.isCollectionNotEmpty(revocationTokens)) { for (RevocationToken revocationToken : revocationTokens) { if (latestRevocationData == null || (revocationToken.getThisUpdate() != null && latestRevocationData.getThisUpdate().before(revocationToken.getThisUpdate()))) { latestRevocationData = revocationToken; } } } return latestRevocationData; } /** * Extracts a {@link RevocationToken} from the defined proxiedSource and inserts/updates its * in the cache source if required. * * @param certificateToken {@link CertificateToken} to extract the revocation token for * @param issuerCertificateToken {@link CertificateToken} of the issuer * @param keys a collection of {@link String} keys that can be used as unique identifications of the revocation entry * @return {@link RevocationToken} */ private RevocationToken extractAndInsertRevocationTokenFromProxiedSource( final CertificateToken certificateToken, final CertificateToken issuerCertificateToken, final Collection keys) { if (proxiedSource == null) { LOG.warn("Proxied revocation source is not initialized for the called RevocationSource!"); return null; } RevocationToken revocationToken = proxiedSource.getRevocationToken(certificateToken, issuerCertificateToken); if (revocationToken != null) { if (revocationToken.isValid()) { String sourceUrl = getRevocationSourceUrl(certificateToken, revocationToken); if (sourceUrl == null) { LOG.warn("Not able to find revocation source URL for certificate '{}'. Revocation will not be added to the cache", certificateToken.getDSSIdAsString()); return revocationToken; } String revocationTokenKey = getRevocationTokenKey(certificateToken, sourceUrl); if (!keys.contains(revocationTokenKey)) { insertRevocation(revocationTokenKey, revocationToken); LOG.info("Revocation token for certificate '{}' is added into the cache", certificateToken.getDSSIdAsString()); } else { updateRevocation(revocationTokenKey, revocationToken); LOG.info("Revocation token for certificate '{}' is updated in the cache", certificateToken.getDSSIdAsString()); } } else { LOG.warn("The extracted revocation token with Id '{}' is invalid! Reason: {}", revocationToken.getDSSIdAsString(), revocationToken.getInvalidityReason()); } } return revocationToken; } /** * Returns a revocation URL for the given {@code revocationToken} * * @param certificateToken {@link CertificateToken} * @param revocationToken {@link RevocationToken} * @return {@link String} */ protected String getRevocationSourceUrl(CertificateToken certificateToken, RevocationToken revocationToken) { String sourceURL = revocationToken.getSourceURL(); if (sourceURL == null) { List urls = getRevocationAccessUrls(certificateToken); if (urls.size() == 0) { LOG.warn("No revocation distribution points have been found for this certificate Token with ID {} ", certificateToken.getDSSIdAsString()); } else if (urls.size() == 1) { sourceURL = urls.get(0); } else { sourceURL = urls.get(0); LOG.debug("There are multiple revocation distribution points for certificate token with ID {}, " + "the first url will be used as Jdbc revocation source key", certificateToken.getDSSIdAsString()); } } return sourceURL; } /** * Returns a revocation access URLs of the given revocation type for the provided {@code CertificateToken} * * @param certificateToken {@link CertificateToken} to get revocation URLs for * @return a list of {@link String} URLs */ protected abstract List getRevocationAccessUrls(CertificateToken certificateToken); /** * Gets a unique revocation token identifier used to store the revocation token * for this {@code certificateToken} within a repository * * @param certificateToken {@link CertificateToken} * @param urlString {@link String} representing a URL used to download the revocation token from * @return {@link String} revocation token key */ protected abstract String getRevocationTokenKey(CertificateToken certificateToken, String urlString); /** * Checks if the nextUpdate date is currently valid with respect of * nextUpdateDelay and maxNexUpdateDelay parameters. * * @param revocationToken {@code CRLToken} or {@code OCSPToken} * @param certificateTokenIssuer issuer of a CertificateToken to check the revocation for * @return TRUE if the token is still valid, FALSE otherwise */ protected boolean isNotExpired(RevocationToken revocationToken, CertificateToken certificateTokenIssuer) { Date validationDate = new Date(); Date nextUpdate = revocationToken.getNextUpdate(); if (nextUpdate == null) { // check the validity of the issuer certificate CertificateToken revocationIssuer = revocationToken.getIssuerCertificateToken(); if (revocationIssuer == null) { revocationIssuer = certificateTokenIssuer; } if (!revocationIssuer.isValidOn(validationDate)) { return false; } } // check the validity of the revocation token itself final Date thisUpdate = revocationToken.getThisUpdate(); if (nextUpdate == null && defaultNextUpdateDelay != null && thisUpdate != null) { nextUpdate = new Date(thisUpdate.getTime() + defaultNextUpdateDelay); } if (nextUpdate != null) { if (maxNextUpdateDelay != null && thisUpdate != null) { Date maxNextUpdate = new Date(thisUpdate.getTime() + maxNextUpdateDelay); if (nextUpdate.after(maxNextUpdate)) { nextUpdate = maxNextUpdate; } } return nextUpdate.after(validationDate); } return false; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy