/* * 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 org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.validation102853.policy.ProcessParameters; import eu.europa.ec.markt.dss.validation102853.processes.dss.ForLegalPerson; import eu.europa.ec.markt.dss.validation102853.processes.dss.QualifiedCertificate; import eu.europa.ec.markt.dss.validation102853.processes.dss.SSCD; import eu.europa.ec.markt.dss.validation102853.processes.subprocesses.X509CertificateValidation; import eu.europa.ec.markt.dss.validation102853.report.Conclusion.Info; import eu.europa.ec.markt.dss.validation102853.rules.MessageTag; 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.BBB_XCV_CCCBB; import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.BBB_XCV_ICSI_ANS; import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.PCV_ICTSC; import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.PCV_ICTSC_ANS; import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.PCV_TINTA_ANS; import static eu.europa.ec.markt.dss.validation102853.rules.MessageTag.XCV_IFCCIIPC_ANS; /** * 9.2 Additional Building blocks<br> * <p/> * 9.2.1 Past certificate validation<br> * <p/> * 9.2.1.1 Description<br> * <p/> * This process validates a certificate at a date/time which may be in the past. This may become necessary in the LTV * settings when a compromising event (for instance, the end-entity certificate expires) prevents the traditional * certificate validation algorithm (see clause 5.3) to asserting the validation status of a certificate (for instance, * in case the end-entity certificate is expired at the current time, the traditional validation algorithm will return * INDETERMINATE/OUT_OF_BOUNDS_NO_POE due to the step 1).<br> * <p/> * The rationale of the algorithm described below are given in [i.4] and can be summarised in the following: if a * certificate chain has been usable to validate a certificate at some date/time in the past, the same chain can be used * at the current time to derive the same validity status, provided each certificate in the chain satisfies one of the * following:<br> * <p/> * a) <b>The revocation status of the certificate can be ascertained at the current time</b> (typically if the * certificate is not yet expired and appropriate revocation status information is obtained at the current time).<br> * <p/> * b) <b>The revocation status of the certificate can be ascertained using "old" revocation status information</b> such * that the certificate (resp. the revocation status information) is proven to having existed at a date in the past when * the issuer of the certificate (resp. the revocation status information) was still considered reliable and under * control of its signing key. This particular date/time will be named <b><i>control-time</i></b>.<br> * <p/> * NOTE: Control-time is an internal variable that is used within the algorithms and not part of the core results of the * validation process.<br> * <p/> * <b>Assuming that the trust anchor is still accepted as such at current time</b>, the validation 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. In addition to the certificate chain, the process outputs the last value of control-time – the control-time * associated with the target certificate (the certificate to validate). Any object signed with the target certificate * and proven to exist before this control-time can be accepted as VALID. This assertion is the basis of the LTV * validation processes presented in the next clauses. For more readability, the sliding algorithm is presented in its * own building block (control-time sliding process) described in the next clause.<br> * <p/> * It is important to note that when all the certificates in the chain can be validated at the current time, the * control-time never slides and the algorithm boils down to the traditional certificate validation algorithm described * in clause 5.3. The process below builds a prospective certificate chain in a very same way as in clause 5.3 except * that the X.509 validation algorithm is performed at a determined date in the past (instead of the current date/time) * and without any revocation checking. For each such chain, the sliding algorithm is executed to calculate the * control-time.<br> * * @author bielecro */ public class PastCertificateValidation extends X509CertificateValidation { private static final Logger LOG = LoggerFactory.getLogger(PastCertificateValidation.class); /** * // TODO: (Bob: 2014 Mar 12) */ private String contextName; /** * @param params */ private void prepareParameters(final ProcessParameters params) { this.constraintData = params.getCurrentValidationPolicy(); isInitialised(); } /** * */ private void isInitialised() { if (constraintData == null) { throw new DSSException(String.format(EXCEPTION_TCOPPNTBI, getClass().getSimpleName(), "validationPolicy")); } } /** * This method prepares the past certificate validation process. * <p/> * Input<br> * - Signature or time-stamp token . Mandatory<br> * - Target certificate ............ Mandatory<br> * - X.509 Validation Parameters ... Mandatory<br> * - A set of POEs ................. Mandatory<br> * - Certificate meta-data ......... Optional<br> * - Chain Constraints ............. Optional<br> * - Cryptographic Constraints ..... Optional<br> * <p/> * 9.2.1.3 Output<br> * - VALID<br> * - INDETERMINATE CHAIN_CONSTRAINTS_FAILURE, NO_CERTIFICATE_CHAIN_FOUND, NO_POE (returned by ControlTimeSliding) * * @param params validation process parameters * @param signatureXmlDom {@code XmlDom} representation of the signature to validate * @return {@code PastCertificateValidationConclusion} including information collected during the validation process. */ public PastCertificateValidationConclusion run(final ProcessParameters params, final XmlDom signatureXmlDom, final String context) { this.contextName = context; prepareParameters(params); LOG.debug(this.getClass().getSimpleName() + ": start."); validationDataXmlNode = new XmlNode(PAST_CERT_VALIDATION_DATA); validationDataXmlNode.setNameSpace(XmlDom.NAMESPACE); contextElement = signatureXmlDom; final PastCertificateValidationConclusion conclusion = process(params); conclusion.setValidationData(validationDataXmlNode); return conclusion; } /** * This method carries out the past certificate validation process. * * @param params validation process parameters * @return */ private PastCertificateValidationConclusion process(final ProcessParameters params) { final PastCertificateValidationConclusion conclusion = new PastCertificateValidationConclusion(); final XmlDom certificateChainXmlDom = contextElement.getElement("./CertificateChain"); /** * 9.2.1.4 Processing<br> * * The following steps shall be performed:<br> * * 1) Build a new prospective certificate chain that has not yet been evaluated. The chain shall satisfy the * conditions of a prospective certificate chain as stated in [4], clause 6.1, using one of the trust anchors * provided in the inputs:<br> * * a) If no new chain can be built, abort the processing with the current status and the last chain built or, if * no chain was built, with INDETERMINATE/NO_CERTIFICATE_CHAIN_FOUND.<br> */ // The current status is not used in this implementation because the DSS framework build just one chain. final String signingCertificateId = certificateChainXmlDom.getValue("./ChainCertificate[1]/@Id"); validationDataXmlNode.setAttribute(CERTIFICATE_ID, signingCertificateId); final boolean trustedProspectiveCertificateChain = Boolean.valueOf(isTrustedProspectiveCertificateChain(params)); if (!checkProspectiveCertificateChainConstraint(conclusion, trustedProspectiveCertificateChain)) { return conclusion; } XmlNode constraintNode = addConstraint(BBB_XCV_CCCBB); final String trustedAnchorId = certificateChainXmlDom.getValue("./ChainCertificate[last()]/@Id"); final XmlDom trustedAnchor = params.getCertificate(trustedAnchorId); boolean isLastTrusted = false; if (trustedAnchor != null) { isLastTrusted = trustedAnchor.getBoolValue("./Trusted/text()"); } if (!isLastTrusted) { constraintNode.addChild(STATUS, KO); conclusion.setIndication(INDETERMINATE, NO_CERTIFICATE_CHAIN_FOUND); final Info info = conclusion.addInfo(PCV_TINTA_ANS); info.addTo(constraintNode); return conclusion; } /** * b) Otherwise, go to the next step. */ /** * 2) Run the Certification Path Validation [4], clause 6.1, with the following inputs:<br> * - the prospective chain built in the previous step,<br> * - the trust anchor used in the previous step,<br> * - the X.509 parameters provided in the inputs and<br> * - a date from the intersection of the validity intervals of all the certificates in the prospective chain.<br> * <b>The validation shall not include revocation checking</b>:<br> */ final List<XmlDom> certChain = certificateChainXmlDom.getElements("./ChainCertificate"); Date intersectionNotBefore = null; Date intersectionNotAfter = null; for (XmlDom certToken : certChain) { final String certificateId = certToken.getValue("./@Id"); final XmlDom certificate = params.getCertificate(certificateId); final boolean isTrusted = certificate.getBoolValue("./Trusted/text()"); final Date notBefore = certificate.getTimeValue("./NotBefore/text()"); final Date notAfter = certificate.getTimeValue("./NotAfter/text()"); if (intersectionNotAfter == null) { intersectionNotAfter = notAfter; } else if (intersectionNotAfter.after(notAfter)) { intersectionNotAfter = notAfter; } if (intersectionNotBefore == null) { intersectionNotBefore = notBefore; } else if (intersectionNotBefore.before(notBefore)) { intersectionNotBefore = notBefore; } if (intersectionNotAfter.before(intersectionNotBefore)) { constraintNode.addChild(STATUS, KO); conclusion.setIndication(INDETERMINATE, NO_CERTIFICATE_CHAIN_FOUND); final Info info = conclusion.addInfo(XCV_IFCCIIPC_ANS, notAfter.toString(), notAfter.toString(), certificateId); info.setAttribute(CERTIFICATE_ID, certificateId); info.addTo(constraintNode); return conclusion; } if (isTrusted) { // There is not need to check the revocation data for the trusted certificate continue; } /** * RFC 5280:<br> * Note that clients MUST reject the certificate if it contains an unsupported critical extension.<br> * ../..<br> * The primary goal of path validation is to verify the binding between a subject distinguished name or a * subject alternative name and subject public key, as represented in the target certificate, based on the * public key of the trust anchor. In most cases, the target certificate will be an end entity certificate, but * the target certificate may be a CA certificate as long as the subject public key is to be used for a purpose * other than verifying the signature on a public key certificate. Verifying the binding between the name and * subject public key requires obtaining a sequence of certificates that support that binding. ../..<br> * To meet this goal, the path validation process verifies, among other things, that a prospective * certification path (a sequence of n certificates) satisfies the following conditions: * * (a) for all x in {1, ..., n-1}, the subject of certificate x is the issuer of certificate x+1; * * (b) certificate 1 is issued by the trust anchor; * * (c) certificate n is the certificate to be validated (i.e., the target certificate); and * * (d) for all x in {1, ..., n}, the certificate was valid at the time in question. * * A certificate MUST NOT appear more than once in a prospective certification path.<br> * When the trust anchor is provided in the form of a self-signed certificate, this self-signed certificate is * not included as part of the prospective certification path. Information about trust anchors is provided as * inputs to the certification path validation algorithm (Section 6.1.1).<br> * * This section presents the algorithm in four basic steps:<br> * (1) Initialisation,<br> * (2) basic certificate processing,<br> * (3) preparation for the next certificate, and<br> * (4) wrap-up.<br> * Steps (1) and (4) are performed exactly once. Step (2) is performed for all certificates in the path. Step * (3) is performed for all certificates in the path except the final certificate.<br> * * 6.1.1. Inputs<br> * This algorithm assumes that the following nine inputs are provided to the path processing logic (limited to * DSS use):<br> * (a) a prospective certification path of length n.<br> * (b) the current date/time.<br> * (d) trust anchor information, describing a CA that serves as a trust anchor for the certification path.(The * trust anchor information may be provided to the path processing procedure in the form of a self-signed * certificate...)<br> * * 6.1.3. Basic Certificate Processing<br> * The basic path processing actions to be performed for certificate i (for all i in [1..n]) are listed below.<br> * - (a) Verify the basic certificate information. The certificate MUST satisfy each of the following:<br> * - - (1) The signature on the certificate can be verified using working_public_key_algorithm, the * working_public_key, and the working_public_key_parameters.<br> * - - (2) The certificate validity period includes the current time.<br> * - - (3) At the current time, the certificate is not revoked. This may be determined by obtaining the * appropriate CRL (Section 6.3), by status information, or by out-of-band mechanisms.<br> * - - (4) The certificate issuer name is the working_issuer_name.<br> * */ final boolean isSignatureIntact = certificate.getBoolValue(XP_SIGNATURE_VALID); if (!isSignatureIntact) { constraintNode.addChild(STATUS, KO); conclusion.setIndication(INDETERMINATE, NO_CERTIFICATE_CHAIN_FOUND); final Info info = conclusion.addInfo(BBB_XCV_ICSI_ANS); info.setAttribute(CERTIFICATE_ID, certificateId); info.addTo(constraintNode); return conclusion; } // The revocation status is checked in ControlTimeSliding process. } constraintNode.addChild(STATUS, OK); /** * a) If the certificate path validation returns a success indication, go to the next step.<br> */ // --> Go to 3) /** * b) If the certificate path validation returns a failure indication, go to step 1.<br> */ // --> DSS builds only one chain /** * 3) Perform the control-time sliding process with the following inputs:<br> * - the prospective chain,<br> * - the set of POEs and<br> * - the cryptographic constraints.<br> */ final ControlTimeSliding controlTimeSliding = new ControlTimeSliding(); final ControlTimeSlidingConclusion ctsConclusion = controlTimeSliding.run(params, certificateChainXmlDom); validationDataXmlNode.addChild(ctsConclusion.getValidationData()); /** * If it outputs a success indication, go to the next step.<br> * Otherwise, set the current status to the returned indication and sub-indication and go back to step 1.<br> */ constraintNode = addConstraint(PCV_ICTSC); // --> DSS builds only one chain // From 1): // a) If no new chain can be built, abort the processing with the current status and the last chain built. final String ctsConclusionIndication = ctsConclusion.getIndication(); if (!VALID.equals(ctsConclusionIndication)) { constraintNode.addChild(STATUS, KO); conclusion.setIndication(ctsConclusionIndication, ctsConclusion.getSubIndication()); conclusion.addInfo(PCV_ICTSC_ANS); return conclusion; } constraintNode.addChild(STATUS, OK); final Date controlTime = ctsConclusion.getControlTime(); final String formatedControlTime = DSSUtils.formatDate(controlTime); constraintNode.addChild(INFO).setAttribute(CONTROL_TIME, formatedControlTime); /** * 4) Apply the Chain Constraints to the chain. Certificate meta-data has to be taken into account when checking * these constraints against the chain. If the chain does not match these constraints, set the current status to * INVALID/CHAIN_CONSTRAINTS_FAILURE and go to step 1.<br> */ if (MAIN_SIGNATURE.equals(contextName)) { /** * A.2 Constraints on X.509 Certificate meta-data * * The following constraints are to be applied to the signer's certificate before considering it as valid for the * intended use. */ final XmlDom signingCertificate = params.getCertificate(signingCertificateId); final QualifiedCertificate qc = new QualifiedCertificate(constraintData); boolean isQC = qc.run(signingCertificate); if (!checkSigningCertificateQualificationConstraint(conclusion, isQC)) { return conclusion; } final SSCD sscd = new SSCD(constraintData); final Boolean isSSCD = sscd.run(signingCertificate); if (!checkSigningCertificateSupportedBySSCDConstraint(conclusion, isSSCD)) { return conclusion; } final ForLegalPerson forLegalPerson = new ForLegalPerson(constraintData); final Boolean isForLegalPerson = forLegalPerson.run(signingCertificate); if (!checkSigningCertificateIssuedToLegalPersonConstraint(conclusion, isForLegalPerson)) { return conclusion; } } /** * 5) Terminate with the current status and, if VALID, the certificate chain and the calculated control-time * returned in step 3. */ conclusion.setIndication(VALID); conclusion.addInfo().setAttribute(CONTROL_TIME, formatedControlTime); conclusion.setControlTime(controlTime); return conclusion; } /** * @param messageTag * @return */ private XmlNode addConstraint(final MessageTag messageTag) { XmlNode constraintNode = validationDataXmlNode.addChild(CONSTRAINT); constraintNode.addChild(NAME, messageTag.getMessage()).setAttribute(NAME_ID, messageTag.name()); return constraintNode; } }