eu.europa.esig.dss.validation.process.ltv.ControlTimeSliding.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.esig.dss.validation.process.ltv.ControlTimeSliding.java

Source

/**
 * 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.process.ltv;

import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.lang.StringUtils;

import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.XmlDom;
import eu.europa.esig.dss.validation.policy.ProcessParameters;
import eu.europa.esig.dss.validation.policy.RuleUtils;
import eu.europa.esig.dss.validation.policy.ValidationPolicy;
import eu.europa.esig.dss.validation.policy.XmlNode;
import eu.europa.esig.dss.validation.policy.rules.AttributeName;
import eu.europa.esig.dss.validation.policy.rules.AttributeValue;
import eu.europa.esig.dss.validation.policy.rules.Indication;
import eu.europa.esig.dss.validation.policy.rules.MessageTag;
import eu.europa.esig.dss.validation.policy.rules.NodeName;
import eu.europa.esig.dss.validation.policy.rules.NodeValue;
import eu.europa.esig.dss.validation.policy.rules.SubIndication;
import eu.europa.esig.dss.validation.process.ValidationXPathQueryHolder;
import eu.europa.esig.dss.validation.process.dss.InvolvedServiceInfo;
import eu.europa.esig.dss.validation.process.subprocess.EtsiPOEExtraction;

/**
 * 9.2.2 Control-time sliding process<br>
 *
 * 9.2.2.1 Description<br>
 *
 * 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.
 *
 *
 *
 *         // 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 {

    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>
     *
     * 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(NodeName.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;
    }

    /**
     * This method returns the certificate id. This id is extracted either from the ChainCertificate, or Certificate.
     *
     * @param xmlDomObject
     * @return
     */
    private String getCertificateId(final XmlDom xmlDomObject) {
        final String certificateId = xmlDomObject.getValue("./@Id");
        return certificateId;
    }

    /**
     * @param params
     * @param certificateChain
     * @return
     */
    private ControlTimeSlidingConclusion process(final ProcessParameters params, final XmlDom certificateChain) {

        final ControlTimeSlidingConclusion conclusion = new ControlTimeSlidingConclusion();

        final String signingCertificateId = certificateChain.getValue("./ChainCertificate[1]/@Id");

        final List<XmlDom> chainCertificates = certificateChain.getElements("./ChainCertificate");

        Collections.reverse(chainCertificates);

        /**
         * 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 : chainCertificates) {

            final String certificateId = getCertificateId(chainCertificate);
            final XmlNode certificateNode = controlTimeSlidingData.addChild(AttributeValue.CERTIFICATE,
                    StringUtils.EMPTY);
            certificateNode.setAttribute(AttributeValue.CERTIFICATE_ID, String.valueOf(certificateId));

            final XmlDom certificate = params.getCertificate(certificateId);

            final boolean isTrusted = certificate.getBoolValue("./Trusted/text()");
            if (isTrusted) {

                continue;
            }

            if (StringUtils.equals(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(MessageTag.CTS_WITSS);

                final String status = InvolvedServiceInfo.getStatus(certificate);
                constraintNode.addChild(NodeName.STATUS, NodeValue.OK);
                constraintNode.addChild(NodeName.INFO).setAttribute(AttributeValue.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(MessageTag.CTS_DRIE);

            final boolean revocationExists = certificate.exists("./Revocation");
            if (!revocationExists) {

                constraintNode.addChild(NodeName.STATUS, NodeValue.KO);
                conclusion.setIndication(Indication.INDETERMINATE);
                conclusion.setSubIndication(SubIndication.NO_POE);
                return conclusion;
            }

            final Date revocationIssuingTime = certificate.getTimeValue("./Revocation/IssuingTime/text()");
            final String formatedRevocationIssuingTime = DSSUtils.formatDate(revocationIssuingTime);

            constraintNode.addChild(NodeName.STATUS, NodeValue.OK);
            constraintNode.addChild(NodeName.INFO).setAttribute(AttributeName.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(MessageTag.CTS_ICNEAIDORSI);

            if (revocationIssuingTime.before(notBeforeTime) || revocationIssuingTime.after(notAfterTime)) {

                constraintNode.addChild(NodeName.STATUS, NodeValue.KO);

                conclusion.setIndication(Indication.INDETERMINATE);
                conclusion.setSubIndication(SubIndication.NO_POE);
                return conclusion;
            }
            constraintNode.addChild(NodeName.STATUS, NodeValue.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(MessageTag.CTS_IIDORSIBCT);

            if (!revocationIssuingTime.before(controlTime)) {

                constraintNode.addChild(NodeName.STATUS, NodeValue.KO);
                addControlTime(constraintNode);

                conclusion.setIndication(Indication.INDETERMINATE);
                conclusion.setSubIndication(SubIndication.NO_POE);
                return conclusion;
            }
            constraintNode.addChild(NodeName.STATUS, NodeValue.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(MessageTag.CTS_DSOPCPOEOC);

            final boolean poeExists = poe.getCertificatePOE(certificateId, controlTime);
            if (!poeExists || (revocationIssuingTime.compareTo(controlTime) > 0)) {

                constraintNode.addChild(NodeName.STATUS, NodeValue.KO);
                conclusion.setIndication(Indication.INDETERMINATE);
                conclusion.setSubIndication(SubIndication.NO_POE);
                return conclusion;
            }
            constraintNode.addChild(NodeName.STATUS, NodeValue.OK);

            /**
             * - c) Update the value of control-time as follows:<br>
             */

            constraintNode = addConstraint(MessageTag.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(NodeName.INFO, NodeValue.CTS_CTSTRT_LABEL);
                constraintNode.addChild(NodeName.INFO).setAttribute(AttributeName.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(NodeName.INFO, NodeValue.CTS_CTSTRIT_LABEL);
                    final XmlNode xmlNode = constraintNode.addChild(NodeName.INFO, MessageTag.BBB_XCV_IRIF_ANS);
                    xmlNode.setAttribute(AttributeValue.CERTIFICATE_ID, String.valueOf(certificateId))
                            .setAttribute(AttributeName.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, NodeValue.CTS_CTSTETOCSA_LABEL);
            checkEncryptionAlgoExpirationDate(certificate, constraintNode, NodeValue.CTS_CTSTETOCSA_LABEL);

            final XmlDom revocation = certificate.getElement("./Revocation");
            checkDigestAlgoExpirationDate(revocation, constraintNode, NodeValue.CTS_CTSTETORSA_LABEL);
            checkEncryptionAlgoExpirationDate(revocation, constraintNode, NodeValue.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(Indication.VALID);
        conclusion.addInfo().setAttribute(AttributeValue.CONTROL_TIME, DSSUtils.formatDate(controlTime));

        return conclusion;
    }

    /**
     * @param constraintNode
     */

    private void addControlTime(XmlNode constraintNode) {

        String formatedControlTime = DSSUtils.formatDate(controlTime);
        constraintNode.addChild(NodeName.INFO).setAttribute(AttributeValue.CONTROL_TIME, formatedControlTime);
    }

    /**
     * @param messageTag
     * @return
     */
    private XmlNode addConstraint(final MessageTag messageTag) {

        XmlNode constraintNode = controlTimeSlidingData.addChild(NodeName.CONSTRAINT);
        constraintNode.addChild(NodeName.NAME, messageTag.getMessage()).setAttribute(AttributeName.NAME_ID,
                messageTag.name());
        return constraintNode;
    }

    private void checkEncryptionAlgoExpirationDate(final XmlDom token, final XmlNode infoContainerNode,
            final String message) {

        String encryptionAlgo = token
                .getValue(ValidationXPathQueryHolder.XP_ENCRYPTION_ALGO_USED_TO_SIGN_THIS_TOKEN);
        encryptionAlgo = RuleUtils.canonicalizeEncryptionAlgo(encryptionAlgo);
        final String encryptionKeyLength = token
                .getValue(ValidationXPathQueryHolder.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(NodeName.INFO, message);
            infoContainerNode.addChild(NodeName.INFO).setAttribute(AttributeValue.ALGORITHM_EXPIRATION_DATE,
                    formatedCertAlgoExpirationDate);
        }
    }

    private void checkDigestAlgoExpirationDate(final XmlDom token, final XmlNode infoContainerNode,
            final String message) {

        String digestAlgo = token.getValue(ValidationXPathQueryHolder.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(NodeName.INFO, message);
            infoContainerNode.addChild(NodeName.INFO).setAttribute(AttributeValue.ALGORITHM_EXPIRATION_DATE,
                    formatedCertAlgoExpirationDate);
        }
    }
}