/**
* DSS - Digital Signature Services
* Copyright (C) 2015 European Commission, provided under the CEF programme
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
*/
package eu.europa.esig.dss.validation;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Constructor;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSDocument;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.jaxb.diagnostic.DiagnosticData;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.validation.executor.CustomProcessExecutor;
import eu.europa.esig.dss.validation.executor.ProcessExecutor;
import eu.europa.esig.dss.validation.executor.ValidationLevel;
import eu.europa.esig.dss.validation.policy.Context;
import eu.europa.esig.dss.validation.policy.EtsiValidationPolicy;
import eu.europa.esig.dss.validation.policy.ValidationPolicy;
import eu.europa.esig.dss.validation.reports.Reports;
import eu.europa.esig.dss.x509.CertificatePool;
import eu.europa.esig.dss.x509.CertificateSourceType;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.dss.x509.crl.ListCRLSource;
import eu.europa.esig.dss.x509.ocsp.ListOCSPSource;
import eu.europa.esig.jaxb.policy.ConstraintsParameters;
/**
* Validate the signed document. The content of the document is determined
* automatically. It can be: XML, CAdES(p7m), PDF or ASiC(zip).
* SignatureScopeFinder can be set using the appropriate setter (ex.
* setCadesSignatureScopeFinder). By default, this class will use the default
* SignatureScopeFinder as defined by
* eu.europa.esig.dss.validation.scope.SignatureScopeFinderFactory
*/
public abstract class SignedDocumentValidator implements DocumentValidator {
private static final Logger LOG = LoggerFactory.getLogger(SignedDocumentValidator.class);
/**
* This variable can hold a specific {@code ProcessExecutor}
*/
protected ProcessExecutor processExecutor = null;
/**
* This is the pool of certificates used in the validation process. The
* pools present in the certificate verifier are merged and added to this
* pool.
*/
protected CertificatePool validationCertPool = null;
/**
* The document to validated (with the signature(s))
*/
protected DSSDocument document;
/**
* In case of a detached signature this {@code List} contains the signed
* documents.
*/
protected List<DSSDocument> detachedContents = new ArrayList<DSSDocument>();
protected CertificateToken providedSigningCertificateToken = null;
/**
* The reference to the certificate verifier. The current DSS implementation
* proposes {@link eu.europa.esig.dss.validation.CommonCertificateVerifier}.
* This verifier encapsulates the references to different sources used in
* the signature validation process.
*/
protected CertificateVerifier certificateVerifier;
protected final SignatureScopeFinder signatureScopeFinder;
protected SignaturePolicyProvider signaturePolicyProvider;
// Default configuration with the highest level
private ValidationLevel validationLevel = ValidationLevel.ARCHIVAL_DATA;
private static List<Class<SignedDocumentValidator>> registredDocumentValidators = new ArrayList<Class<SignedDocumentValidator>>();
static {
Properties properties = new Properties();
try {
properties.load(SignedDocumentValidator.class.getResourceAsStream("/document-validators.properties"));
} catch (IOException e) {
LOG.error("Cannot load properties from document-validators.properties : " + e.getMessage(), e);
}
for (String propName : properties.stringPropertyNames()) {
registerDocumentValidator(propName, properties.getProperty(propName));
}
}
private static void registerDocumentValidator(String type, String clazzToFind) {
try {
@SuppressWarnings("unchecked")
Class<SignedDocumentValidator> documentValidator = (Class<SignedDocumentValidator>) Class.forName(clazzToFind);
registredDocumentValidators.add(documentValidator);
LOG.info("Validator '" + documentValidator.getName() + "' is registred");
} catch (ClassNotFoundException e) {
LOG.warn("Validator not found for signature type " + type);
}
}
protected SignedDocumentValidator(SignatureScopeFinder signatureScopeFinder) {
this.signatureScopeFinder = signatureScopeFinder;
}
/**
* This method guesses the document format and returns an appropriate
* document validator.
*
* @param dssDocument
* The instance of {@code DSSDocument} to validate
* @return returns the specific instance of SignedDocumentValidator in terms
* of the document type
*/
public static SignedDocumentValidator fromDocument(final DSSDocument dssDocument) {
if (Utils.isCollectionEmpty(registredDocumentValidators)) {
throw new DSSException("No validator registred");
}
for (Class<SignedDocumentValidator> clazz : registredDocumentValidators) {
try {
Constructor<SignedDocumentValidator> defaultAndPrivateConstructor = clazz.getDeclaredConstructor();
defaultAndPrivateConstructor.setAccessible(true);
SignedDocumentValidator validator = defaultAndPrivateConstructor.newInstance();
if (validator.isSupported(dssDocument)) {
Constructor<? extends SignedDocumentValidator> constructor = clazz.getDeclaredConstructor(DSSDocument.class);
return constructor.newInstance(dssDocument);
}
} catch (Exception e) {
LOG.error("Cannot instanciate class '" + clazz.getName() + "' : " + e.getMessage(), e);
}
}
throw new DSSException("Document format not recognized/handled");
}
public abstract boolean isSupported(DSSDocument dssDocument);
@Override
public void defineSigningCertificate(final CertificateToken token) {
if (token == null) {
throw new NullPointerException("Token is not defined");
}
if (validationCertPool == null) {
throw new NullPointerException("Certificate pool is not instantiated");
}
providedSigningCertificateToken = validationCertPool.getInstance(token, CertificateSourceType.OTHER);
}
/**
* To carry out the validation process of the signature(s) some external
* sources of certificates and of revocation data can be needed. The
* certificate verifier is used to pass these values. Note that once this
* setter is called any change in the content of the
* <code>CommonTrustedCertificateSource</code> or in adjunct certificate
* source is not taken into account.
*
* @param certificateVerifier
*/
@Override
public void setCertificateVerifier(final CertificateVerifier certificateVerifier) {
this.certificateVerifier = certificateVerifier;
if (validationCertPool == null) {
validationCertPool = certificateVerifier.createValidationPool();
}
}
@Override
public void setDetachedContents(final List<DSSDocument> detachedContents) {
this.detachedContents = detachedContents;
}
/**
* This method allows to specify the validation level (Basic / Timestamp /
* Long Term / Archival). By default, the selected validation is ARCHIVAL
*/
@Override
public void setValidationLevel(ValidationLevel validationLevel) {
this.validationLevel = validationLevel;
}
@Override
public Reports validateDocument() {
return validateDocument((InputStream) null);
}
@Override
public Reports validateDocument(final URL validationPolicyURL) {
if (validationPolicyURL == null) {
return validateDocument((InputStream) null);
}
try {
return validateDocument(validationPolicyURL.openStream());
} catch (IOException e) {
throw new DSSException(e);
}
}
@Override
public Reports validateDocument(final String policyResourcePath) {
if (policyResourcePath == null) {
return validateDocument((InputStream) null);
}
return validateDocument(getClass().getResourceAsStream(policyResourcePath));
}
@Override
public Reports validateDocument(final File policyFile) {
if ((policyFile == null) || !policyFile.exists()) {
return validateDocument((InputStream) null);
}
final InputStream inputStream = DSSUtils.toByteArrayInputStream(policyFile);
return validateDocument(inputStream);
}
/**
* Validates the document and all its signatures. The policyDataStream
* contains the constraint file. If null or empty the default file is used.
*
* @param policyDataStream
* {@code InputStream}
*/
@Override
public Reports validateDocument(final InputStream policyDataStream) {
final ConstraintsParameters validationPolicyJaxb = ValidationResourceManager.loadPolicyData(policyDataStream);
return validateDocument(validationPolicyJaxb);
}
/**
* Validates the document and all its signatures. The
* {@code validationPolicyDom} contains the constraint file. If null or
* empty the default file is used.
*
* @param validationPolicyDom
* {@code Document}
* @return
*/
@Override
public Reports validateDocument(final ConstraintsParameters validationPolicyJaxb) {
final ValidationPolicy validationPolicy = new EtsiValidationPolicy(validationPolicyJaxb);
return validateDocument(validationPolicy);
}
/**
* Validates the document and all its signatures. The
* {@code validationPolicyDom} contains the constraint file. If null or
* empty the default file is used.
*
* @param validationPolicy
* {@code ValidationPolicy}
* @return
*/
@Override
public Reports validateDocument(final ValidationPolicy validationPolicy) {
LOG.info("Document validation...");
if (certificateVerifier == null) {
throw new NullPointerException("CertificateVerifier not defined");
}
ensureSignaturePolicyDetectorInitialized();
boolean structuralValidation = isRequireStructuralValidation(validationPolicy);
final ValidationContext validationContext = new SignatureValidationContext(validationCertPool);
List<AdvancedSignature> allSignatureList = processSignaturesValidation(validationContext, structuralValidation);
DiagnosticDataBuilder builder = new DiagnosticDataBuilder();
builder.document(document).containerInfo(getContainerInfo()).foundSignatures(allSignatureList)
.usedCertificates(validationContext.getProcessedCertificates()).trustedListsCertificateSource(certificateVerifier.getTrustedCertSource())
.validationDate(validationContext.getCurrentTime());
return processValidationPolicy(builder.build(), validationPolicy);
}
@Override
public List<AdvancedSignature> processSignaturesValidation(final ValidationContext validationContext, boolean structuralValidation) {
final List<AdvancedSignature> allSignatureList = getAllSignatures();
// The list of all signing certificates is created to allow a parallel
// validation.
prepareCertificatesAndTimestamps(allSignatureList, validationContext);
final ListCRLSource signatureCRLSource = getSignatureCrlSource(allSignatureList);
certificateVerifier.setSignatureCRLSource(signatureCRLSource);
final ListOCSPSource signatureOCSPSource = getSignatureOcspSource(allSignatureList);
certificateVerifier.setSignatureOCSPSource(signatureOCSPSource);
validationContext.setCurrentTime(provideProcessExecutorInstance().getCurrentTime());
validationContext.initialize(certificateVerifier);
validationContext.validate();
for (final AdvancedSignature signature : allSignatureList) {
signature.checkSigningCertificate();
signature.checkSignatureIntegrity();
signature.validateTimestamps();
if (structuralValidation) {
signature.validateStructure();
}
signature.checkSignaturePolicy(signaturePolicyProvider);
if (signatureScopeFinder != null) {
signature.findSignatureScope(signatureScopeFinder);
}
}
return allSignatureList;
}
/**
* This method allows to retrieve the container information (ASiC Container)
*
* @return
*/
protected ContainerInfo getContainerInfo() {
return null;
}
protected Reports processValidationPolicy(DiagnosticData diagnosticData, ValidationPolicy validationPolicy) {
final ProcessExecutor executor = provideProcessExecutorInstance();
executor.setValidationPolicy(validationPolicy);
executor.setValidationLevel(validationLevel);
executor.setDiagnosticData(diagnosticData);
final Reports reports = executor.execute();
return reports;
}
@Override
public void setSignaturePolicyProvider(SignaturePolicyProvider signaturePolicyProvider) {
this.signaturePolicyProvider = signaturePolicyProvider;
}
protected void ensureSignaturePolicyDetectorInitialized() {
if (signaturePolicyProvider == null) {
signaturePolicyProvider = new SignaturePolicyProvider();
signaturePolicyProvider.setDataLoader(certificateVerifier.getDataLoader());
}
}
@Override
public void setProcessExecutor(final ProcessExecutor processExecutor) {
this.processExecutor = processExecutor;
}
/**
* This method returns the process executor. If the instance of this class
* is not yet instantiated then the new instance is created.
*
* @return {@code ProcessExecutor}
*/
public ProcessExecutor provideProcessExecutorInstance() {
if (processExecutor == null) {
processExecutor = new CustomProcessExecutor();
}
return processExecutor;
}
/**
* This method returns the list of all signatures including the
* countersignatures.
*
* @return {@code List} of {@code AdvancedSignature} to validate
*/
private List<AdvancedSignature> getAllSignatures() {
final List<AdvancedSignature> allSignatureList = new ArrayList<AdvancedSignature>();
List<AdvancedSignature> signatureList = getSignatures();
for (final AdvancedSignature signature : signatureList) {
allSignatureList.add(signature);
allSignatureList.addAll(signature.getCounterSignatures());
}
return allSignatureList;
}
/**
* For all signatures to be validated this method merges the CRL sources.
*
* @param allSignatureList
* {@code List} of {@code AdvancedSignature}s to validate
* including the countersignatures
* @return {@code ListCRLSource}
*/
private ListCRLSource getSignatureCrlSource(final List<AdvancedSignature> allSignatureList) {
final ListCRLSource signatureCrlSource = new ListCRLSource();
for (final AdvancedSignature signature : allSignatureList) {
signatureCrlSource.addAll(signature.getCRLSource());
}
return signatureCrlSource;
}
/**
* For all signatures to be validated this method merges the OCSP sources.
*
* @param allSignatureList
* {@code List} of {@code AdvancedSignature}s to validate
* including the countersignatures
* @return {@code ListOCSPSource}
*/
private ListOCSPSource getSignatureOcspSource(final List<AdvancedSignature> allSignatureList) {
final ListOCSPSource signatureOcspSource = new ListOCSPSource();
for (final AdvancedSignature signature : allSignatureList) {
signatureOcspSource.addAll(signature.getOCSPSource());
}
return signatureOcspSource;
}
/**
* @param allSignatureList
* {@code List} of {@code AdvancedSignature}s to validate
* including the countersignatures
* @param validationContext
* {@code ValidationContext} is the implementation of the
* validators for: certificates, timestamps and revocation data.
*/
private void prepareCertificatesAndTimestamps(final List<AdvancedSignature> allSignatureList, final ValidationContext validationContext) {
for (final AdvancedSignature signature : allSignatureList) {
final List<CertificateToken> candidates = signature.getCertificateSource().getCertificates();
for (final CertificateToken certificateToken : candidates) {
validationContext.addCertificateTokenForVerification(certificateToken);
}
signature.prepareTimestamps(validationContext);
}
}
private boolean isRequireStructuralValidation(ValidationPolicy validationPolicy) {
return ((validationPolicy != null) && (validationPolicy.getStructuralValidationConstraint(Context.SIGNATURE) != null));
}
}