/*
* 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.ltv;
import java.util.Date;
import java.util.List;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.validation102853.RuleUtils;
import eu.europa.ec.markt.dss.validation102853.policy.ProcessParameters;
import eu.europa.ec.markt.dss.validation102853.policy.ValidationPolicy;
import eu.europa.ec.markt.dss.validation102853.processes.ValidationXPathQueryHolder;
import eu.europa.ec.markt.dss.validation102853.processes.dss.InvolvedServiceInfo;
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.RuleConstant;
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.engine.rules.wrapper.XPathSignature.getCertificateId;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.BBB_XCV_IRIF_ANS;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_DRIE;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_DSOPCPOEOC;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_ICNEAIDORSI;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_IIDORSIBCT;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_SCT;
import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.CTS_WITSS;
import static eu.europa.ec.markt.dss.validation102853.toolbox.Reversed.reversed;
/**
* 9.2.2 Control-time sliding process<br>
* <p/>
* 9.2.2.1 Description<br>
* <p/>
* This process will slide the control-time from the current-time to some date in the past each time it encounters a
* certificate proven to be revoked.
*
* @author bielecro
* <p/>
* // Summary:<br>
* // - When the service status is not UNDERSUPERVISION then the closing date of this status need to be found.
* The class which handle this information need to be updated.<br>
* // The CRL extension expiredCertOnCRL should be taken into account during the revocation information
* retrieval.<br>
* // NOTE 4 is not completely taken into account.<br>
* // --> Closed
*/
public class ControlTimeSliding implements Indication, SubIndication, NodeName, NodeValue, AttributeName, AttributeValue, RuleConstant, ExceptionMessage, ValidationXPathQueryHolder {
private static final org.slf4j.Logger LOG = org.slf4j.LoggerFactory.getLogger(PastCertificateValidation.class);
private ValidationPolicy constraintData;
private Date controlTime;
private EtsiPOEExtraction poe;
// returned data
private XmlNode controlTimeSlidingData;
private void prepareParameters(final ProcessParameters params) {
this.constraintData = params.getCurrentValidationPolicy();
this.poe = (EtsiPOEExtraction) params.getPOE();
isInitialised(params);
}
private void isInitialised(final ProcessParameters params) {
if (poe == null) {
poe = new EtsiPOEExtraction();
params.setPOE(poe);
}
}
/**
* 9.2.2.4 Processing<br>
* <p/>
* The following steps shall be performed:<br>
*
* @param params
*/
public ControlTimeSlidingConclusion run(final ProcessParameters params, final XmlDom certificateChain) {
prepareParameters(params);
LOG.debug(this.getClass().getSimpleName() + ": start.");
controlTimeSlidingData = new XmlNode(CONTROL_TIME_SLIDING_DATA);
/**
* 1) Initialise control-time to the current date/time.<br>
*/
// The control-time is re-initialised at every turn
controlTime = params.getCurrentTime();
final ControlTimeSlidingConclusion conclusion = process(params, certificateChain);
conclusion.setControlTime(controlTime);
conclusion.setValidationData(controlTimeSlidingData);
return conclusion;
}
/**
* @param params
* @param certificateChain
* @return
*/
private ControlTimeSlidingConclusion process(final ProcessParameters params, final XmlDom certificateChain) {
final ControlTimeSlidingConclusion conclusion = new ControlTimeSlidingConclusion();
final int signingCertificateId = certificateChain.getIntValue("./ChainCertificate[1]/@Id");
final List<XmlDom> chainCertificates = certificateChain.getElements("./ChainCertificate");
/**
* 2) For each certificate in the chain starting from the first certificate (the certificate issued by the trust
* anchor), do the following:<br>
*/
for (final XmlDom chainCertificate : reversed(chainCertificates)) {
final int certificateId = getCertificateId(chainCertificate);
final XmlNode certificateNode = controlTimeSlidingData.addChild(CERTIFICATE, DSSUtils.EMPTY);
certificateNode.setAttribute(CERTIFICATE_ID, String.valueOf(certificateId));
final XmlDom certificate = params.getCertificate(certificateId);
final boolean isTrusted = certificate.getBoolValue("./Trusted/text()");
if (isTrusted) {
continue;
}
if (signingCertificateId == certificateId) {
/**
* (See NOTE 1) Concerning the trust anchor, it must be checked if it is still trusted at the current
* date/time. Other checks are not necessary.<br>
*/
final XmlNode constraintNode = addConstraint(CTS_WITSS);
final String status = InvolvedServiceInfo.getStatus(certificate);
constraintNode.addChild(STATUS, OK);
constraintNode.addChild(INFO).setAttribute(TRUSTED_SERVICE_STATUS, status);
final boolean underSupervision = InvolvedServiceInfo.isSERVICE_STATUS_UNDERSUPERVISION(status);
final boolean supervisionInCessation = InvolvedServiceInfo.isSERVICE_STATUS_SUPERVISIONINCESSATION(status);
final boolean accredited = InvolvedServiceInfo.isSERVICE_STATUS_ACCREDITED(status);
if (!underSupervision && !supervisionInCessation && !accredited) {
/**
* ...where the trust anchor is broken at a known date by initialising control-time to this date/time.<br>
*/
if (status.isEmpty()) {
// Trusted service is unknown
final String serviceName = InvolvedServiceInfo.getServiceName(certificate);
LOG.warn("The status of the service is unknown: (serviceName: " + serviceName + ")");
} else {
final Date statusEndDate = InvolvedServiceInfo.getEndDate(certificate);
controlTime = statusEndDate;
addControlTime(constraintNode);
}
}
}
/**
* - a) Find revocation status information satisfying the following:<br>
* - - The revocation status information is consistent with the rules conditioning its use to check the
* revocation status of the considered certificate. For instance, in the case of a CRL, it shall satisfy the
* checks described in (see clause 6.3).<br>
*
* TODO: 20130704 by bielecro: To notify ETSI --> (see clause 6.3) is not the right clause.<br>
*/
XmlNode constraintNode = addConstraint(CTS_DRIE);
final boolean revocationExists = certificate.exists("./Revocation");
if (!revocationExists) {
constraintNode.addChild(STATUS, KO);
conclusion.setIndication(INDETERMINATE);
conclusion.setSubIndication(NO_POE);
return conclusion;
}
final Date revocationIssuingTime = certificate.getTimeValue("./Revocation/IssuingTime/text()");
final String formatedRevocationIssuingTime = DSSUtils.formatDate(revocationIssuingTime);
constraintNode.addChild(STATUS, OK);
constraintNode.addChild(INFO).setAttribute(REVOCATION_ISSUING_TIME, formatedRevocationIssuingTime);
final Date notAfterTime = certificate.getTimeValue("./NotAfter/text()");
final Date notBeforeTime = certificate.getTimeValue("./NotBefore/text()");
/**
* (See NOTE 2)<br>
* TODO: ...(for instance, using the CRL extension expiredCertOnCRL (OID: 2.5.29.60)) This check need to be
* added to the revocation information retrieval.
*/
constraintNode = addConstraint(CTS_ICNEAIDORSI);
if (revocationIssuingTime.before(notBeforeTime) || revocationIssuingTime.after(notAfterTime)) {
constraintNode.addChild(STATUS, KO);
conclusion.setIndication(INDETERMINATE);
conclusion.setSubIndication(NO_POE);
return conclusion;
}
constraintNode.addChild(STATUS, OK);
/**
* - - The issuance date of the revocation status information is before control-time. If more than one
* revocation status is found, consider the most recent one and go to the next step. If there is no such
* information, terminate with INDETERMINATE/NO_POE:<br>
*/
constraintNode = addConstraint(CTS_IIDORSIBCT);
if (!revocationIssuingTime.before(controlTime)) {
constraintNode.addChild(STATUS, KO);
addControlTime(constraintNode);
conclusion.setIndication(INDETERMINATE);
conclusion.setSubIndication(NO_POE);
return conclusion;
}
constraintNode.addChild(STATUS, OK);
/**
* - b) If the set of POEs contains a proof of existence of the certificate and the revocation status
* information at (or before) control-time, go to step c). Otherwise, terminate with INDETERMINATE/NO_POE.
*/
constraintNode = addConstraint(CTS_DSOPCPOEOC);
final boolean poeExists = poe.getCertificatePOE(certificateId, controlTime);
if (!poeExists || revocationIssuingTime.compareTo(controlTime) > 0) {
constraintNode.addChild(STATUS, KO);
conclusion.setIndication(INDETERMINATE);
conclusion.setSubIndication(NO_POE);
return conclusion;
}
constraintNode.addChild(STATUS, OK);
/**
* - c) Update the value of control-time as follows:<br>
*/
constraintNode = addConstraint(CTS_SCT);
addControlTime(constraintNode);
final boolean revoked = !certificate.getBoolValue("./Revocation/Status/text()");
/**
* - - If the certificate is marked as revoked in the revocation status information, set control-time to the
* revocation date.<br>
*/
if (revoked) {
final Date revocationDate = certificate.getTimeValue("./Revocation/DateTime/text()");
controlTime = revocationDate;
final String formatedRevocationDate = DSSUtils.formatDate(revocationDate);
constraintNode.addChild(INFO, CTS_CTSTRT_LABEL);
constraintNode.addChild(INFO).setAttribute(REVOCATION_TIME, formatedRevocationDate);
} else {
/**
* - - If the certificate is not marked as revoked.<br>
* - - - - If the revocation status information is not considered "fresh", set control-time to the issuance
* date of the revocation status information.<br>
*/
final long revocationDeltaTime = controlTime.getTime() - revocationIssuingTime.getTime();
if (revocationDeltaTime > constraintData.getMaxRevocationFreshness()) {
controlTime = revocationIssuingTime;
constraintNode.addChild(INFO, CTS_CTSTRIT_LABEL);
final XmlNode xmlNode = constraintNode.addChild(INFO, BBB_XCV_IRIF_ANS);
xmlNode.setAttribute(CERTIFICATE_ID, String.valueOf(certificateId)).setAttribute(REVOCATION_ISSUING_TIME, formatedRevocationIssuingTime);
}
/**
* - - - - Otherwise, the value of control-time is not changed.<br>
*/
}
/**
* - d) Apply the cryptographic constraints to the certificate and the revocation status information. If the
* certificate (or the revocation status information) does not match these constraints, set control-time to the
* lowest time up to which the listed algorithms were considered reliable.<br>
*/
checkDigestAlgoExpirationDate(certificate, constraintNode, CTS_CTSTETOCSA_LABEL);
checkEncryptionAlgoExpirationDate(certificate, constraintNode, CTS_CTSTETOCSA_LABEL);
final XmlDom revocation = certificate.getElement("./Revocation");
checkDigestAlgoExpirationDate(revocation, constraintNode, CTS_CTSTETORSA_LABEL);
checkEncryptionAlgoExpirationDate(revocation, constraintNode, CTS_CTSTETORSA_LABEL);
/**
* 3) Continue with the next certificate in the chain or, if no further certificate exists, terminate with
* VALID and the calculated control-time.<br>
*/
}
/**
* NOTE 1: In step 1, initialising control-time with current date/time assumes that the trust anchor is still
* trusted at the current date/time. The algorithm can capture the very exotic case where the trust anchor is
* broken (or becomes untrusted for any other reason) at a known date by initialising control-time to this
* date/time.<br>
*
* NOTE 2: The rational of step 2-a) is to check that the revocation status information is "in-scope" for the
* given certificate. In other words, the rationale is to check that the revocation status information is reliable
* to be used to ascertain the revocation status of the given certificate. For instance, this includes the fact
* the certificate is not expired at the issuance date of the revocation status information, unless the issuing CA
* states that its issues revocation information status for expired certificates (for instance, using the CRL
* extension expiredCertOnCRL).<br>
*
* NOTE 3: If the certificate (or the revocation status information) was authentic, but the signature has been
* faked exploiting weaknesses of the algorithms used, this is assumed only to be possible after the date the
* algorithms are declared to be no longer acceptable. Therefore, the owner of the original key pair is assumed to
* having been under control of his key up to that date. This is the rational of sliding control-time in step
* 2-d).<br>
*
* NOTE 4: For more readability, the algorithm above implicitly assumes that the revocation information status is
* signed by the certificate's issuer which is the most traditional revocation setting but not the only one. The
* same algorithm can be adapted to the cases where the revocation information status has its own certificate
* chain by applying the control-time sliding process to this chain which would output a control-time that has to
* be compared to the control-time associated to the certificate.
*/
conclusion.setIndication(VALID);
conclusion.addInfo().setAttribute(CONTROL_TIME, DSSUtils.formatDate(controlTime));
return conclusion;
}
/**
* @param constraintNode
*/
private void addControlTime(XmlNode constraintNode) {
String formatedControlTime = DSSUtils.formatDate(controlTime);
constraintNode.addChild(INFO).setAttribute(CONTROL_TIME, formatedControlTime);
}
/**
* @param messageTag
* @return
*/
private XmlNode addConstraint(final MessageTag messageTag) {
XmlNode constraintNode = controlTimeSlidingData.addChild(CONSTRAINT);
constraintNode.addChild(NAME, messageTag.getMessage()).setAttribute(NAME_ID, messageTag.name());
return constraintNode;
}
private void checkEncryptionAlgoExpirationDate(final XmlDom token, final XmlNode infoContainerNode, final String message) {
String encryptionAlgo = token.getValue(XP_ENCRYPTION_ALGO_USED_TO_SIGN_THIS_TOKEN);
encryptionAlgo = RuleUtils.canonicalizeEncryptionAlgo(encryptionAlgo);
final String encryptionKeyLength = token.getValue(XP_KEY_LENGTH_USED_TO_SIGN_THIS_TOKEN);
final String algoWithKeyLength = encryptionAlgo + encryptionKeyLength;
final Date algoExpirationDate = constraintData.getAlgorithmExpirationDate(algoWithKeyLength);
if (algoExpirationDate != null && controlTime.after(algoExpirationDate)) {
controlTime = algoExpirationDate;
final String formatedCertAlgoExpirationDate = DSSUtils.formatDate(algoExpirationDate);
infoContainerNode.addChild(INFO, message);
infoContainerNode.addChild(INFO).setAttribute(ALGORITHM_EXPIRATION_DATE, formatedCertAlgoExpirationDate);
}
}
private void checkDigestAlgoExpirationDate(final XmlDom token, final XmlNode infoContainerNode, final String message) {
String digestAlgo = token.getValue(XP_DIGEST_ALGO_USED_TO_SIGN_THIS_TOKEN);
digestAlgo = RuleUtils.canonicalizeSignatureAlgo(digestAlgo);
final Date algoExpirationDate = constraintData.getAlgorithmExpirationDate(digestAlgo);
if (algoExpirationDate != null && controlTime.after(algoExpirationDate)) {
controlTime = algoExpirationDate;
final String formatedCertAlgoExpirationDate = DSSUtils.formatDate(algoExpirationDate);
infoContainerNode.addChild(INFO, message);
infoContainerNode.addChild(INFO).setAttribute(ALGORITHM_EXPIRATION_DATE, formatedCertAlgoExpirationDate);
}
}
}