XAdESLevelBaselineT.java

// @formatter:off

/**
 * DSS - Digital Signature Services
 * Copyright (C) 2015 European Commission, provided under the CEF programme
 * <p>
 * This file is part of the "DSS - Digital Signature Services" project.
 * <p>
 * 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.
 * <p>
 * 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.
 * <p>
 * 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.xades.signature;

import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.enumerations.TimestampType;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.model.DSSMessageDigest;
import eu.europa.esig.dss.model.DigestDocument;
import eu.europa.esig.dss.model.TimestampBinary;
import eu.europa.esig.dss.model.TimestampParameters;
import eu.europa.esig.dss.model.x509.CertificateToken;
import eu.europa.esig.dss.signature.SignatureExtension;
import eu.europa.esig.dss.signature.SignatureRequirementsChecker;
import eu.europa.esig.dss.enumerations.SigningOperation;
import eu.europa.esig.dss.spi.DSSASN1Utils;
import eu.europa.esig.dss.spi.exception.IllegalInputException;
import eu.europa.esig.dss.spi.signature.AdvancedSignature;
import eu.europa.esig.dss.spi.validation.CertificateVerifier;
import eu.europa.esig.dss.spi.validation.ValidationData;
import eu.europa.esig.dss.spi.validation.executor.CompleteValidationContextExecutor;
import eu.europa.esig.dss.spi.x509.revocation.RevocationToken;
import eu.europa.esig.dss.spi.x509.revocation.crl.CRLToken;
import eu.europa.esig.dss.spi.x509.revocation.ocsp.OCSPToken;
import eu.europa.esig.dss.spi.x509.tsp.TSPSource;
import eu.europa.esig.dss.spi.x509.tsp.TimestampToken;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.xades.DSSXMLUtils;
import eu.europa.esig.dss.xades.XAdESProfileParameters;
import eu.europa.esig.dss.xades.XAdESSignatureParameters;
import eu.europa.esig.dss.xades.XAdESTimestampParameters;
import eu.europa.esig.dss.xades.definition.XAdESNamespace;
import eu.europa.esig.dss.xades.definition.xades111.XAdES111Attribute;
import eu.europa.esig.dss.xades.definition.xades111.XAdES111Element;
import eu.europa.esig.dss.xades.definition.xades122.XAdES122Attribute;
import eu.europa.esig.dss.xades.definition.xades122.XAdES122Element;
import eu.europa.esig.dss.xades.definition.xades141.XAdES141Element;
import eu.europa.esig.dss.xades.validation.XAdESAttributeIdentifier;
import eu.europa.esig.dss.xades.validation.XAdESSignature;
import eu.europa.esig.dss.xades.validation.XMLDocumentAnalyzer;
import eu.europa.esig.dss.xml.common.definition.DSSElement;
import eu.europa.esig.dss.xml.common.definition.xmldsig.XMLDSigAttribute;
import eu.europa.esig.dss.xml.common.definition.xmldsig.XMLDSigElement;
import eu.europa.esig.dss.xml.utils.DomUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Set;

import static eu.europa.esig.dss.enumerations.SignatureLevel.XAdES_BASELINE_T;

/**
 * -T profile of XAdES signature
 *
 */
public class XAdESLevelBaselineT extends ExtensionBuilder implements SignatureExtension<XAdESSignatureParameters> {

    private static final Logger LOG = LoggerFactory.getLogger(XAdESLevelBaselineT.class);

    /**
     * The object encapsulating the Time Stamp Protocol needed to create the level -T, of the signature
     */
    protected TSPSource tspSource;

    /**
     * The default constructor for XAdESLevelBaselineT.
     *
     * @param certificateVerifier {@link CertificateVerifier}
     */
    public XAdESLevelBaselineT(final CertificateVerifier certificateVerifier) {
        super(certificateVerifier);
    }

    private void incorporateC14nMethod(final Element parentDom, final String signedInfoC14nMethod) {

        // <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/>
        final Element canonicalizationMethodDom = DomUtils.createElementNS(documentDom, getXmldsigNamespace(), XMLDSigElement.CANONICALIZATION_METHOD);
        canonicalizationMethodDom.setAttribute(XMLDSigAttribute.ALGORITHM.getAttributeName(), signedInfoC14nMethod);
        parentDom.appendChild(canonicalizationMethodDom);
    }

    @Override
    public DSSDocument extendSignatures(final DSSDocument dssDocument, final XAdESSignatureParameters params) throws DSSException {
        Objects.requireNonNull(dssDocument, "The document cannot be null");
        Objects.requireNonNull(tspSource, "The TSPSource cannot be null");
        this.params = params;
        final XAdESProfileParameters context = params.getContext();
        if (LOG.isInfoEnabled()) {
            LOG.info("====> Extending: {}", (dssDocument.getName() == null ? "IN MEMORY DOCUMENT" : dssDocument.getName()));
        }

        documentAnalyzer = new XMLDocumentAnalyzer(dssDocument);
        documentAnalyzer.setCertificateVerifier(certificateVerifier);
        documentAnalyzer.setDetachedContents(params.getDetachedContents());
        documentAnalyzer.setValidationContextExecutor(CompleteValidationContextExecutor.INSTANCE);

        documentDom = documentAnalyzer.getRootElement();

        List<AdvancedSignature> signatures = documentAnalyzer.getSignatures();
        if (Utils.isCollectionEmpty(signatures)) {
            throw new IllegalInputException("There is no signature to extend!");
        }

        // In the case of the enveloped signature we have a specific treatment:<br>
        // we will just extend the signature that is being created (during creation process)
        List<AdvancedSignature> signaturesToExtend = signatures;

        final SigningOperation operationKind = context.getOperationKind();
        if (SigningOperation.SIGN.equals(operationKind)) {
            String signatureId = params.getDeterministicId();

            for (AdvancedSignature signature : signatures) {
                if (signatureId.equals(signature.getDAIdentifier())) {
                    signaturesToExtend = Collections.singletonList(signature);
                    break;
                }
            }
        }

        signaturesToExtend = assertNoEmbeddedSignaturesPresent(signaturesToExtend);

        extendSignatures(signaturesToExtend);

        return createXmlDocument();
    }

    /**
     * This method is used to exclude signatures embedded within other signatures for consecutive extension
     *
     * @param signatures a list of {@link AdvancedSignature} to process
     * @return a list of {@link AdvancedSignature}s excluding wrapped signatures
     */
    private List<AdvancedSignature> assertNoEmbeddedSignaturesPresent(List<AdvancedSignature> signatures) {
        List<AdvancedSignature> result = new ArrayList<>();
        for (AdvancedSignature signature : signatures) {
            XAdESSignature xadesSignature = (XAdESSignature) signature;
            Element signatureElement = xadesSignature.getSignatureElement();
            if (!hasSignatureAsParent(signatureElement)) {
                result.add(signature);
            } else {
                LOG.warn("The signature with Id '{}' has a ds:Signature parent within its XML tree! " +
                        "The signature will not be extended.", signature.getId());
            }
        }
        return result;
    }

    private boolean hasSignatureAsParent(Element element) {
        Node parent = element.getParentNode();
        while (parent != null) {
            if (XMLDSigElement.SIGNATURE.isSameTagName(parent.getLocalName()) &&
                    XMLDSigElement.SIGNATURE.getURI().equals(parent.getNamespaceURI())) {
                return true;
            }
            parent = parent.getParentNode();
        }
        return false;
    }

    /**
     * Extends signatures to a desired level.<br>
     * This method is overridden by other profiles.<br>
     * For -T profile adds the SignatureTimeStamp element which contains a single HashDataInfo element that refers to
     * the ds:SignatureValue element of the [XMLDSIG] signature. The timestamp token is obtained from TSP source.<br>
     * Adds {@code <SignatureTimeStamp>} segment into {@code <UnsignedSignatureProperties>} element.
     *
     * @param signatures a list of {@link AdvancedSignature}s to extend
     */
    protected void extendSignatures(List<AdvancedSignature> signatures) {
        final List<AdvancedSignature> signaturesToExtend = getExtendToTLevelSignatures(signatures);
        if (Utils.isCollectionEmpty(signaturesToExtend)) {
            return;
        }

        final SignatureRequirementsChecker signatureRequirementsChecker = getSignatureRequirementsChecker();
        signatureRequirementsChecker.assertExtendToTLevelPossible(signatures);

        signatureRequirementsChecker.assertSignaturesValid(signaturesToExtend);
        signatureRequirementsChecker.assertSigningCertificateIsValid(signaturesToExtend);

        for (AdvancedSignature signature : signaturesToExtend) {
            initializeSignatureBuilder((XAdESSignature) signature);

            Element levelBUnsignedProperties = (Element) unsignedSignaturePropertiesDom.cloneNode(true);

            final XAdESTimestampParameters signatureTimestampParameters = params.getSignatureTimestampParameters();
            final DigestAlgorithm digestAlgorithm = signatureTimestampParameters.getDigestAlgorithm();
            final String canonicalizationMethod = signatureTimestampParameters.getCanonicalizationMethod();
            final DSSMessageDigest messageDigest = xadesSignature.getTimestampSource()
                    .getSignatureTimestampMessageDigest(digestAlgorithm, canonicalizationMethod);
            createXAdESTimeStampType(TimestampType.SIGNATURE_TIMESTAMP, canonicalizationMethod, messageDigest);

            unsignedSignaturePropertiesDom = indentIfPrettyPrint(unsignedSignaturePropertiesDom, levelBUnsignedProperties);
        }
    }

    /**
     * Instantiates a {@code SignatureRequirementsChecker}
     *
     * @return {@link SignatureRequirementsChecker}
     */
    protected SignatureRequirementsChecker getSignatureRequirementsChecker() {
        return new SignatureRequirementsChecker(certificateVerifier, params);
    }

    private List<AdvancedSignature> getExtendToTLevelSignatures(List<AdvancedSignature> signatures) {
        final List<AdvancedSignature> toBeExtended = new ArrayList<>();
        for (AdvancedSignature signature : signatures) {
            if (tLevelExtensionRequired(signature)) {
                toBeExtended.add(signature);
            }
        }
        return toBeExtended;
    }

    private boolean tLevelExtensionRequired(AdvancedSignature signature) {
        return XAdES_BASELINE_T.equals(params.getSignatureLevel()) || !signature.hasTProfile();
    }

    /**
     * Sets the TSP source to be used when extending the digital signature
     *
     * @param tspSource
     *            the tspSource to set
     */
    public void setTspSource(final TSPSource tspSource) {
        this.tspSource = tspSource;
    }

    /**
     * This method incorporates all certificates passed as parameter :
     *
     * <pre>
     * {@code
     * 	<xades:CertificateValues>
     *		<xades:EncapsulatedX509Certificate>MIIC9TC...</xades:EncapsulatedX509Certificate>
     *		...
     * 	</xades:CertificateValues>
     * }
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param certificatesToBeAdded
     *            a collection of {@link CertificateToken}s to be added into the signature values element
     * @return {@link Element} incorporated signature values element
     */
    protected Element incorporateCertificateValues(final Element parentDom, final Collection<CertificateToken> certificatesToBeAdded) {
        Element certificateValuesDom = null;
        if (Utils.isCollectionNotEmpty(certificatesToBeAdded)) {
            certificateValuesDom = DomUtils.addElement(documentDom, parentDom, getXadesNamespace(), getCurrentXAdESElements().getElementCertificateValues());
            for (final CertificateToken certificateToken : certificatesToBeAdded) {

            // ########################OVERRIDE_DSS#########################
            // #####  Bu alan, XadesUtil kullanarak Tübitak'da         #####
            // #####  olduğu gibi 76 karakter satır-sonlu base64       #####
            // #####  sertifika değerinin CertificateValues altına     #####
            // #####  kapsüllenmiş şekilde eklenmesini garanti eder.   #####
            // #####  Mevcut kütüphane davranışını yerel gereksinime   #####
            // #####  uygun şekilde özelleştirir.                      #####
            // #############################################################
            //
            // Bu yardımcı metot ile üretilen EncapsulatedX509Certificate
            // elemanları, okunabilirliği artırmak için satır sonu eklenir.
                
                XadesUtil.createEncapsulatedCertificateElement(documentDom, certificateValuesDom, getXadesNamespace(), certificateToken.getEncoded());
            }
        }
        return certificateValuesDom;
    }

    /**
     * This method incorporates revocation values.
     *
     * <pre>
     * 	{@code
     * 		<xades:RevocationValues>
     * 	}
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param crlsToAdd
     *            a collection of {@link CRLToken}s to be added into the signature
     * @param ocspsToAdd
     *            a collection of {@link OCSPToken}s to be added into the signature
     * @return {@link Element} incorporated revocation values element
     */
    protected Element incorporateRevocationValues(final Element parentDom, final Collection<CRLToken> crlsToAdd, final Collection<OCSPToken> ocspsToAdd) {
        Element revocationValuesDom = null;

        if (Utils.isCollectionNotEmpty(crlsToAdd) || Utils.isCollectionNotEmpty(ocspsToAdd)) {
            revocationValuesDom = DomUtils.addElement(documentDom, parentDom, getXadesNamespace(), getCurrentXAdESElements().getElementRevocationValues());
            incorporateCrlTokens(revocationValuesDom, crlsToAdd);
            incorporateOcspTokens(revocationValuesDom, ocspsToAdd);
        }
        return revocationValuesDom;
    }

    /**
     * This method incorporates the CRLValues :
     *
     * <pre>
     * 	{@code
     * 		<xades:CRLValues>
     * 			<xades:EncapsulatedCRLValue>...</xades:EncapsulatedCRLValue>
     * 			...
     * 		</xades:CRLValues>
     * 	}
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param crlTokens
     *            a collection of CRL Tokens to be added
     */
    private void incorporateCrlTokens(final Element parentDom, final Collection<CRLToken> crlTokens) {
        if (crlTokens.isEmpty()) {
            return;
        }
        final Element crlValuesDom = DomUtils.addElement(documentDom, parentDom, getXadesNamespace(), getCurrentXAdESElements().getElementCRLValues());

        for (final RevocationToken<?> revocationToken : crlTokens) {
            
            // ########################OVERRIDE_DSS#########################
            // #####  Bu alan, XadesUtil kullanarak Tübitak'da         #####
            // #####  olduğu gibi 76 karakter satır-sonlu base64       #####
            // #####  CRL değerinin EncapsulatedCRLValue altına        #####
            // #####  kapsüllenmiş şekilde eklenmesini garanti eder.   #####
            // #####  Mevcut kütüphane davranışını yerel gereksinime   #####
            // #####  uygun şekilde özelleştirir.                      #####
            // #############################################################
            //
            // Bu yardımcı metot ile üretilen EncapsulatedCRLValue
            // elemanları, okunabilirliği artırmak için satır sonu eklenir.

            XadesUtil.createEncapsulatedCRLElement(documentDom, crlValuesDom, getXadesNamespace(), revocationToken.getEncoded());
        }
    }

    /**
     * This method incorporates the OCSP responses :
     *
     * <pre>
     * 	{@code
     * 		<xades:OCSPValues>
     * 			<xades:EncapsulatedOCSPValue>...</xades:EncapsulatedOCSPValue>
     * 			...
     * 		</xades:OCSPValues>
     * 	}
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param ocspTokens
     *            a collection of OCSP Tokens to be added
     */
    private void incorporateOcspTokens(Element parentDom, final Collection<OCSPToken> ocspTokens) {
        if (ocspTokens.isEmpty()) {
            return;
        }
        final Element ocspValuesDom = DomUtils.addElement(documentDom, parentDom, getXadesNamespace(), getCurrentXAdESElements().getElementOCSPValues());

        for (final RevocationToken<?> revocationToken : ocspTokens) {
            
            // ########################OVERRIDE_DSS#########################
            // #####  Bu alan, XadesUtil kullanarak Tübitak'da         #####
            // #####  olduğu gibi 76 karakter satır-sonlu base64       #####
            // #####  OCSP değerinin OCSPValues altına kapsüllenmiş    #####
            // #####  şekilde eklenmesini garanti eder.                #####
            // #####  Mevcut kütüphane davranışını yerel gereksinime   #####
            // #####  uygun şekilde özelleştirir.                      #####
            // #############################################################
            //
            // Bu yardımcı metot ile üretilen EncapsulatedOCSPValue
            // elemanları, okunabilirliği artırmak için satır sonu eklenir.

            XadesUtil.createEncapsulatedOCSPElement(documentDom, ocspValuesDom, getXadesNamespace(), revocationToken.getEncoded());
        }
    }

    /**
     * This method incorporates all certificates passed as parameter, as well as adds missing indents if the parameter is specified
     *
     * <pre>
     * {@code
     * 	<xades:CertificateValues>
     *		<xades:EncapsulatedX509Certificate>MIIC9TC...</xades:EncapsulatedX509Certificate>
     *		...
     * 	</xades:CertificateValues>
     * }
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param certificatesToBeAdded
     *            the certificates to be added into the signature
     * @param indent
     *            {@link String} to add between elements (if not NULL)
     */
    protected void incorporateCertificateValues(final Element parentDom,
                                                final Collection<CertificateToken> certificatesToBeAdded,
                                                String indent) {
        Element certificatesDom = incorporateCertificateValues(parentDom, certificatesToBeAdded);
        if (certificatesDom != null && indent != null) {
            DomUtils.setTextNode(documentDom, parentDom, indent);
            DSSXMLUtils.indentAndReplace(documentDom, certificatesDom);
        }
    }


    /**
     * This method incorporates revocation values, as well as adds missing indents if the parameter is specified:
     *
     * <pre>
     * 	{@code
     * 		<xades:RevocationValues>
     * 	}
     * </pre>
     *
     * @param parentDom
     *            the parent element
     * @param crlsToAdd
     *            a collection of {@link CRLToken}s to be added into the signature
     * @param ocspsToAdd
     *            a collection of {@link OCSPToken}s to be added into the signature
     * @param indent
     *            {@link String} to add between elements (if not NULL)
     */
    protected void incorporateRevocationValues(final Element parentDom, final Collection<CRLToken> crlsToAdd,
                                               final Collection<OCSPToken> ocspsToAdd, String indent) {
        Element revocationDom = incorporateRevocationValues(parentDom, crlsToAdd, ocspsToAdd);
        if (revocationDom != null && indent != null) {
            DomUtils.setTextNode(documentDom, parentDom, indent);
            DSSXMLUtils.indentAndReplace(documentDom, revocationDom);
        }
    }

    /**
     * This method removes old certificate values from the unsigned signature properties element.
     *
     * @return {@link String} indent
     */
    protected String removeOldCertificateValues() {
        String text = null;
        final Element toRemove = DomUtils.getElement(xadesSignature.getSignatureElement(), xadesPath.getCertificateValuesPath());
        if (toRemove != null) {
            text = removeNode(toRemove);
            xadesSignature.resetCertificateSource();
        }
        return text;
    }

    /**
     * This method removes old revocation values from the unsigned signature properties element.
     */
    protected void removeOldRevocationValues() {
        final Element toRemove = DomUtils.getElement(xadesSignature.getSignatureElement(), xadesPath.getRevocationValuesPath());
        if (toRemove != null) {
            removeNode(toRemove);
            xadesSignature.resetRevocationSources();
        }
    }

    /**
     * This method removes the TimeStampValidationData and AnyValidationData elements appearing
     * in the end of the unsigned properties.
     *
     * @return indent of the last {@code TimeStampValidationData} or {@code AnyValidationData} xml element, if any present
     */
    protected String removeLastTimestampAndAnyValidationData() {
        Element toRemove = getLastElementIfPresent(XAdES141Element.TIMESTAMP_VALIDATION_DATA, XAdES141Element.ANY_VALIDATION_DATA);
        if (toRemove == null) {
            return null;
        }
        String intent = null;
        while (toRemove != null) {
            intent = removeNode(toRemove);
            toRemove = getLastElementIfPresent(XAdES141Element.TIMESTAMP_VALIDATION_DATA, XAdES141Element.ANY_VALIDATION_DATA);
        }
        /*
         * Certificate and revocation sources need to be reset because of
         * the removing of TimeStampValidationData or AnyValidationData element
         */
        xadesSignature.resetCertificateSource();
        xadesSignature.resetRevocationSources();
        return intent;
    }

    private Element getLastElementIfPresent(DSSElement... xadesElements) {
        final NodeList nodeList = DomUtils.getNodeList(xadesSignature.getSignatureElement(), xadesPath.getUnsignedSignaturePropertiesPath() + "/*");
        if (nodeList.getLength() > 0) {
            final Element unsignedSignatureElement = (Element) nodeList.item(nodeList.getLength() - 1);
            final String nodeName = unsignedSignatureElement.getLocalName();
            if (Arrays.stream(xadesElements).anyMatch(e -> e.isSameTagName(nodeName))) {
                return unsignedSignatureElement;
            }
        }
        return null;
    }

    /**
     * This method incorporates the timestamp validation data in the signature
     *
     * @param validationDataForInclusion {@link ValidationData} to be included into the signature
     * @param indent {@link String}
     */
    protected void incorporateTimestampValidationData(final ValidationData validationDataForInclusion, String indent) {
        incorporateValidationData(validationDataForInclusion, indent, XAdES141Element.TIMESTAMP_VALIDATION_DATA, TST_VD_PREFIX);
    }

    /**
     * This method incorporates the AnyValidationData in the signature
     *
     * @param validationDataForInclusion {@link ValidationData} to be included into the signature
     * @param indent {@link String}
     */
    protected void incorporateAnyValidationData(final ValidationData validationDataForInclusion, String indent) {
        incorporateValidationData(validationDataForInclusion, indent, XAdES141Element.ANY_VALIDATION_DATA, ANY_VD_PREFIX);
    }

    /**
     * This method incorporates the timestamp validation data in the signature
     *
     * @param validationDataForInclusion {@link ValidationData} to be included into the signature
     * @param indent {@link String}
     * @param element {@link DSSElement}
     * @param prefix {@link String}
     */
    protected void incorporateValidationData(final ValidationData validationDataForInclusion, String indent,
                                             DSSElement element, String prefix) {
        if (!validationDataForInclusion.isEmpty()) {

            Set<CertificateToken> certificateValuesToAdd = validationDataForInclusion.getCertificateTokens();
            Set<CRLToken> crlsToAdd = validationDataForInclusion.getCrlTokens();
            Set<OCSPToken> ocspsToAdd = validationDataForInclusion.getOcspTokens();

            final Element timeStampValidationDataDom = DomUtils.addElement(
                    documentDom, unsignedSignaturePropertiesDom, getXades141Namespace(), element);

            incorporateCertificateValues(timeStampValidationDataDom, certificateValuesToAdd, indent);
            incorporateRevocationValues(timeStampValidationDataDom, crlsToAdd, ocspsToAdd, indent);

            String id = "1";
            final List<TimestampToken> timestamps = xadesSignature.getAllTimestamps();
            if (Utils.isCollectionNotEmpty(timestamps)) {
                final TimestampToken timestampToken = timestamps.get(timestamps.size() - 1);
                id = toXmlIdentifier(timestampToken.getDSSId());
            }

            timeStampValidationDataDom.setAttribute("Id", prefix + id);
            if (params.isPrettyPrint()) {
                DSSXMLUtils.indentAndReplace(documentDom, timeStampValidationDataDom);
            }

        }
    }

    /**
     * This method incorporate timestamp type object.
     */
    protected void incorporateArchiveTimestamp() {
        final XAdESTimestampParameters archiveTimestampParameters = params.getArchiveTimestampParameters();
        final DigestAlgorithm digestAlgorithm = archiveTimestampParameters.getDigestAlgorithm();
        final String canonicalizationMethod = archiveTimestampParameters.getCanonicalizationMethod();
        final DSSMessageDigest messageDigest = xadesSignature.getTimestampSource().getArchiveTimestampData(
                digestAlgorithm, canonicalizationMethod);
        createXAdESTimeStampType(TimestampType.ARCHIVE_TIMESTAMP, canonicalizationMethod, messageDigest);
    }

    /**
     * Creates any XAdES TimeStamp object representation. The timestamp token is obtained from TSP source
     *
     * @param timestampType
     *            {@code TimestampType}
     * @param timestampC14nMethod
     *            canonicalization method
     * @param messageDigest
     *            {@link DSSMessageDigest} representing the message-imprint digest to timestamp
     * @throws DSSException
     *             in case of any error
     */
    protected void createXAdESTimeStampType(final TimestampType timestampType, final String timestampC14nMethod, final DSSMessageDigest messageDigest) throws DSSException {

        if ((XAdESNamespace.XADES_111.isSameUri(getXadesNamespace().getUri())
                || XAdESNamespace.XADES_122.isSameUri(getXadesNamespace().getUri()))
                && TimestampType.SIGNATURE_TIMESTAMP != timestampType) {
            throw new UnsupportedOperationException("Signature Timestamp creation is only supported for XAdES 1.1.1 and 1.2.2");
        }

        final TimestampParameters signatureTimestampParameters = params.getSignatureTimestampParameters();
        DigestAlgorithm timestampDigestAlgorithm = signatureTimestampParameters.getDigestAlgorithm();

        Element timeStampDom;
        switch (timestampType) {
            case SIGNATURE_TIMESTAMP:
                // <xades:SignatureTimeStamp Id="time-stamp-1dee38c4-8388-40d1-8880-9eeda853fe60">
                timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXadesNamespace(), getCurrentXAdESElements().getElementSignatureTimeStamp());
                break;
            case VALIDATION_DATA_TIMESTAMP:
                // <xades:SigAndRefsTimeStamp Id="time-stamp-a762ab0e-e05c-4cc8-a804-cf2c4ffb5516">
                if (params.isEn319132()) {
                    timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXades141Namespace(), XAdES141Element.SIG_AND_REFS_TIMESTAMP_V2);
                } else {
                    timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXadesNamespace(), getCurrentXAdESElements().getElementSigAndRefsTimeStamp());
                }
                break;
            case VALIDATION_DATA_REFSONLY_TIMESTAMP:
                // <xades:RefsOnlyTimeStamp Id="time-stamp-a762ab0e-e05c-4cc8-a804-cf2c4ffb5516">
                if (params.isEn319132()) {
                    timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXades141Namespace(), XAdES141Element.REFS_ONLY_TIMESTAMP_V2);
                } else {
                    timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXadesNamespace(), getCurrentXAdESElements().getElementRefsOnlyTimeStamp());
                }
                break;
            case ARCHIVE_TIMESTAMP:
                // <xades141:ArchiveTimeStamp Id="time-stamp-a762ab0e-e05c-4cc8-a804-cf2c4ffb5516">
                timeStampDom = DomUtils.addElement(documentDom, unsignedSignaturePropertiesDom, getXades141Namespace(), XAdES141Element.ARCHIVE_TIMESTAMP);
                timestampDigestAlgorithm = params.getArchiveTimestampParameters().getDigestAlgorithm();
                break;
            default:
                // Content timestamps need to be generated before the signature itself
                throw new UnsupportedOperationException("Unsupported timestamp type : " + timestampType);
        }

        if (LOG.isDebugEnabled()) {
            LOG.debug("Timestamp generation: {} / {} / {}", timestampDigestAlgorithm.getName(), timestampC14nMethod,
                    Utils.toBase64(messageDigest.getValue()));
        }
        final TimestampBinary timeStampToken = tspSource.getTimeStampResponse(timestampDigestAlgorithm, messageDigest.getValue());

        // Use centralized utility method for consistent Base64 formatting
        final String base64EncodedTimeStampToken = XadesUtil.formatWithBase64(DSSASN1Utils.getDEREncoded(timeStampToken));

        if (XAdESNamespace.XADES_122.isSameUri(getXadesNamespace().getUri())) {
            incorporateXAdES122Include(timeStampDom);
        }
        if (XAdESNamespace.XADES_111.isSameUri(getXadesNamespace().getUri())) {
            incorporateHashDataInfo(timeStampDom, timestampC14nMethod);
        } else {
        
        // ########################OVERRIDE_DSS#########################
        // ##### Bu alan, DSS kütüphanesinin timestamp öğelerine      #
        // ##### CanonicalizationMethod eklemesini kontrol eder.      #
        // ##### TÜBİTAK çıktısından farklı olarak, DSS bu tag'i      #
        // ##### varsayılan olarak üretir. İhtiyaç durumunda          #
        // ##### kapatılabilir (else bloğunu comment yaparak).        #
        // #############################################################
        //
        // DSS, EN 319 132-1 standardına göre timestamp öğelerinde
        // CanonicalizationMethod element'ini zorunlu kabul eder ve
        // aşağıdaki XML tag'ini oluşturur:
        //
        // <ds:CanonicalizationMethod 
        //     Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
        //
        // Ancak TÜBİTAK çıktılarında bu element bulunmamaktadır. Bu farklılık
        // SignatureTimeStamp altında bulunmamaktadır. Bu farklılık
        // doğrulamada herhangi bir sorun yaratmamaktadır.
        //
        // KULLANIM:
        // - TÜBİTAK ile tam uyum için: else bloğunu comment yapın
        // - EN 319 132-1 uyumu için: Olduğu gibi bırakın (şu anki hali)
        //
        // ŞU ANKİ DURUM: CanonicalizationMethod EKLENİYOR (DSS varsayılan)
        //
        // #############################################################
        
            // <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/>
            incorporateC14nMethod(timeStampDom, timestampC14nMethod);
        }

        // <xades:EncapsulatedTimeStamp Id="time-stamp-token-6a150419-caab-4615-9a0b-6e239596643a">MIAGCSqGSIb3DQEH
        final Element encapsulatedTimeStampDom = DomUtils.addElement(documentDom, timeStampDom,
                getXadesNamespace(), getCurrentXAdESElements().getElementEncapsulatedTimeStamp());
        DomUtils.setTextNode(documentDom, encapsulatedTimeStampDom, base64EncodedTimeStampToken);

        // Id="..." attribute is not allowed in XAdES 1.1.1
        if (!XAdESNamespace.XADES_111.isSameUri(getXadesNamespace().getUri())) {
            // Add Id after the element is constructed
            final String timestampId = toXmlIdentifier(XAdESAttributeIdentifier.build(timeStampDom));
            timeStampDom.setAttribute(XMLDSigAttribute.ID.getAttributeName(), TIMESTAMP_PREFIX + timestampId);
            encapsulatedTimeStampDom.setAttribute(XMLDSigAttribute.ID.getAttributeName(), ENCAPSULATED_TIMESTAMP_PREFIX + timestampId);
        }
    }

    /**
     * <HashDataInfo URI="AI-NDS-HGI-32019423">
     * 	<Transforms xmlns="http://www.w3.org/2000/09/xmldsig#">
     * 		<Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"></Transform>
     *	</Transforms>
     * </HashDataInfo>
     *
     * @param timeStampDom {@link Element}
     * @param timestampC14nMethod {@link String} canonicalization algorithm for the timestamp
     */
    private void incorporateHashDataInfo(Element timeStampDom, String timestampC14nMethod) {
        Element hashDataInfoDom = DomUtils.addElement(documentDom, timeStampDom, getXadesNamespace(), XAdES111Element.HASH_DATA_INFO);
        hashDataInfoDom.setAttribute(XAdES111Attribute.URI.getAttributeName(), '#' + xadesSignature.getId());
        Element transformsDom = DomUtils.addElement(documentDom, hashDataInfoDom, getXadesNamespace(), XAdES111Element.TRANSFORMS);
        Element transformDom = DomUtils.addElement(documentDom, transformsDom, getXmldsigNamespace(), XMLDSigElement.TRANSFORM);
        transformDom.setAttribute(XMLDSigAttribute.ALGORITHM.getAttributeName(), timestampC14nMethod);
    }

    private void incorporateXAdES122Include(Element timeStampDom) {
        Element includeDom = DomUtils.addElement(documentDom, timeStampDom, getXadesNamespace(), XAdES122Element.INCLUDE);
        includeDom.setAttribute(XAdES122Attribute.URI.getAttributeName(), '#' + xadesSignature.getSignatureValueId());
    }

    /**
     * Checks if the detached content represented by binary documents (used for -LTA level extension)
     */
    protected void assertDetachedDocumentsContainBinaries() {
        List<DSSDocument> detachedContents = params.getDetachedContents();
        if (Utils.isCollectionNotEmpty(detachedContents)) {
            for (DSSDocument detachedDocument : detachedContents) {
                if (detachedDocument instanceof DigestDocument) {
                    throw new IllegalArgumentException("XAdES-LTA requires complete binaries of signed documents! "
                            + "Extension with a DigestDocument is not possible.");
                }
            }
        }
    }

}