/*
* DSS - Digital Signature Services
*
* Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel
*
* Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com
*
* This file is part of the "DSS - Digital Signature Services" project.
*
* "DSS - Digital Signature Services" 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.
*
* DSS 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
* "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>.
*/
package eu.europa.ec.markt.dss.validation102853.processes;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.validation102853.RuleUtils;
import eu.europa.ec.markt.dss.validation102853.TimestampType;
import eu.europa.ec.markt.dss.validation102853.policy.ProcessParameters;
import eu.europa.ec.markt.dss.validation102853.processes.ltv.PastSignatureValidation;
import eu.europa.ec.markt.dss.validation102853.processes.ltv.PastSignatureValidationConclusion;
import eu.europa.ec.markt.dss.validation102853.processes.subprocesses.EtsiPOEExtraction;
import eu.europa.ec.markt.dss.validation102853.rules.AttributeName;
import eu.europa.ec.markt.dss.validation102853.rules.AttributeValue;
import eu.europa.ec.markt.dss.validation102853.rules.ExceptionMessage;
import eu.europa.ec.markt.dss.validation102853.rules.Indication;
import eu.europa.ec.markt.dss.validation102853.rules.MessageTag;
import eu.europa.ec.markt.dss.validation102853.rules.NodeName;
import eu.europa.ec.markt.dss.validation102853.rules.NodeValue;
import eu.europa.ec.markt.dss.validation102853.rules.SubIndication;
import eu.europa.ec.markt.dss.validation102853.xml.XmlDom;
import eu.europa.ec.markt.dss.validation102853.xml.XmlNode;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.ADEST_IMIVC;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.ADEST_IMIVC_ANS;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.PSV_IATVC;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.PSV_IPSVC;
/**
* 9.3 Long Term Validation Process<br>
* <p/>
* 9.3.1 Description<br>
* <p/>
* An AdES-A (Archival Electronic Signature) is built on an XL signature (EXtended Long Electronic Signature). Several
* unsigned attributes may be present in such signatures:<br>
* <p/>
* • Time-stamp(s) on the signature value (AdES-T).<br>
* • Attributes with references of validation data (AdES-C).<br>
* • Time-stamp(s) on the references of validation data (AdES-XT2).<br>
* • Time-stamp(s) on the references of validation data, the signature value and the signature time stamp (AdES-XT1).<br>
* • Attributes with the values of validation data (AdES-XL).<br>
* • Archive time-stamp(s) on the whole signature except the last archive time-stamp (AdES-A).<br>
* <p/>
* The process described in this clause is able to validate any of the forms above but also any basic form (namely BES
* and EPES).<br>
* <p/>
* The process handles the AdES signature as a succession of layers of signatures. Starting from the most external layer
* (e.g. the last archive-time-stamp) to the most inner layer (the signature value to validate), the process performs
* the basic signature validation algorithm (see clause 8 for the signature itself and clause 7 for the time-stamps). If
* the basic validation outputs INDETERMINATE/REVOKED_NO_POE, INDETERMINATE/OUT_OF_BOUNDS_NO_POE or
* INDETERMINATE/CRYPTO_CONSTRAINTS_FAILURE_NO_POE, we perform the past certificate validation which will output a
* control-time in the past. The layer is accepted as VALID, provided we have a proof of existence before this
* control-time.<br>
* <p/>
* The process does not necessarily fail when an intermediate time-stamp gives the status INVALID or INDETERMINATE
* unless some validation constraints force the process to do so. If the validity of the signature can be ascertained
* despite some time-stamps which were ignored due to INVALID (or INDETERLINATE) status, the SVA shall report this
* information to the DA. What the DA does with this information is out of the scope of the present document.
*
* @author bielecro
*/
public class LongTermValidation implements Indication, SubIndication, NodeName, NodeValue, AttributeName, AttributeValue, ExceptionMessage, ValidationXPathQueryHolder {
private static final Logger LOG = LoggerFactory.getLogger(LongTermValidation.class);
ProcessParameters params;
// Primary inputs
/**
* See {@link eu.europa.ec.markt.dss.validation102853.policy.ProcessParameters#getDiagnosticData()}
*
* @return
*/
private XmlDom diagnosticData;
private XmlDom timestampValidationData; // Basic Building Blocks for timestamps
private XmlDom adestValidationData;
// returned data
private XmlNode signatureNode;
private XmlNode conclusionNode;
// This object represents the set of POEs.
private EtsiPOEExtraction poe;
private void prepareParameters(final XmlNode mainNode) {
this.diagnosticData = params.getDiagnosticData();
isInitialised(mainNode);
}
private void isInitialised(final XmlNode mainNode) {
if (diagnosticData == null) {
throw new DSSException(String.format(EXCEPTION_TCOPPNTBI, getClass().getSimpleName(), "diagnosticData"));
}
if (params.getValidationPolicy() == null) {
throw new DSSException(String.format(EXCEPTION_TCOPPNTBI, getClass().getSimpleName(), "validationPolicy"));
}
if (adestValidationData == null) {
/**
* The execution of the Basic Validation process which creates the basic validation data.<br>
*/
final AdESTValidation adestValidation = new AdESTValidation();
adestValidationData = adestValidation.run(mainNode, params);
// Basic Building Blocks for timestamps
timestampValidationData = params.getTsData();
}
if (poe == null) {
poe = new EtsiPOEExtraction();
params.setPOE(poe);
}
}
/**
* This method lunches the long term validation process.
* <p/>
* 9.3.2 Input<br>
* Signature ..................... Mandatory<br>
* Signed data object (s) ........ Optional<br>
* Trusted-status Service Lists .. Optional<br>
* Signature Validation Policies . Optional<br>
* Local configuration ........... Optional<br>
* A set of POEs ................. Optional<br>
* Signer's Certificate .......... Optional<br>
* <p/>
* 9.3.3 Output<br>
* The main output of this signature validation process is a status indicating the validity of the signature. This
* status may be accompanied by additional information (see clause 4).<br>
* <p/>
* 9.3.4 Processing<br>
* The following steps shall be performed:
*
* @param params
* @return
*/
public XmlDom run(final XmlNode mainNode, final ProcessParameters params) {
this.params = params;
prepareParameters(mainNode);
LOG.debug(this.getClass().getSimpleName() + ": start.");
XmlNode longTermValidationData = mainNode.addChild(LONG_TERM_VALIDATION_DATA);
final List<XmlDom> signatures = diagnosticData.getElements("/DiagnosticData/Signature");
for (final XmlDom signature : signatures) {
final String signatureId = signature.getValue("./@Id");
final String type = signature.getValue("./@Type");
if (COUNTERSIGNATURE.equals(type)) {
params.setCurrentValidationPolicy(params.getCountersignatureValidationPolicy());
} else {
params.setCurrentValidationPolicy(params.getValidationPolicy());
}
final XmlDom signatureTimestampValidationData = timestampValidationData.getElement("./Signature[@Id='%s']", signatureId);
final XmlDom adestSignatureValidationData = adestValidationData.getElement("/AdESTValidationData/Signature[@Id='%s']", signatureId);
signatureNode = longTermValidationData.addChild(SIGNATURE);
signatureNode.setAttribute(ID, signatureId);
conclusionNode = new XmlNode(CONCLUSION);
try {
final boolean valid = process(params, signature, signatureTimestampValidationData, adestSignatureValidationData);
if (valid) {
conclusionNode.addFirstChild(INDICATION, VALID);
}
} catch (Exception e) {
LOG.warn("Unexpected exception: " + e.toString(), e);
}
conclusionNode.setParent(signatureNode);
}
final XmlDom ltvDom = longTermValidationData.toXmlDom();
params.setLtvData(ltvDom);
return ltvDom;
}
/**
* 9.3.4 Processing<br>
* <p/>
* The following steps shall be performed:<br>
*
* @param params
* @param signature
* @param signatureTimestampValidationData
* @param adestSignatureValidationData
* @return
*/
private boolean process(ProcessParameters params, XmlDom signature, XmlDom signatureTimestampValidationData, XmlDom adestSignatureValidationData) {
/**
* 1) POE initialisation: Add a POE for each object in the signature at the current time to the set of POEs.<br>
*
* NOTE 1: The set of POE in the input may have been initialised from external sources (e.g. provided from an
* external archiving system). These POEs will be used without additional processing.<br>
*/
// This means that the framework needs to extend the signature (add a LTV timestamp).
// --> This is not done in the 102853 implementation. The DSS user can extend the signature by adding his own
// code.
final List<XmlDom> certificates = params.getCertPool().getElements("./Certificate");
//!! poe.initialisePOE(signature, certificates, params.getCurrentTime());
/**
* 2) Basic signature validation: Perform the validation process for AdES-T signatures (see clause 8) with all the
* inputs, including the processing of any signed attributes/properties as specified.<br>
*/
// --> This is done in the prepareParameters(ProcessParameters params) method.
final XmlDom adestSignatureConclusion = adestSignatureValidationData.getElement("./Conclusion");
final String adestSignatureIndication = adestSignatureConclusion.getValue("./Indication/text()");
final String adestSignatureSubIndication = adestSignatureConclusion.getValue("./SubIndication/text()");
/**
* - If the validation outputs VALID<br>
* - - If there is no validation constraint mandating the validation of the LTV attributes/properties, go to step
* 9.<br>
* - - Otherwise, go to step 3.<br>
* TODO: 20130702 by bielecro: To notify ETSI --> There is no step 9.
*
*/
XmlNode constraintNode = addConstraint(signatureNode, PSV_IATVC);
if (VALID.equals(adestSignatureIndication)) {
constraintNode.addChild(STATUS, OK);
final List<XmlDom> adestInfo = adestSignatureConclusion.getElements("./Info");
constraintNode.addChildren(adestInfo);
conclusionNode.addChildren(adestInfo);
return true;
}
/**
* - If the validation outputs one of the following:<br>
* -- INDETERMINATE/REVOKED_NO_POE,<br>
* -- INDETERMINATE/REVOKED_CA_NO_POE,<br>
* -- INDETERMINATE/OUT_OF_BOUNDS_NO_POE or<br>
* -- INDETERMINATE/CRYPTO_CONSTRAINTS_FAILURE_NO_POE,<br>
* go to the next step.<br>
*
* - In all other cases, fail with returned code and information.<br>
*
* NOTE 2: We go to the LTV part of the validation process in the cases INDETERMINATE/REVOKED_NO_POE,
* INDETERMINATE/REVOKED_CA_NO_POE, INDETERMINATE/OUT_OF_BOUNDS_NO_POE and INDETERMINATE/
* CRYPTO_CONSTRAINTS_FAILURE_NO_POE because additional proof of existences may help to go from INDETERMINATE to a
* determined status.<br>
*
* NOTE 3: Performing the LTV part of the algorithm even when the basic validation gives VALID may be useful in
* the case the SVA is controlled by an archiving service. In such cases, it may be necessary to ensure that any
* LTV attribute/property present in the signature is actually valid before making a decision about the archival
* of the signature.<br>
*/
final boolean finalStatus = INDETERMINATE.equals(adestSignatureIndication) && (RuleUtils
.in(adestSignatureSubIndication, REVOKED_NO_POE, REVOKED_CA_NO_POE, OUT_OF_BOUNDS_NO_POE, CRYPTO_CONSTRAINTS_FAILURE_NO_POE));
if (!finalStatus) {
conclusionNode.addChildrenOf(adestSignatureConclusion);
constraintNode.addChild(STATUS, KO);
constraintNode.addChild(INFO, adestSignatureIndication).setAttribute(FIELD, INDICATION);
constraintNode.addChild(INFO, adestSignatureSubIndication).setAttribute(FIELD, SUB_INDICATION);
return false;
}
constraintNode.addChild(STATUS, OK);
constraintNode.addChild(INFO, adestSignatureIndication).setAttribute(FIELD, INDICATION);
constraintNode.addChild(INFO, adestSignatureSubIndication).setAttribute(FIELD, SUB_INDICATION);
/**
* 3) If there is at least one long-term-validation attribute with a poeValue, process them, starting from the
* last (the newest) one as follows: Perform the time-stamp validation process (see clause 7) for the time-stamp
* in the poeValue:<br>
* a) If VALID is returned and the cryptographic hash function used in the time-stamp
* (MessageImprint.hashAlgorithm) is considered reliable at the generation time of the time-stamp: Perform the POE
* extraction process with the signature, the long-term-validation attribute, the set of POEs and the
* cryptographic constraints as inputs. Add the returned POEs to the set of POEs.<br>
* b) Otherwise, perform past signature validation process with the following inputs: the time-stamp in the
* poeValue, the status/sub-indication returned in step 3, the TSA's certificate, the X.509 validation parameters,
* certificate meta-data, chain constraints, cryptographic constraints and the set of POEs. If it returns VALID
* and the cryptographic hash function used in the time-stamp is considered reliable at the generation time of the
* time-stamp, perform the POE extraction process and add the returned POEs to the set of POEs. In all other
* cases:<br>
* If no specific constraints mandating the validity of the attribute are specified in the validation
* constraints, ignore the attribute and consider the next long-term-validation attribute.<br>
* Otherwise, fail with the returned indication/sub-indication and associated explanations<br>
*/
// TODO 20130702 by bielecro: This must be implemented with the new CAdES Baseline Profile.
// This is the part of the new CAdES specification:
// http://www.etsi.org/deliver/etsi_ts/101700_101799/101733/02.01.01_60/ts_101733v020101p.pdf
/**
* 4) If there is at least one archive-time-stamp attribute, process them, starting from the last (the newest)
* one, as follows: perform the time-stamp validation process (see clause 7):
*/
final XmlNode archiveTimestampsNode = signatureNode.addChild("ArchiveTimestamps");
final List<XmlDom> archiveTimestamps = signature.getElements("./Timestamps/Timestamp[@Type='%s']", TimestampType.ARCHIVE_TIMESTAMP);
if (archiveTimestamps.size() > 0) {
dealWithTimestamp(archiveTimestampsNode, signatureTimestampValidationData, archiveTimestamps);
}
/**
* 5) If there is at least one time-stamp attribute on the references, process them, starting from the last one
* (the newest), as follows: perform the time-stamp validation process (see clause 7):<br>
*/
final XmlNode refsOnlyTimestampsNode = signatureNode.addChild("RefsOnlyTimestamps");
final List<XmlDom> refsOnlyTimestamps = signature.getElements("./Timestamps/Timestamp[@Type='%s']", TimestampType.VALIDATION_DATA_REFSONLY_TIMESTAMP);
if (refsOnlyTimestamps.size() > 0) {
dealWithTimestamp(refsOnlyTimestampsNode, signatureTimestampValidationData, refsOnlyTimestamps);
}
/**
* 6) If there is at least one time-stamp attribute on the references and the signature value, process them,
* starting from the last one, as follows: perform the time-stamp validation process (see clause 7):<br>
*/
final XmlNode sigAndRefsTimestampsNode = signatureNode.addChild("SigAndRefsTimestamps");
final List<XmlDom> sigAndRefsTimestamps = signature.getElements("./Timestamps/Timestamp[@Type='%s']", TimestampType.VALIDATION_DATA_TIMESTAMP);
if (sigAndRefsTimestamps.size() > 0) {
dealWithTimestamp(sigAndRefsTimestampsNode, signatureTimestampValidationData, sigAndRefsTimestamps);
}
/**
* 7) If there is at least one signature-time-stamp attribute, process them, in the order of their appearance
* starting from the last one, as follows: Perform the time-stamp validation process (see clause 7)<br>
*/
final XmlNode timestampsNode = signatureNode.addChild("Timestamps");
final List<XmlDom> timestamps = signature.getElements("./Timestamps/Timestamp[@Type='%s']", TimestampType.SIGNATURE_TIMESTAMP);
if (timestamps.size() > 0) {
dealWithTimestamp(timestampsNode, signatureTimestampValidationData, timestamps);
}
/**
* 8) Past signature validation: perform the past signature validation process with the following inputs: the
* signature, the status indication/sub-indication returned in step 2, the signer's certificate, the x.509
* validation parameters, certificate meta-data, chain constraints, cryptographic constraints and the set of POEs.
*/
final PastSignatureValidation pastSignatureValidation = new PastSignatureValidation();
final PastSignatureValidationConclusion psvConclusion = pastSignatureValidation.run(params, signature, adestSignatureConclusion, MAIN_SIGNATURE);
signatureNode.addChild(psvConclusion.getValidationData());
/**
* If it returns VALID go to the next step. Otherwise, abort with the returned indication/sub-indication and
* associated explanations.<br>
*/
constraintNode = addConstraint(signatureNode, PSV_IPSVC);
if (!VALID.equals(psvConclusion.getIndication())) {
constraintNode.addChild(STATUS, KO);
constraintNode.addChild(INFO, psvConclusion.getIndication()).setAttribute(FIELD, INDICATION);
constraintNode.addChild(INFO, psvConclusion.getSubIndication()).setAttribute(FIELD, SUB_INDICATION);
psvConclusion.infoToXmlNode(constraintNode);
conclusionNode.addChild(INDICATION, psvConclusion.getIndication());
conclusionNode.addChild(SUB_INDICATION, psvConclusion.getSubIndication());
psvConclusion.infoToXmlNode(conclusionNode);
return false;
}
constraintNode.addChild(STATUS, OK);
/**
* Data extraction: the SVA shall return the success indication VALID. In addition, the SVA should return
* additional information extracted from the signature and/or used by the intermediate steps. In particular, the
* SVA should return intermediate results such as the validation results of any time-stamp token or time-mark.
* What the DA does with this information is out of the scope of the present document.<br>
*/
return true;
}
/**
* @param processNode
* @param signatureTimestampValidationData
* @param timestamps
* @throws eu.europa.ec.markt.dss.exception.DSSException
*/
private void dealWithTimestamp(final XmlNode processNode, final XmlDom signatureTimestampValidationData, final List<XmlDom> timestamps) throws DSSException {
Collections.sort(timestamps, new TimestampComparator());
for (final XmlDom timestamp : timestamps) {
final String timestampId = timestamp.getValue("./@Id");
try {
/**
* FROM ADES-T (ETSI error ?!)
* 4) Signature time-stamp validation: Perform the following steps:
*
* a) Message imprint verification: For each time-stamp token in the set of signature time-stamp tokens, do the
* message imprint verification as specified in clauses 8.4.1 or 8.4.2 depending on the type of the signature.
* If the verification fails, remove the token from the set.
*/
XmlNode constraintNode = addConstraint(processNode, ADEST_IMIVC);
// constraintNode.setAttribute("Id", timestampId);
final boolean messageImprintDataIntact = timestamp.getBoolValue(XP_MESSAGE_IMPRINT_DATA_INTACT);
if (!messageImprintDataIntact) {
constraintNode.addChild(STATUS, KO);
XmlNode xmlNode = conclusionNode.addChild(INFO, ADEST_IMIVC_ANS.getMessage());
xmlNode.setAttribute("Id", timestampId);
continue;
}
constraintNode.addChild(STATUS, OK);
final XmlDom timestampConclusion = signatureTimestampValidationData.getElement("./Timestamp[@Id='%s']/BasicBuildingBlocks/Conclusion", timestampId);
final String timestampIndication = timestampConclusion.getValue("./Indication/text()");
/**
* a) If VALID is returned and the cryptographic hash function used in the time-stamp
* (MessageImprint.hashAlgorithm) is considered reliable at the generation time of the time-stamp: Perform
* the POE extraction process with:<br>
* - the signature,<br>
* - the archive-time-stamp,<br>
* - the set of POEs and<br>
* - the cryptographic constraints as inputs.<br>
* Add the returned POEs to the set of POEs.
*/
if (VALID.equals(timestampIndication)) {
processNode.addChild("POEExtraction", OK);
extractPOEs(timestamp);
} else {
/**
* b) Otherwise, perform past signature validation process with the following inputs:<br>
* - the archive time-stamp,<br>
* - the status/sub-indication returned in step 4,<br>
* - the TSA's certificate,<br>
* - the X.509 validation parameters,<br>
* - certificate meta-data, <br>
* - chain constraints,<br>
* - cryptographic constraints and<br>
* - the set of POEs.
*/
final PastSignatureValidation psvp = new PastSignatureValidation();
final PastSignatureValidationConclusion psvConclusion = psvp.run(params, timestamp, timestampConclusion, TIMESTAMP);
processNode.addChild(psvConclusion.getValidationData());
/**
* If it returns VALID and the cryptographic hash function used in the time-stamp is considered reliable
* at the generation time of the time-stamp, perform the POE extraction process and add the returned POEs
* to the set of POEs.
*/
if (VALID.equals(psvConclusion.getIndication())) {
final boolean couldExtract = extractPOEs(timestamp);
if (couldExtract) {
continue;
}
}
/**
* In all other cases:<br>
* If no specific constraints mandating the validity of the attribute are specified in the validation
* constraints, ignore the attribute and consider the next archive-time-stamp attribute.<br>
*/
/**
* --> Concerning DSS there is no specific constraints.
*/
/**
* Otherwise, fail with the returned indication/sub-indication and associated explanations.<br>
*
* NOTE 4: If the signature is PAdES, document time-stamps replace archive-time-stamp attributes and the
* process "Extraction from a PDF document time-stamp" replaces the process
* "Extraction from an archive-time-stamp".<br>
*/
}
} catch (Exception e) {
throw new DSSException("Error for timestamp: id: " + timestampId, e);
}
}
}
/**
* @param timestamp
* @return
* @throws eu.europa.ec.markt.dss.exception.DSSException
*/
private boolean extractPOEs(final XmlDom timestamp) throws DSSException {
final String digestAlgorithm = RuleUtils.canonicalizeDigestAlgo(timestamp.getValue("./SignedDataDigestAlgo/text()"));
final Date algorithmExpirationDate = params.getCurrentValidationPolicy().getAlgorithmExpirationDate(digestAlgorithm);
final Date timestampProductionTime = timestamp.getTimeValue("./ProductionTime/text()");
if (algorithmExpirationDate == null || timestampProductionTime.before(algorithmExpirationDate)) {
poe.addPOE(timestamp, params.getCertPool());
return true;
}
return false;
}
/**
* This method adds the constraint
*
* @param parentNode
* @param messageTag
* @return
*/
private XmlNode addConstraint(final XmlNode parentNode, final MessageTag messageTag) {
final XmlNode constraintNode = parentNode.addChild(CONSTRAINT);
constraintNode.addChild(NAME, messageTag.getMessage()).setAttribute(NAME_ID, messageTag.name());
return constraintNode;
}
}