eu.europa.esig.dss.xades.validation.XAdESSignature.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.esig.dss.xades.validation.XAdESSignature.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.xades.validation;

import static eu.europa.esig.dss.XPathQueryHolder.XMLE_ALGORITHM;
import static eu.europa.esig.dss.XPathQueryHolder.XMLE_REFS_ONLY_TIME_STAMP;
import static eu.europa.esig.dss.XPathQueryHolder.XMLE_SIGNATURE_TIME_STAMP;
import static eu.europa.esig.dss.XPathQueryHolder.XMLE_SIG_AND_REFS_TIME_STAMP;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.StringReader;
import java.math.BigInteger;
import java.security.PublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.security.auth.x500.X500Principal;
import javax.xml.crypto.dsig.CanonicalizationMethod;
import javax.xml.transform.stream.StreamSource;

import org.apache.commons.codec.binary.Base64;
import org.apache.xml.security.Init;
import org.apache.xml.security.algorithms.JCEMapper;
import org.apache.xml.security.keys.KeyInfo;
import org.apache.xml.security.keys.keyresolver.KeyResolverException;
import org.apache.xml.security.signature.Reference;
import org.apache.xml.security.signature.ReferenceNotInitializedException;
import org.apache.xml.security.signature.SignedInfo;
import org.apache.xml.security.signature.XMLSignature;
import org.apache.xml.security.signature.XMLSignatureException;
import org.bouncycastle.asn1.ASN1InputStream;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.tsp.TimeStampToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSNotETSICompliantException;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.DSSXMLUtils;
import eu.europa.esig.dss.DigestAlgorithm;
import eu.europa.esig.dss.EncryptionAlgorithm;
import eu.europa.esig.dss.SignatureAlgorithm;
import eu.europa.esig.dss.SignatureForm;
import eu.europa.esig.dss.SignatureLevel;
import eu.europa.esig.dss.TokenIdentifier;
import eu.europa.esig.dss.XAdESNamespaces;
import eu.europa.esig.dss.XPathQueryHolder;
import eu.europa.esig.dss.validation.AdvancedSignature;
import eu.europa.esig.dss.validation.CRLRef;
import eu.europa.esig.dss.validation.CandidatesForSigningCertificate;
import eu.europa.esig.dss.validation.CertificateRef;
import eu.europa.esig.dss.validation.CertificateValidity;
import eu.europa.esig.dss.validation.CertifiedRole;
import eu.europa.esig.dss.validation.CommitmentType;
import eu.europa.esig.dss.validation.DefaultAdvancedSignature;
import eu.europa.esig.dss.validation.OCSPRef;
import eu.europa.esig.dss.validation.SignatureCryptographicVerification;
import eu.europa.esig.dss.validation.SignatureProductionPlace;
import eu.europa.esig.dss.validation.TimestampInclude;
import eu.europa.esig.dss.validation.TimestampReference;
import eu.europa.esig.dss.validation.TimestampToken;
import eu.europa.esig.dss.x509.ArchiveTimestampType;
import eu.europa.esig.dss.x509.CertificatePool;
import eu.europa.esig.dss.x509.CertificateToken;
import eu.europa.esig.dss.x509.SignaturePolicy;
import eu.europa.esig.dss.x509.TimestampType;
import eu.europa.esig.dss.x509.crl.OfflineCRLSource;
import eu.europa.esig.dss.x509.ocsp.OfflineOCSPSource;

/**
 * Parse an XAdES signature structure. Note that for each signature to be validated a new instance of this object must be created.
 *
 */
public class XAdESSignature extends DefaultAdvancedSignature {

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

    /**
     * This array contains all the XAdES signatures levels TODO: do not return redundant levels.
     */
    private static SignatureLevel[] signatureLevels = new SignatureLevel[] { SignatureLevel.XML_NOT_ETSI,
            SignatureLevel.XAdES_BASELINE_B, SignatureLevel.XAdES_BASELINE_T, SignatureLevel.XAdES_C,
            SignatureLevel.XAdES_X, SignatureLevel.XAdES_BASELINE_LT, SignatureLevel.XAdES_BASELINE_LTA };

    /**
     * This variable contains the list of {@code XPathQueryHolder} adapted to the specific signature schema.
     */
    private final List<XPathQueryHolder> xPathQueryHolders;

    /**
     * This variable contains the XPathQueryHolder adapted to the signature schema.
     */
    protected XPathQueryHolder xPathQueryHolder;

    public static final String DEFAULT_TIMESTAMP_VALIDATION_CANONICALIZATION_METHOD = CanonicalizationMethod.INCLUSIVE;

    private final Element signatureElement;

    /**
     * Indicates the id of the signature. If not existing this attribute is auto calculated.
     */
    private String signatureId;

    private XAdESCertificateSource certificatesSource;

    /**
     * This variable contains all references found within the signature. They are extracted when the method {@code checkSignatureIntegrity} is called.
     */
    private transient List<Reference> references = new ArrayList<Reference>();

    /**
     * This list represents all digest algorithms used to calculate the digest values of certificates.
     */
    private Set<DigestAlgorithm> usedCertificatesDigestAlgorithms = new HashSet<DigestAlgorithm>();

    /**
     * Cached list of the Signing Certificate Timestamp References.
     */
    private List<TimestampReference> signingCertificateTimestampReferences;

    static {

        Init.init();

        /**
         * Adds the support of ECDSA_RIPEMD160 for XML signature. Used by AT. The BC provider must be previously added.
         */
        // final JCEMapper.Algorithm algorithm = new JCEMapper.Algorithm("",
        // SignatureAlgorithm.ECDSA_RIPEMD160.getJCEId(), "Signature");
        // final String xmlId = SignatureAlgorithm.ECDSA_RIPEMD160.getXMLId();
        // JCEMapper.register(xmlId, algorithm);
        // try {
        // org.apache.xml.security.algorithms.SignatureAlgorithm.register(xmlId,
        // SignatureECDSARIPEMD160.class);
        // } catch (Exception e) {
        // LOG.error("ECDSA_RIPEMD160 algorithm initialisation failed.", e);
        // }

        /**
         * Adds the support of not standard algorithm name: http://www.w3.org/2001/04/xmldsig-more/rsa-ripemd160. Used by some AT signature providers.
         * The BC provider must be previously added.
         */

        final JCEMapper.Algorithm notStandardAlgorithm = new JCEMapper.Algorithm("",
                SignatureAlgorithm.RSA_RIPEMD160.getJCEId(), "Signature");
        JCEMapper.register(SignatureRSARIPEMD160AT.XML_ID, notStandardAlgorithm);
        try {
            org.apache.xml.security.algorithms.SignatureAlgorithm.register(SignatureRSARIPEMD160AT.XML_ID,
                    SignatureRSARIPEMD160AT.class);
        } catch (Exception e) {
            LOG.error("ECDSA_RIPEMD160AT algorithm initialisation failed.", e);
        }
    }

    /**
     * This constructor is used when creating the signature. The default {@code XPathQueryHolder} is set.
     *
     * @param signatureElement
     *            w3c.dom <ds:Signature> element
     * @param certPool
     *            can be null
     */
    public XAdESSignature(final Element signatureElement, final CertificatePool certPool) {

        this(signatureElement, (new ArrayList<XPathQueryHolder>() {
            {
                add(new XPathQueryHolder());
            }
        }), certPool);
    }

    /**
     * The default constructor for XAdESSignature.
     *
     * @param signatureElement
     *            w3c.dom <ds:Signature> element
     * @param xPathQueryHolders
     *            List of {@code XPathQueryHolder} to use when handling signature
     * @param certPool
     *            can be null
     */
    public XAdESSignature(final Element signatureElement, final List<XPathQueryHolder> xPathQueryHolders,
            final CertificatePool certPool) throws NullPointerException {

        super(certPool);
        if (signatureElement == null) {

            throw new NullPointerException("signatureElement");
        }
        this.signatureElement = signatureElement;
        this.xPathQueryHolders = xPathQueryHolders;
        initialiseSettings();
    }

    /**
     * This method is called when creating a new instance of the {@code XAdESSignature} with unknown schema.
     */
    private void initialiseSettings() {

        recursiveNamespaceBrowser(signatureElement);
        if (xPathQueryHolder == null) {

            LOG.warn(
                    "There is no suitable XPathQueryHolder to manage the signature. The default one will be used.");
            xPathQueryHolder = new XPathQueryHolder();
        }
    }

    /**
     * This method sets the namespace which will determinate the {@code XPathQueryHolder} to use. The content of the Transform element is ignored.
     *
     * @param element
     */
    public void recursiveNamespaceBrowser(final Element element) {

        for (int ii = 0; ii < element.getChildNodes().getLength(); ii++) {

            final Node node = element.getChildNodes().item(ii);
            if (node.getNodeType() == Node.ELEMENT_NODE) {

                final Element childElement = (Element) node;
                final String namespaceURI = childElement.getNamespaceURI();
                // final String tagName = childElement.getTagName();
                final String localName = childElement.getLocalName();
                // final String nodeName = childElement.getNodeName();
                // System.out.println(tagName + "-->" + namespaceURI);
                if (XPathQueryHolder.XMLE_TRANSFORM.equals(localName)
                        && javax.xml.crypto.dsig.XMLSignature.XMLNS.equals(namespaceURI)) {
                    continue;
                } else if (XPathQueryHolder.XMLE_QUALIFYING_PROPERTIES.equals(localName)) {

                    setXPathQueryHolder(namespaceURI);
                    return;
                }
                recursiveNamespaceBrowser(childElement);
            }
        }
    }

    private void setXPathQueryHolder(final String namespaceURI) {

        for (final XPathQueryHolder xPathQueryHolder : xPathQueryHolders) {

            final boolean canUseThisXPathQueryHolder = xPathQueryHolder.canUseThisXPathQueryHolder(namespaceURI);
            if (canUseThisXPathQueryHolder) {

                this.xPathQueryHolder = xPathQueryHolder;
            }
        }
    }

    /**
     * This getter returns the {@code XPathQueryHolder}
     *
     * @return
     */
    public XPathQueryHolder getXPathQueryHolder() {
        return xPathQueryHolder;
    }

    /**
     * This method returns the certificate pool used by this instance to handle encapsulated certificates.
     *
     * @return
     */
    public CertificatePool getCertPool() {
        return certPool;
    }

    /**
     * Returns the w3c.dom encapsulated signature element.
     *
     * @return the signatureElement
     */
    public Element getSignatureElement() {

        return signatureElement;
    }

    @Override
    public SignatureForm getSignatureForm() {

        return SignatureForm.XAdES;
    }

    @Override
    public EncryptionAlgorithm getEncryptionAlgorithm() {

        final String xmlName = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_SIGNATURE_METHOD)
                .getAttribute(XMLE_ALGORITHM);
        final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forXML(xmlName, null);
        if (signatureAlgorithm == null) {
            return null;
        }
        return signatureAlgorithm.getEncryptionAlgorithm();
    }

    @Override
    public DigestAlgorithm getDigestAlgorithm() {

        final String xmlName = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_SIGNATURE_METHOD)
                .getAttribute(XMLE_ALGORITHM);
        final SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.forXML(xmlName, null);
        if (signatureAlgorithm == null) {
            return null;
        }
        return signatureAlgorithm.getDigestAlgorithm();
    }

    @Override
    public XAdESCertificateSource getCertificateSource() {

        if (certificatesSource == null) {
            certificatesSource = new XAdESCertificateSource(signatureElement, xPathQueryHolder, certPool);
        }
        return certificatesSource;
    }

    /**
     * This method resets the source of certificates. It must be called when any certificate is added to the KeyInfo or CertificateValues.
     */
    public void resetCertificateSource() {

        certificatesSource = null;
    }

    @Override
    public OfflineCRLSource getCRLSource() {

        if (offlineCRLSource == null) {
            offlineCRLSource = new XAdESCRLSource(signatureElement, xPathQueryHolder);
        }
        return offlineCRLSource;
    }

    @Override
    public OfflineOCSPSource getOCSPSource() {

        if (offlineOCSPSource == null) {
            offlineOCSPSource = new XAdESOCSPSource(signatureElement, xPathQueryHolder);
        }
        return offlineOCSPSource;
    }

    /**
     * This method resets the sources of the revocation data. It must be called when -LT level is created.
     */
    public void resetRevocationSources() {

        offlineCRLSource = null;
        offlineOCSPSource = null;
    }

    @Override
    public CandidatesForSigningCertificate getCandidatesForSigningCertificate() {

        if (candidatesForSigningCertificate != null) {
            return candidatesForSigningCertificate;
        }
        candidatesForSigningCertificate = new CandidatesForSigningCertificate();
        /**
         * 5.1.4.1 XAdES processing<br>
         * <i>Candidates for the signing certificate extracted from ds:KeyInfo element</i> shall be checked against all references present in the
         * ds:SigningCertificate property, if present, since one of these references shall be a reference to the signing certificate.
         */
        final XAdESCertificateSource certSource = getCertificateSource();
        for (final CertificateToken certificateToken : certSource.getKeyInfoCertificates()) {

            final CertificateValidity certificateValidity = new CertificateValidity(certificateToken);
            candidatesForSigningCertificate.add(certificateValidity);
        }
        return candidatesForSigningCertificate;
    }

    @Override
    public void checkSigningCertificate() {

        final CandidatesForSigningCertificate candidates = getCandidatesForSigningCertificate();
        /**
         * The ../SignedProperties/SignedSignatureProperties/SigningCertificate element MAY contain references and digests values of other
         * certificates (that MAY form a chain up to the point of trust).
         */
        boolean isEn319132 = false;
        NodeList list = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_SIGNING_CERTIFICATE_CERT);
        int length = list.getLength();
        if (length == 0) {
            list = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_SIGNING_CERTIFICATE_CERT_V2);
            length = list.getLength();
            isEn319132 = true;
        }
        if (length == 0) {
            final CertificateValidity theCertificateValidity = candidates.getTheCertificateValidity();
            final CertificateToken certificateToken = theCertificateValidity == null ? null
                    : theCertificateValidity.getCertificateToken();
            // The check need to be done at the level of KeyInfo
            for (final Reference reference : references) {

                final String uri = reference.getURI();
                if (!uri.startsWith("#")) {
                    continue;
                }

                final String id = uri.substring(1);
                final Element element = signatureElement.getOwnerDocument().getElementById(id);
                // final Element element =
                // DSSXMLUtils.getElement(signatureElement, "");
                if (!hasSignatureAsParent(element)) {

                    continue;
                }
                if ((certificateToken != null) && id.equals(certificateToken.getXmlId())) {

                    theCertificateValidity.setSigned(element.getNodeName());
                    return;
                }
            }
        }
        // This Map contains the list of the references to the certificate which
        // were already checked and which correspond to a certificate.
        Map<Element, Boolean> alreadyProcessedElements = new HashMap<Element, Boolean>();

        final List<CertificateValidity> certificateValidityList = candidates.getCertificateValidityList();
        for (final CertificateValidity certificateValidity : certificateValidityList) {

            final CertificateToken certificateToken = certificateValidity.getCertificateToken();
            for (int ii = 0; ii < length; ii++) {

                certificateValidity.setAttributePresent(true);
                final Element element = (Element) list.item(ii);
                if (alreadyProcessedElements.containsKey(element)) {
                    continue;
                }
                final Element certDigestElement = DSSXMLUtils.getElement(element,
                        xPathQueryHolder.XPATH__CERT_DIGEST);
                certificateValidity.setDigestPresent(certDigestElement != null);

                final Element digestMethodElement = DSSXMLUtils.getElement(certDigestElement,
                        xPathQueryHolder.XPATH__DIGEST_METHOD);
                if (digestMethodElement == null) {
                    continue;
                }
                final String xmlAlgorithmName = digestMethodElement.getAttribute(XMLE_ALGORITHM);
                // The default algorithm is used in case of bad encoded
                // algorithm name
                final DigestAlgorithm digestAlgorithm = DigestAlgorithm.forXML(xmlAlgorithmName,
                        DigestAlgorithm.SHA1);

                final Element digestValueElement = DSSXMLUtils.getElement(element,
                        xPathQueryHolder.XPATH__CERT_DIGEST_DIGEST_VALUE);
                if (digestValueElement == null) {
                    continue;
                }
                // That must be a binary comparison
                final byte[] storedBase64DigestValue = DSSUtils
                        .base64StringToBase64Binary(digestValueElement.getTextContent());

                /**
                 * Step 1:<br>
                 * Take the first child of the property and check that the content of ds:DigestValue matches the result of digesting <i>the candidate
                 * for</i> the signing certificate with the algorithm indicated in ds:DigestMethod. If they do not match, take the next child and
                 * repeat this step until a matching child element has been found or all children of the element have been checked. If they do match,
                 * continue with step 2. If the last element is reached without finding any match, the validation of this property shall be taken as
                 * failed and INVALID/FORMAT_FAILURE is returned.
                 */
                final byte[] digest = DSSUtils.digest(digestAlgorithm, certificateToken.getEncoded());
                final byte[] recalculatedBase64DigestValue = Base64.encodeBase64(digest);
                certificateValidity.setDigestEqual(false);
                BigInteger serialNumber = new BigInteger("0");
                if (Arrays.equals(recalculatedBase64DigestValue, storedBase64DigestValue)) {
                    X500Principal issuerName = null;
                    if (isEn319132) {
                        final Element issuerNameEl = DSSXMLUtils.getElement(element,
                                xPathQueryHolder.XPATH__X509_ISSUER_V2);
                        if (issuerNameEl != null) {
                            final String textContent = issuerNameEl.getTextContent();
                            ASN1InputStream is = new ASN1InputStream(Base64.decodeBase64(textContent));
                            ASN1Sequence seq = null;
                            try {
                                seq = (ASN1Sequence) is.readObject();
                                is.close();
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                            //IssuerAndSerialNumber issuerAndSerial = new IssuerAndSerialNumber(seq);
                            IssuerAndSerialNumber issuerAndSerial = IssuerAndSerialNumber.getInstance(seq);
                            issuerName = new X500Principal(issuerAndSerial.getName().toString());
                            serialNumber = issuerAndSerial.getSerialNumber().getValue();
                        }
                    } else {
                        final Element issuerNameEl = DSSXMLUtils.getElement(element,
                                xPathQueryHolder.XPATH__X509_ISSUER_NAME);
                        // This can be allayed when the distinguished name is not
                        // correctly encoded
                        // final String textContent =
                        // DSSUtils.unescapeMultiByteUtf8Literals(issuerNameEl.getTextContent());
                        final String textContent = issuerNameEl.getTextContent();

                        issuerName = DSSUtils.getX500PrincipalOrNull(textContent);
                    }
                    final X500Principal candidateIssuerName = certificateToken.getIssuerX500Principal();

                    // final boolean issuerNameMatches =
                    // candidateIssuerName.equals(issuerName);
                    final boolean issuerNameMatches = DSSUtils.x500PrincipalAreEquals(candidateIssuerName,
                            issuerName);
                    if (!issuerNameMatches) {

                        final String c14nCandidateIssuerName = candidateIssuerName.getName(X500Principal.CANONICAL);
                        LOG.info("candidateIssuerName: " + c14nCandidateIssuerName);
                        final String c14nIssuerName = issuerName == null ? ""
                                : issuerName.getName(X500Principal.CANONICAL);
                        LOG.info("issuerName         : " + c14nIssuerName);
                    }

                    if (!isEn319132) {
                        final Element serialNumberEl = DSSXMLUtils.getElement(element,
                                xPathQueryHolder.XPATH__X509_SERIAL_NUMBER);
                        final String serialNumberText = serialNumberEl.getTextContent();
                        // serial number can contain leading and trailing whitespace.
                        serialNumber = new BigInteger(serialNumberText.trim());
                    }
                    final BigInteger candidateSerialNumber = certificateToken.getSerialNumber();
                    final boolean serialNumberMatches = candidateSerialNumber.equals(serialNumber);

                    certificateValidity.setDigestEqual(true);
                    certificateValidity.setSerialNumberEqual(serialNumberMatches);
                    certificateValidity.setDistinguishedNameEqual(issuerNameMatches);
                    // The certificate was identified
                    alreadyProcessedElements.put(element, true);
                    // If the signing certificate is not set yet then it must be
                    // done now. Actually if the signature is tempered then the
                    // method checkSignatureIntegrity cannot set the signing
                    // certificate.
                    if (candidates.getTheCertificateValidity() == null) {

                        candidates.setTheCertificateValidity(certificateValidity);
                    }
                    break;
                }
            }
        }
    }

    /**
     * Checks if the given {@code Element} has as parent the current signature. This is the security check.
     *
     * @param element
     *            the element to be checked (can be null)
     * @return true if the given element has as parent the current signature element, false otherwise
     */
    private boolean hasSignatureAsParent(final Element element) {

        if (element == null) {
            return false;
        }
        Node node = element;
        String nodeName = node.getNodeName();
        if (XPathQueryHolder.XMLE_X509CERTIFICATE.equals(nodeName)) {

            node = node.getParentNode();
            if (node == null) {
                return false;
            }
            nodeName = node.getNodeName();

        }
        if (XPathQueryHolder.XMLE_X509DATA.equals(nodeName)) {

            node = node.getParentNode();
            if (node == null) {
                return false;
            }
            nodeName = node.getNodeName();
        }
        if (XPathQueryHolder.XMLE_KEYINFO.equals(nodeName)) {

            node = node.getParentNode();
            if (node == null) {
                return false;
            }
        }
        if (!node.equals(signatureElement)) {
            return false;
        }
        return true;
    }

    @Override
    public Date getSigningTime() {

        final Element signingTimeEl = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_SIGNING_TIME);
        if (signingTimeEl == null) {
            return null;
        }
        final String text = signingTimeEl.getTextContent();
        return DSSXMLUtils.getDate(text);
    }

    @Override
    public SignaturePolicy getPolicyId() {

        final Element policyIdentifier = DSSXMLUtils.getElement(signatureElement,
                xPathQueryHolder.XPATH_SIGNATURE_POLICY_IDENTIFIER);
        if (policyIdentifier != null) {

            // There is a policy
            final Element policyId = DSSXMLUtils.getElement(policyIdentifier, xPathQueryHolder.XPATH__POLICY_ID);
            if (policyId != null) {
                // Explicit policy
                final String policyIdString = policyId.getTextContent();
                final SignaturePolicy signaturePolicy = new SignaturePolicy(policyIdString);
                final Node policyDigestMethod = DSSXMLUtils.getNode(policyIdentifier,
                        xPathQueryHolder.XPATH__POLICY_DIGEST_METHOD);
                final String policyDigestMethodString = policyDigestMethod.getTextContent();
                final DigestAlgorithm digestAlgorithm = DigestAlgorithm.forXML(policyDigestMethodString);
                signaturePolicy.setDigestAlgorithm(digestAlgorithm);
                final Element policyDigestValue = DSSXMLUtils.getElement(policyIdentifier,
                        xPathQueryHolder.XPATH__POLICY_DIGEST_VALUE);
                final String digestValue = policyDigestValue.getTextContent().trim();
                signaturePolicy.setDigestValue(digestValue);
                final Element policyUrl = DSSXMLUtils.getElement(policyIdentifier,
                        xPathQueryHolder.XPATH__POLICY_SPURI);
                if (policyUrl != null) {
                    signaturePolicy.setUrl(policyUrl.getTextContent().trim());
                }
                return signaturePolicy;
            } else {
                // Implicit policy
                final Element signaturePolicyImplied = DSSXMLUtils.getElement(policyIdentifier,
                        xPathQueryHolder.XPATH__SIGNATURE_POLICY_IMPLIED);
                if (signaturePolicyImplied != null) {
                    return new SignaturePolicy();
                }
            }
        }
        return null;
    }

    @Override
    public SignatureProductionPlace getSignatureProductionPlace() {

        NodeList nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_PRODUCTION_PLACE);
        if ((nodeList.getLength() == 0) || (nodeList.item(0) == null)) {
            nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_PRODUCTION_PLACE_V2);
            if ((nodeList.getLength() == 0) || (nodeList.item(0) == null)) {
                return null;
            }
        }
        final SignatureProductionPlace signatureProductionPlace = new SignatureProductionPlace();
        final NodeList list = nodeList.item(0).getChildNodes();
        for (int ii = 0; ii < list.getLength(); ii++) {

            final Node item = list.item(ii);
            final String name = item.getLocalName();
            final String nodeValue = item.getTextContent();
            if (XPathQueryHolder.XMLE_CITY.equals(name)) {

                signatureProductionPlace.setCity(nodeValue);
            } else if (XPathQueryHolder.XMLE_STATE_OR_PROVINCE.equals(name)) {

                signatureProductionPlace.setStateOrProvince(nodeValue);
            } else if (XPathQueryHolder.XMLE_POSTAL_CODE.equals(name)) {

                signatureProductionPlace.setPostalCode(nodeValue);
            } else if (XPathQueryHolder.XMLE_COUNTRY_NAME.equals(name)) {

                signatureProductionPlace.setCountryName(nodeValue);
            } else if (XPathQueryHolder.XMLE_STREET_ADDRESS.equals(name)) {

                signatureProductionPlace.setStreetAddress(nodeValue);
            }
        }
        return signatureProductionPlace;
    }

    @Override
    public String[] getClaimedSignerRoles() {

        NodeList nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_CLAIMED_ROLE);
        if (nodeList.getLength() == 0) {
            nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_CLAIMED_ROLE_V2);
            if (nodeList.getLength() == 0) {
                return null;
            }
        }
        final String[] roles = new String[nodeList.getLength()];
        for (int ii = 0; ii < nodeList.getLength(); ii++) {

            roles[ii] = nodeList.item(ii).getTextContent();
        }
        return roles;
    }

    @Override
    public List<CertifiedRole> getCertifiedSignerRoles() {

        /**
         * <!-- Start EncapsulatedPKIDataType--> <xsd:element name="EncapsulatedPKIData" type="EncapsulatedPKIDataType"/> <xsd:complexType
         * name="EncapsulatedPKIDataType"> <xsd:simpleContent> <xsd:extension base="xsd:base-64Binary"> <xsd:attribute name="Id" type="xsd:ID"
         * use="optional"/> <xsd:attribute name="Encoding" type="xsd:anyURI" use="optional"/> </xsd:extension> </xsd:simpleContent> </xsd:complexType>
         * <!-- End EncapsulatedPKIDataType -->
         */
        NodeList nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_CERTIFIED_ROLE);
        if (nodeList.getLength() == 0) {
            nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_CERTIFIED_ROLE_V2);
            if (nodeList.getLength() == 0) {
                return null;
            }
        }
        final List<CertifiedRole> roles = new ArrayList<CertifiedRole>();
        for (int ii = 0; ii < nodeList.getLength(); ii++) {

            final Element certEl = (Element) nodeList.item(ii);
            final String textContent = certEl.getTextContent();
            final CertificateToken x509Certificate = DSSUtils.loadCertificateFromBase64EncodedString(textContent);
            if (!roles.contains(x509Certificate)) {

                roles.add(new CertifiedRole());
            }
        }
        return roles;
    }

    @Override
    public String getContentType() {

        return "text/xml";
    }

    @Override
    public String getContentIdentifier() {
        return null;
    }

    @Override
    public String getContentHints() {
        return null;
    }

    /**
     * This method creates {@code TimestampToken} based on provided parameters.
     *
     * @param timestampElement
     *            contains the encapsulated timestamp
     * @param timestampType
     *            {@code TimestampType}
     * @return {@code TimestampToken} of the given type
     * @throws DSSException
     */
    private TimestampToken makeTimestampToken(final Element timestampElement, final TimestampType timestampType)
            throws DSSException {

        final Element timestampTokenNode = DSSXMLUtils.getElement(timestampElement,
                xPathQueryHolder.XPATH__ENCAPSULATED_TIMESTAMP);
        if (timestampTokenNode == null) {

            // TODO (09/11/2014): The error message must be propagated to the
            // validation report
            LOG.warn("The timestamp (" + timestampType.name() + ") cannot be extracted from the signature!");
            return null;

        }
        final String base64EncodedTimestamp = timestampTokenNode.getTextContent();
        final TimeStampToken timeStampToken = createTimeStampToken(base64EncodedTimestamp);
        final TimestampToken timestampToken = new TimestampToken(timeStampToken, timestampType, certPool);
        timestampToken.setHashCode(timestampElement.hashCode());
        setTimestampCanonicalizationMethod(timestampElement, timestampToken);

        // TODO: timestampToken.setIncludes(element.getIncludes)...
        // final NodeList includes =
        // timestampTokenNode.getElementsByTagName("Include");
        // for (int i = 0; i < includes.getLength(); ++i) {
        // timestampToken.getTimestampIncludes().add(new
        // TimestampInclude(includes.item(i).getBaseURI(),
        // includes.item(i).getAttributes()));
        // }
        return timestampToken;
    }

    /**
     * This method generates a bouncycastle {@code TimeStampToken} based on base 64 encoded {@code String}.
     *
     * @param base64EncodedTimestamp
     * @return bouncycastle {@code TimeStampToken}
     * @throws DSSException
     */
    private TimeStampToken createTimeStampToken(final String base64EncodedTimestamp) throws DSSException {
        try {
            final byte[] tokenBytes = Base64.decodeBase64(base64EncodedTimestamp);
            final CMSSignedData signedData = new CMSSignedData(tokenBytes);
            return new TimeStampToken(signedData);
        } catch (Exception e) {
            throw new DSSException(e);
        }
    }

    public Element getSignatureValue() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_SIGNATURE_VALUE);
    }

    public Element getObject() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_OBJECT);
    }

    /**
     * This method returns the list of ds:Object elements for the current signature element.
     *
     * @return
     */
    public NodeList getObjects() {
        return DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_OBJECT);
    }

    public Element getCompleteCertificateRefs() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_COMPLETE_CERTIFICATE_REFS);
    }

    public Element getCompleteRevocationRefs() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_COMPLETE_REVOCATION_REFS);
    }

    public NodeList getSigAndRefsTimeStamp() {
        NodeList nodeList = DSSXMLUtils.getNodeList(signatureElement,
                xPathQueryHolder.XPATH_SIG_AND_REFS_TIMESTAMP);
        if (nodeList == null || nodeList.getLength() == 0) {
            nodeList = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_SIG_AND_REFS_TIMESTAMP_V2);
        }
        return nodeList;
    }

    public Element getCertificateValues() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_CERTIFICATE_VALUES);
    }

    public Element getRevocationValues() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_REVOCATION_VALUES);
    }

    /**
     * Checks the presence of ... segment in the signature, what is the proof -B profile existence
     *
     * @return
     */
    public boolean hasBProfile() {
        return DSSXMLUtils.isNotEmpty(signatureElement, xPathQueryHolder.XPATH_SIGNED_SIGNATURE_PROPERTIES);
    }

    /**
     * Checks the presence of SignatureTimeStamp segment in the signature, what is the proof -T profile existence
     *
     * @return
     */
    public boolean hasTProfile() {
        return DSSXMLUtils.isNotEmpty(signatureElement, xPathQueryHolder.XPATH_SIGNATURE_TIMESTAMP);
    }

    /**
     * Checks the presence of CompleteCertificateRefs & CompleteRevocationRefs segments in the signature, what is the proof -C profile existence
     *
     * @return
     */
    public boolean hasCProfile() {
        final boolean certRefs = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_COMPLETE_CERTIFICATE_REFS);
        final boolean revocationRefs = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_COMPLETE_REVOCATION_REFS);
        return certRefs || revocationRefs;
    }

    /**
     * Checks the presence of SigAndRefsTimeStamp segment in the signature, what is the proof -X profile existence
     *
     * @return true if the -X extension is present
     */
    public boolean hasXProfile() {
        return DSSXMLUtils.isNotEmpty(signatureElement, xPathQueryHolder.XPATH_SIG_AND_REFS_TIMESTAMP);
    }

    /**
     * Checks the presence of CertificateValues and RevocationValues segments in the signature, what is the proof -LT (or -XL) profile existence
     *
     * @return true if -LT (or -XL) extension is present
     */
    public boolean hasLTProfile() {
        final boolean certValues = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_CERTIFICATE_VALUES);

        final boolean revocationValues = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_REVOCATION_VALUES);
        boolean notEmptyCRL = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_ENCAPSULATED_CRL_VALUE);
        boolean notEmptyOCSP = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_ENCAPSULATED_OCSP_VALUE);

        boolean isLTProfile = revocationValues && (notEmptyCRL || notEmptyOCSP);
        if (!isLTProfile && certValues) {
            isLTProfile = hasTProfile();
        }

        return isLTProfile;
        //return certValues || (revocationValues && (notEmptyCRL || notEmptyOCSP));
    }

    /**
     * Checks the presence of CertificateValues and RevocationValues segments in the signature, what is the proof -LTA (or -A) profile existence
     *
     * @return true if -LTA (or -A) extension is present
     */
    public boolean hasLTAProfile() {
        final boolean archiveTimestamp = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_ARCHIVE_TIMESTAMP);
        final boolean archiveTimestamp141 = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_ARCHIVE_TIMESTAMP_141);
        final boolean archiveTimestampV2 = DSSXMLUtils.isNotEmpty(signatureElement,
                xPathQueryHolder.XPATH_ARCHIVE_TIMESTAMP_V2);
        return archiveTimestamp || archiveTimestamp141 || archiveTimestampV2;
    }

    /**
     * Utility method to add content timestamps.
     *
     * @param timestampTokens
     * @param nodes
     * @param timestampType
     *            {@code TimestampType}
     */
    public void addContentTimestamps(final List<TimestampToken> timestampTokens, final NodeList nodes,
            TimestampType timestampType) {

        for (int ii = 0; ii < nodes.getLength(); ii++) {

            final Node node = nodes.item(ii);
            if (node.getNodeType() != Node.ELEMENT_NODE) {
                continue;
            }
            final Element element = (Element) node;
            final TimestampToken timestampToken = makeTimestampToken(element, timestampType);
            if (timestampToken != null) {
                continue;
            }
            if (timestampToken.getTimestampIncludes() == null) {
                timestampToken.setTimestampIncludes(new ArrayList<TimestampInclude>());
            }
            final NodeList timestampIncludes = DSSXMLUtils.getNodeList(element, xPathQueryHolder.XPATH__INCLUDE);
            for (int jj = 0; jj < timestampIncludes.getLength(); jj++) {

                final Element include = (Element) timestampIncludes.item(jj);
                final String uri = include.getAttribute("URI").substring(1); // '#'
                // is
                // removed
                timestampToken.getTimestampIncludes()
                        .add(new TimestampInclude(uri, include.getAttribute("referencedData")));
            }
            timestampTokens.add(timestampToken);
        }
    }

    @Override
    public byte[] getContentTimestampData(final TimestampToken timestampToken) {

        switch (timestampToken.getTimeStampType()) {
        case INDIVIDUAL_DATA_OBJECTS_TIMESTAMP:
            return getIndividualDataObjectsTimestampData(timestampToken);
        case ALL_DATA_OBJECTS_TIMESTAMP:
            return getAllDataObjectsTimestampData(timestampToken);
        default:
            return null;
        }
    }

    /**
     * See ETSI TS 101 903 v1.4.1, clause G.2.2.16.1.2
     *
     * @param timestampToken
     * @return
     */
    public byte[] getIndividualDataObjectsTimestampData(final TimestampToken timestampToken) {

        // TODO: check whether a warning would be more appropriate
        if (!checkTimestampTokenIncludes(timestampToken)) {
            throw new DSSException("The Included referencedData attribute is either not present or set to false!");
        }
        if (references.size() == 0) {
            throw new DSSException("The method 'checkSignatureIntegrity' must be invoked first!");
        }
        // get first include element
        // check coherence of the value of the not-fragment part of the URI
        // within its URI attribute according to the rules stated in 7.1.4.3.1
        // de-reference the URI according to the rules in 7.1.4.3.1
        // check that retrieved element is actually a ds:Reference element of
        // the ds:SignedInfo of the qualified signature and that its Type
        // attribute is not SignedProperties
        // if result is node-set, canonicalize it using the indicated
        // canonicalizationMethod element of the property || use standard canon.
        // method
        // concatenate the resulting bytes in an octet stream
        // repeat for all subsequent include elements, in order of appearance,
        // within the time-stamp container
        // return digest of resulting byte stream using the algorithm indicated
        // in the time-stamp token

        // get include elements from signature
        List<TimestampInclude> includes = timestampToken.getTimestampIncludes();

        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

        for (TimestampInclude include : includes) {
            // retrieve reference element
            // -> go through references and check for one whose URI matches the
            // URI of include
            for (final Reference reference : references) {
                String id = include.getURI();

                if (reference.getId().equals(id)) {
                    try {
                        final byte[] referencedBytes = reference.getReferencedBytes();
                        outputStream.write(referencedBytes);
                    } catch (IOException e) {
                        throw new DSSException(e);
                    } catch (ReferenceNotInitializedException e) {
                        throw new DSSException(e);
                    } catch (XMLSignatureException e) {
                        throw new DSSException(e);
                    }
                }
            }
        }
        byte[] octetStream = outputStream.toByteArray();
        return octetStream;
    }

    /**
     * See ETSI TS 101 903 v1.4.1, clause G.2.2.16.1.1
     *
     * Retrieves the data from {@code TimeStampToken} of type AllDataObjectsTimestampData
     *
     * @param timestampToken
     * @return a {@code byte} array containing the concatenated data from all reference elements of type differing from SignedProperties
     */
    public byte[] getAllDataObjectsTimestampData(final TimestampToken timestampToken) {

        // TODO: check whether a warning would be more appropriate
        if (!checkTimestampTokenIncludes(timestampToken)) {
            throw new DSSException("The Included referencedData attribute is either not present or set to false!");
        }
        if (references.size() == 0) {
            throw new DSSException("The method 'checkSignatureIntegrity' must be invoked first!");
        }
        final ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        for (final Reference reference : references) {

            // Take, the first ds:Reference element within ds:SignedInfo if and
            // only if the Type attribute does not
            // have the value "http://uri.etsi.org/01903#SignedProperties".
            if (!xPathQueryHolder.XADES_SIGNED_PROPERTIES.equals(reference.getType())) {

                try {

                    final byte[] referencedBytes = reference.getReferencedBytes();
                    outputStream.write(referencedBytes);
                } catch (IOException e) {
                    throw new DSSException(e);
                } catch (ReferenceNotInitializedException e) {
                    throw new DSSException(e);
                } catch (XMLSignatureException e) {
                    throw new DSSException(e);
                }
            }
        }
        // compute digest of resulting octet stream using algorithm indicated in
        // the time-stamp token
        // -> digest is computed in TimestampToken verification/match
        // return the computed digest
        byte[] toTimestampBytes = outputStream.toByteArray();
        if (LOG.isTraceEnabled()) {
            LOG.trace("AllDataObjectsTimestampData bytes: " + new String(toTimestampBytes));
        }
        return toTimestampBytes;
    }

    private List<TimestampReference> getSignatureTimestampedReferences() {

        final List<TimestampReference> references = new ArrayList<TimestampReference>();
        final TimestampReference signatureReference = getSignatureTimestampReference();
        references.add(signatureReference);
        final List<TimestampReference> signingCertificateTimestampReferences = getSigningCertificateTimestampReferences();
        references.addAll(signingCertificateTimestampReferences);
        return references;
    }

    private List<TimestampReference> getSigningCertificateTimestampReferences() {

        if (signingCertificateTimestampReferences == null) {

            signingCertificateTimestampReferences = new ArrayList<TimestampReference>();
            final NodeList list = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_CERT_DIGEST);
            for (int jj = 0; jj < list.getLength(); jj++) {

                final Element element = (Element) list.item(jj);
                final TimestampReference signingCertReference = createCertificateTimestampReference(element);
                signingCertificateTimestampReferences.add(signingCertReference);
            }
        }
        return signingCertificateTimestampReferences;
    }

    /**
     * This method ensures that all Include elements referring to the Reference elements have a referencedData attribute, which is set to "true". In
     * case one of these Include elements has its referenceData set to false, the method returns false
     *
     * @param timestampToken
     * @retun
     */
    public boolean checkTimestampTokenIncludes(final TimestampToken timestampToken) {

        final List<TimestampInclude> timestampIncludes = timestampToken.getTimestampIncludes();
        for (final TimestampInclude timestampInclude : timestampIncludes) {
            if (!timestampInclude.isReferencedData()) {
                return false;
            }
        }
        return true;
    }

    @Override
    public List<TimestampToken> getContentTimestamps() {

        if (contentTimestamps == null) {
            makeTimestampTokens();
        }
        return contentTimestamps;
    }

    @Override
    public List<TimestampToken> getSignatureTimestamps() {

        if (signatureTimestamps == null) {
            makeTimestampTokens();
        }
        return signatureTimestamps;
    }

    @Override
    public List<TimestampToken> getTimestampsX1() {

        if (sigAndRefsTimestamps == null) {
            makeTimestampTokens();
        }
        return sigAndRefsTimestamps;
    }

    @Override
    public List<TimestampToken> getTimestampsX2() {

        if (refsOnlyTimestamps == null) {
            makeTimestampTokens();
        }
        return refsOnlyTimestamps;
    }

    @Override
    public List<TimestampToken> getArchiveTimestamps() {

        if (archiveTimestamps == null) {
            makeTimestampTokens();
        }
        return archiveTimestamps;
    }

    /**
     * This method must not be called more than once.
     */
    private void makeTimestampTokens() {

        contentTimestamps = new ArrayList<TimestampToken>();
        signatureTimestamps = new ArrayList<TimestampToken>();
        refsOnlyTimestamps = new ArrayList<TimestampToken>();
        sigAndRefsTimestamps = new ArrayList<TimestampToken>();
        archiveTimestamps = new ArrayList<TimestampToken>();
        // TODO (20/12/2014): Browse in the physical order
        final NodeList allDataObjectsTimestamps = DSSXMLUtils.getNodeList(signatureElement,
                xPathQueryHolder.XPATH_ALL_DATA_OBJECTS_TIMESTAMP);
        addContentTimestamps(contentTimestamps, allDataObjectsTimestamps, TimestampType.ALL_DATA_OBJECTS_TIMESTAMP);
        final NodeList individualDataObjectsTimestampsNodes = DSSXMLUtils.getNodeList(signatureElement,
                xPathQueryHolder.XPATH_INDIVIDUAL_DATA_OBJECTS_TIMESTAMP);
        addContentTimestamps(contentTimestamps, individualDataObjectsTimestampsNodes,
                TimestampType.INDIVIDUAL_DATA_OBJECTS_TIMESTAMP);

        final Element unsignedSignaturePropertiesDom = getUnsignedSignaturePropertiesDom();
        if (unsignedSignaturePropertiesDom == null) {
            return;
        }
        final List<String> timestampedTimestamps = new ArrayList<String>();
        final NodeList unsignedProperties = unsignedSignaturePropertiesDom.getChildNodes();
        for (int ii = 0; ii < unsignedProperties.getLength(); ii++) {

            final Node node = unsignedProperties.item(ii);
            if (node.getNodeType() != Node.ELEMENT_NODE) { // This can happened
                // when there is a
                // blank line
                // between tags.
                continue;
            }
            TimestampToken timestampToken;
            final String localName = node.getLocalName();
            if (XMLE_SIGNATURE_TIME_STAMP.equals(localName)) {

                timestampToken = makeTimestampToken((Element) node, TimestampType.SIGNATURE_TIMESTAMP);
                if (timestampToken == null) {
                    continue;
                }
                timestampToken.setTimestampedReferences(getSignatureTimestampedReferences());
                signatureTimestamps.add(timestampToken);
            } else if (XMLE_REFS_ONLY_TIME_STAMP.equals(localName)
                    || XPathQueryHolder.XMLE_REFS_ONLY_TIME_STAMP_V2.equals(localName)) {

                timestampToken = makeTimestampToken((Element) node,
                        TimestampType.VALIDATION_DATA_REFSONLY_TIMESTAMP);
                if (timestampToken == null) {
                    continue;
                }
                timestampToken.setTimestampedReferences(getTimestampedReferences());
                refsOnlyTimestamps.add(timestampToken);
            } else if (XMLE_SIG_AND_REFS_TIME_STAMP.equals(localName)
                    || XPathQueryHolder.XMLE_SIG_AND_REFS_TIME_STAMP_V2.equals(localName)) {

                timestampToken = makeTimestampToken((Element) node, TimestampType.VALIDATION_DATA_TIMESTAMP);
                if (timestampToken == null) {
                    continue;
                }
                final List<TimestampReference> references = getSignatureTimestampedReferences();
                references.addAll(getTimestampedReferences());
                timestampToken.setTimestampedReferences(references);
                sigAndRefsTimestamps.add(timestampToken);
            } else if (isArchiveTimestamp(localName)) {

                timestampToken = makeTimestampToken((Element) node, TimestampType.ARCHIVE_TIMESTAMP);
                if (timestampToken == null) {
                    continue;
                }
                final ArchiveTimestampType archiveTimestampType = getArchiveTimestampType(node, localName);
                timestampToken.setArchiveTimestampType(archiveTimestampType);

                final List<TimestampReference> references = getSignatureTimestampedReferences();
                for (final String timestampId : timestampedTimestamps) {

                    final TimestampReference signatureReference_ = new TimestampReference(timestampId);
                    references.add(signatureReference_);
                }
                references.addAll(getTimestampedReferences());
                final List<CertificateToken> encapsulatedCertificates = getCertificateSource()
                        .getEncapsulatedCertificates();
                for (final CertificateToken certificateToken : encapsulatedCertificates) {

                    final TimestampReference certificateTimestampReference = createCertificateTimestampReference(
                            certificateToken);
                    if (!references.contains(certificateTimestampReference)) {
                        references.add(certificateTimestampReference);
                    }
                }
                // TODO (20/12/2014): ValidationData references to be added
                timestampToken.setTimestampedReferences(references);
                archiveTimestamps.add(timestampToken);
            } else {
                continue;
            }
            timestampedTimestamps.add(String.valueOf(timestampToken.getDSSId()));
        }
    }

    private ArchiveTimestampType getArchiveTimestampType(final Node node, final String localName) {

        if (XPathQueryHolder.XMLE_ARCHIVE_TIME_STAMP_V2.equals(localName)) {
            return ArchiveTimestampType.XAdES_141_V2;
        } else if (XPathQueryHolder.XMLE_ARCHIVE_TIME_STAMP.equals(localName)) {

            final String namespaceURI = node.getNamespaceURI();
            if (XAdESNamespaces.XAdES141.equals(namespaceURI)) {
                return ArchiveTimestampType.XAdES_141;
            }
        }
        return ArchiveTimestampType.XAdES;
    }

    private TimestampReference getSignatureTimestampReference() {

        final TimestampReference signatureReference = new TimestampReference(getId());
        return signatureReference;
    }

    private void setTimestampCanonicalizationMethod(final Element timestampElement,
            final TimestampToken timestampToken) {

        final Element canonicalizationMethodElement = DSSXMLUtils.getElement(timestampElement,
                xPathQueryHolder.XPATH__CANONICALIZATION_METHOD);
        String canonicalizationMethod = DEFAULT_TIMESTAMP_VALIDATION_CANONICALIZATION_METHOD;
        if (canonicalizationMethodElement != null) {
            canonicalizationMethod = canonicalizationMethodElement.getAttribute(XMLE_ALGORITHM);
        }
        timestampToken.setCanonicalizationMethod(canonicalizationMethod);
    }

    /*
     * Returns an unmodifiable list of all certificate tokens encapsulated in the signature
     *
     * @see eu.europa.esig.dss.validation.AdvancedSignature#getCertificates()
     */
    @Override
    public List<CertificateToken> getCertificates() {

        return getCertificateSource().getCertificates();
    }

    /*
     * Returns the list of certificates encapsulated in the KeyInfo segment
     */
    public List<CertificateToken> getKeyInfoCertificates() {

        return getCertificateSource().getKeyInfoCertificates();
    }

    /*
     * Returns the list of certificates encapsulated in the KeyInfo segment
     */
    public List<CertificateToken> getTimestampCertificates() {

        return getCertificateSource().getTimestampCertificates();
    }

    @Override
    public SignatureCryptographicVerification checkSignatureIntegrity() {

        if (signatureCryptographicVerification != null) {
            return signatureCryptographicVerification;
        }
        signatureCryptographicVerification = new SignatureCryptographicVerification();
        final Document document = signatureElement.getOwnerDocument();
        final Element rootElement = document.getDocumentElement();

        DSSXMLUtils.setIDIdentifier(rootElement);
        DSSXMLUtils.recursiveIdBrowse(rootElement);
        try {

            final XMLSignature santuarioSignature = new XMLSignature(signatureElement, "");
            santuarioSignature.addResourceResolver(new XPointerResourceResolver(signatureElement));
            santuarioSignature.addResourceResolver(new OfflineResolver(detachedContents));

            boolean coreValidity = false;
            final List<CertificateValidity> certificateValidityList = getSigningCertificateValidityList(
                    santuarioSignature, signatureCryptographicVerification, providedSigningCertificateToken);
            for (final CertificateValidity certificateValidity : certificateValidityList) {

                try {

                    final PublicKey publicKey = certificateValidity.getPublicKey();
                    coreValidity = santuarioSignature.checkSignatureValue(publicKey);
                    if (coreValidity) {

                        candidatesForSigningCertificate.setTheCertificateValidity(certificateValidity);
                        break;
                    }
                } catch (XMLSignatureException e) {
                    LOG.warn("Exception when validating signature: ", e);
                    signatureCryptographicVerification.setErrorMessage(e.getMessage());
                }
            }
            final SignedInfo signedInfo = santuarioSignature.getSignedInfo();
            final int length = signedInfo.getLength();

            boolean referenceDataFound = length > 0;
            boolean referenceDataHashValid = length > 0;

            boolean foundSignedProperties = false;
            for (int ii = 0; ii < length; ii++) {
                final Reference reference = signedInfo.item(ii);
                if (xPathQueryHolder.XADES_SIGNED_PROPERTIES.equals(reference.getType())) {
                    foundSignedProperties = true;
                }
                if (!coreValidity) {
                    referenceDataHashValid = referenceDataHashValid && reference.verify();
                }
                references.add(reference);
            }

            // 1 reference for SignedProperties + 1 reference / signed object
            referenceDataFound = referenceDataFound && foundSignedProperties;

            signatureCryptographicVerification.setReferenceDataFound(referenceDataFound);
            signatureCryptographicVerification.setReferenceDataIntact(referenceDataHashValid);
            signatureCryptographicVerification.setSignatureIntact(coreValidity);
        } catch (Exception e) {

            LOG.error(e.getMessage(), e);
            StackTraceElement[] stackTrace = e.getStackTrace();
            final String name = XAdESSignature.class.getName();
            int lineNumber = 0;
            for (StackTraceElement element : stackTrace) {

                final String className = element.getClassName();
                if (className.equals(name)) {

                    lineNumber = element.getLineNumber();
                    break;
                }
            }
            signatureCryptographicVerification
                    .setErrorMessage(e.getMessage() + "/ XAdESSignature/Line number/" + lineNumber);
        }
        return signatureCryptographicVerification;
    }

    /**
     * This method returns a {@code List} of {@code SigningCertificateValidity} base on the certificates extracted from the signature or on the
     * {@code providedSigningCertificateToken}. The field {@code candidatesForSigningCertificate} is instantiated in case where the signing
     * certificated is provided.
     *
     * @param santuarioSignature
     *            The object created tro validate the signature
     * @param scv
     *            {@code SignatureCryptographicVerification} containing information on the signature validation
     * @param providedSigningCertificate
     *            provided signing certificate: {@code CertificateToken} @return
     * @return the {@code List} of the {@code SigningCertificateValidity}
     * @throws KeyResolverException
     */
    private List<CertificateValidity> getSigningCertificateValidityList(final XMLSignature santuarioSignature,
            SignatureCryptographicVerification scv, final CertificateToken providedSigningCertificate)
            throws KeyResolverException {

        List<CertificateValidity> certificateValidityList;
        if (providedSigningCertificate == null) {

            // To determine the signing certificate it is necessary to browse
            // through all candidates extracted from the signature.
            final CandidatesForSigningCertificate candidates = getCandidatesForSigningCertificate();
            certificateValidityList = candidates.getCertificateValidityList();
            if (certificateValidityList.size() == 0) {

                // The public key can also be extracted from the signature.
                final KeyInfo extractedKeyInfo = santuarioSignature.getKeyInfo();
                final PublicKey publicKey;
                if ((extractedKeyInfo == null) || ((publicKey = extractedKeyInfo.getPublicKey()) == null)) {

                    scv.setErrorMessage("There is no signing certificate within the signature.");
                    return certificateValidityList;
                }
                certificateValidityList = getSigningCertificateValidityList(publicKey);
            }
        } else {

            candidatesForSigningCertificate = new CandidatesForSigningCertificate();
            final CertificateValidity certificateValidity = new CertificateValidity(providedSigningCertificate);
            candidatesForSigningCertificate.add(certificateValidity);
            certificateValidityList = candidatesForSigningCertificate.getCertificateValidityList();
        }
        return certificateValidityList;
    }

    /**
     * This method returns a {@code List} of {@code SigningCertificateValidity} base on the provided {@code providedSigningCertificateToken}. The
     * field {@code candidatesForSigningCertificate} is instantiated.
     *
     * @param extractedPublicKey
     *            provided public key: {@code PublicKey}
     * @return
     */
    protected List<CertificateValidity> getSigningCertificateValidityList(final PublicKey extractedPublicKey) {

        candidatesForSigningCertificate = new CandidatesForSigningCertificate();
        final CertificateValidity certificateValidity = new CertificateValidity(extractedPublicKey);
        candidatesForSigningCertificate.add(certificateValidity);
        final List<CertificateValidity> certificateValidityList = candidatesForSigningCertificate
                .getCertificateValidityList();
        return certificateValidityList;
    }

    /**
     * This method retrieves the potential countersignatures embedded in the XAdES signature document. From ETSI TS 101 903 v1.4.2:
     *
     * 7.2.4.1 Countersignature identifier in Type attribute of ds:Reference
     *
     * A XAdES signature containing a ds:Reference element whose Type attribute has value "http://uri.etsi.org/01903#CountersignedSignature" will
     * indicate that is is, in fact, a countersignature of the signature referenced by this element.
     *
     * 7.2.4.2 Enveloped countersignatures: the CounterSignature element
     *
     * The CounterSignature is an unsigned property that qualifies the signature. A XAdES signature MAY have more than one CounterSignature
     * properties. As indicated by its name, it contains one countersignature of the qualified signature.
     *
     * @return a list containing the countersignatures embedded in the XAdES signature document
     */
    @Override
    public List<AdvancedSignature> getCounterSignatures() {

        // see ETSI TS 101 903 V1.4.2 (2010-12) pp. 38/39/40
        final NodeList counterSignatures = DSSXMLUtils.getNodeList(signatureElement,
                xPathQueryHolder.XPATH_COUNTER_SIGNATURE);
        if (counterSignatures == null) {
            return null;
        }
        final List<AdvancedSignature> xadesList = new ArrayList<AdvancedSignature>();
        for (int ii = 0; ii < counterSignatures.getLength(); ii++) {

            final Element counterSignatureElement = (Element) counterSignatures.item(ii);
            final Element signatureElement = DSSXMLUtils.getElement(counterSignatureElement,
                    xPathQueryHolder.XPATH__SIGNATURE);

            // Verify that the element is a proper signature by trying to build
            // a XAdESSignature out of it
            final XAdESSignature xadesCounterSignature = new XAdESSignature(signatureElement, xPathQueryHolders,
                    certPool);
            if (isCounterSignature(xadesCounterSignature)) {
                xadesCounterSignature.setMasterSignature(this);
                xadesList.add(xadesCounterSignature);
            }
        }
        return xadesList;
    }

    /**
     * This method verifies whether a given signature is a countersignature.
     *
     * From ETSI TS 101 903 V1.4.2: - The signature's ds:SignedInfo element MUST contain one ds:Reference element referencing the ds:Signature element
     * of the embedding and countersigned XAdES signature - The content of the ds:DigestValue in the aforementioned ds:Reference element of the
     * countersignature MUST be the base-64 encoded digest of the complete (and canonicalized) ds:SignatureValue element (i.e. including the starting
     * and closing tags) of the embedding and countersigned XAdES signature.
     *
     * @param xadesCounterSignature
     * @return
     */
    private boolean isCounterSignature(final XAdESSignature xadesCounterSignature) {

        final List<Element> signatureReferences = xadesCounterSignature.getSignatureReferences();
        // gets Element with
        // Type="http://uri.etsi.org/01903#CountersignedSignature"
        for (final Element reference : signatureReferences) {

            final String type = reference.getAttribute("Type");
            if (xPathQueryHolder.XADES_COUNTERSIGNED_SIGNATURE.equals(type)) {
                return true;
            }
        }
        return false;
    }

    @Override
    public List<CertificateRef> getCertificateRefs() {

        Element signingCertEl = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_CERT_REFS);
        if (signingCertEl == null) {

            return null;
        }
        List<CertificateRef> certIds = new ArrayList<CertificateRef>();
        NodeList certIdnodes = DSSXMLUtils.getNodeList(signingCertEl, "./xades:Cert");
        for (int i = 0; i < certIdnodes.getLength(); i++) {

            Element certId = (Element) certIdnodes.item(i);
            Element issuerNameEl = DSSXMLUtils.getElement(certId, xPathQueryHolder.XPATH__X509_ISSUER_NAME);
            Element issuerSerialEl = DSSXMLUtils.getElement(certId, xPathQueryHolder.XPATH__X509_SERIAL_NUMBER);
            Element digestAlgorithmEl = DSSXMLUtils.getElement(certId,
                    xPathQueryHolder.XPATH__CERT_DIGEST_DIGEST_METHOD);
            Element digestValueEl = DSSXMLUtils.getElement(certId,
                    xPathQueryHolder.XPATH__CERT_DIGEST_DIGEST_VALUE);

            CertificateRef genericCertId = new CertificateRef();
            if ((issuerNameEl != null) && (issuerSerialEl != null)) {
                genericCertId.setIssuerName(issuerNameEl.getTextContent());
                genericCertId.setIssuerSerial(issuerSerialEl.getTextContent());
            }

            String xmlName = digestAlgorithmEl.getAttribute(XMLE_ALGORITHM);
            genericCertId.setDigestAlgorithm(DigestAlgorithm.forXML(xmlName).getName());

            genericCertId.setDigestValue(Base64.decodeBase64(digestValueEl.getTextContent()));
            certIds.add(genericCertId);
        }

        return certIds;

    }

    @Override
    public List<CRLRef> getCRLRefs() {

        final List<CRLRef> certIds = new ArrayList<CRLRef>();
        final Element signingCertEl = DSSXMLUtils.getElement(signatureElement,
                xPathQueryHolder.XPATH_REVOCATION_CRL_REFS);
        if (signingCertEl != null) {

            final NodeList crlRefNodes = DSSXMLUtils.getNodeList(signingCertEl, xPathQueryHolder.XPATH__CRL_REF);
            for (int i = 0; i < crlRefNodes.getLength(); i++) {

                final Element certId = (Element) crlRefNodes.item(i);
                final Element digestAlgorithmEl = DSSXMLUtils.getElement(certId,
                        xPathQueryHolder.XPATH__DAAV_DIGEST_METHOD);
                final Element digestValueEl = DSSXMLUtils.getElement(certId,
                        xPathQueryHolder.XPATH__DAAV_DIGEST_VALUE);

                final String xmlName = digestAlgorithmEl.getAttribute(XMLE_ALGORITHM);
                final DigestAlgorithm digestAlgo = DigestAlgorithm.forXML(xmlName);

                final CRLRef ref = new CRLRef(digestAlgo, Base64.decodeBase64(digestValueEl.getTextContent()));
                certIds.add(ref);
            }
        }
        return certIds;
    }

    @Override
    public List<OCSPRef> getOCSPRefs() {

        final List<OCSPRef> certIds = new ArrayList<OCSPRef>();
        final Element signingCertEl = DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_OCSP_REFS);
        if (signingCertEl != null) {

            final NodeList ocspRefNodes = DSSXMLUtils.getNodeList(signingCertEl, xPathQueryHolder.XPATH__OCSPREF);
            for (int i = 0; i < ocspRefNodes.getLength(); i++) {

                final Element certId = (Element) ocspRefNodes.item(i);
                final Element digestAlgorithmEl = DSSXMLUtils.getElement(certId,
                        xPathQueryHolder.XPATH__DAAV_DIGEST_METHOD);
                final Element digestValueEl = DSSXMLUtils.getElement(certId,
                        xPathQueryHolder.XPATH__DAAV_DIGEST_VALUE);

                if ((digestAlgorithmEl == null) || (digestValueEl == null)) {
                    throw new DSSNotETSICompliantException(
                            DSSNotETSICompliantException.MSG.XADES_DIGEST_ALG_AND_VALUE_ENCODING);
                }

                final String xmlName = digestAlgorithmEl.getAttribute(XMLE_ALGORITHM);
                final DigestAlgorithm digestAlgo = DigestAlgorithm.forXML(xmlName);

                final String digestValue = digestValueEl.getTextContent();
                final byte[] base64EncodedDigestValue = Base64.decodeBase64(digestValue);
                final OCSPRef ocspRef = new OCSPRef(digestAlgo, base64EncodedDigestValue, false);
                certIds.add(ocspRef);
            }
        }
        return certIds;
    }

    @Override
    public byte[] getSignatureTimestampData(final TimestampToken timestampToken, String canonicalizationMethod) {

        canonicalizationMethod = timestampToken != null ? timestampToken.getCanonicalizationMethod()
                : canonicalizationMethod;
        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try {

            writeCanonicalizedValue(xPathQueryHolder.XPATH_SIGNATURE_VALUE, canonicalizationMethod, buffer);
            if (LOG.isTraceEnabled()) {
                LOG.trace("Signature timestamp: canonicalization method  --> {}", canonicalizationMethod);
                LOG.trace("                   : canonicalized string     --> {}", buffer.toString());
            }
        } catch (IOException e) {
            throw new DSSException("Error when computing the SignatureTimestamp", e);
        }
        return buffer.toByteArray();
    }

    @Override
    public byte[] getTimestampX1Data(final TimestampToken timestampToken, String canonicalizationMethod) {

        canonicalizationMethod = timestampToken != null ? timestampToken.getCanonicalizationMethod()
                : canonicalizationMethod;
        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try {

            writeCanonicalizedValue(xPathQueryHolder.XPATH_SIGNATURE_VALUE, canonicalizationMethod, buffer);

            final NodeList signatureTimeStampNode = DSSXMLUtils.getNodeList(signatureElement,
                    xPathQueryHolder.XPATH_SIGNATURE_TIMESTAMP);
            if (signatureTimeStampNode != null) {
                for (int ii = 0; ii < signatureTimeStampNode.getLength(); ii++) {

                    final Node item = signatureTimeStampNode.item(ii);
                    final byte[] canonicalizedValue = DSSXMLUtils.canonicalizeSubtree(canonicalizationMethod, item);
                    buffer.write(canonicalizedValue);
                }
            }
            writeCanonicalizedValue(xPathQueryHolder.XPATH_COMPLETE_CERTIFICATE_REFS, canonicalizationMethod,
                    buffer);
            writeCanonicalizedValue(xPathQueryHolder.XPATH_COMPLETE_REVOCATION_REFS, canonicalizationMethod,
                    buffer);
            if (LOG.isTraceEnabled()) {
                LOG.trace("X1Timestamp (SigAndRefsTimeStamp) canonicalised string:\n" + buffer.toString());
            }
            return buffer.toByteArray();
        } catch (IOException e) {
            throw new DSSException("Error when computing the SigAndRefsTimeStamp (X1Timestamp)", e);
        }
    }

    @Override
    public byte[] getTimestampX2Data(final TimestampToken timestampToken, String canonicalizationMethod) {

        canonicalizationMethod = timestampToken != null ? timestampToken.getCanonicalizationMethod()
                : canonicalizationMethod;
        final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
        try {

            writeCanonicalizedValue(xPathQueryHolder.XPATH_COMPLETE_CERTIFICATE_REFS, canonicalizationMethod,
                    buffer);
            writeCanonicalizedValue(xPathQueryHolder.XPATH_COMPLETE_REVOCATION_REFS, canonicalizationMethod,
                    buffer);
            if (LOG.isTraceEnabled()) {
                LOG.trace("TimestampX2Data (RefsOnlyTimeStamp) canonicalised string:\n" + buffer.toString());
            }
            return buffer.toByteArray();
        } catch (IOException e) {

            throw new DSSException("Error when computing the RefsOnlyTimeStamp (TimestampX2D)", e);
        }
    }

    /**
     * Gathers the data to be used to calculate the hash value sent to the TSA (messageImprint).
     *
     * @param timestampToken
     *            {@code TimestampToken} to validate, or {@code null} when adding a new archive timestamp
     * @param canonicalizationMethod
     * @return {@code byte} array containing the canonicalized and concatenated timestamped data
     */
    @Override
    public byte[] getArchiveTimestampData(final TimestampToken timestampToken, String canonicalizationMethod) {

        if (LOG.isTraceEnabled()) {
            LOG.trace("--->Get archive timestamp data:"
                    + (timestampToken == null ? "--> CREATION" : "--> VALIDATION"));
        }
        canonicalizationMethod = timestampToken != null ? timestampToken.getCanonicalizationMethod()
                : canonicalizationMethod;
        /**
         * 8.2.1 Not distributed case<br>
         *
         * When xadesv141:ArchiveTimeStamp and all the unsigned properties covered by its time-stamp certificateToken have the same parent, this
         * property uses the Implicit mechanism for all the time-stamped data objects. The input to the computation of the digest value MUST be built
         * as follows:
         */
        try {

            /**
             * 1) Initialize the final octet stream as an empty octet stream.
             */
            final ByteArrayOutputStream buffer = new ByteArrayOutputStream();

            /**
             * 2) Take all the ds:Reference elements in their order of appearance within ds:SignedInfo referencing whatever the signer wants to sign
             * including the SignedProperties element. Process each one as indicated below:<br>
             * - Process the retrieved ds:Reference element according to the reference processing model of XMLDSIG.<br>
             * - If the result is a XML node set, canonicalize it. If ds:Canonicalization is present, the algorithm indicated by this element is used.
             * If not, the standard canonicalization method specified by XMLDSIG is used.<br>
             * - Concatenate the resulting octets to the final octet stream.
             */

            /**
             * The references are already calculated {@see #checkSignatureIntegrity()}
             */
            final Set<String> referenceURIs = new HashSet<String>();
            for (final Reference reference : references) {

                try {

                    String uri = reference.getURI();
                    if (uri.startsWith("#")) {
                        uri = uri.substring(1);
                    }
                    referenceURIs.add(uri);
                    final byte[] bytes = reference.getReferencedBytes();
                    DSSUtils.write(bytes, buffer);
                } catch (XMLSignatureException e) {
                    throw new DSSException(e);
                }
            }
            /**
             * 3) Take the following XMLDSIG elements in the order they are listed below, canonicalize each one and concatenate each resulting octet
             * stream to the final octet stream:<br>
             * - The ds:SignedInfo element.<br>
             * - The ds:SignatureValue element.<br>
             * - The ds:KeyInfo element, if present.
             */
            writeCanonicalizedValue(xPathQueryHolder.XPATH_SIGNED_INFO, canonicalizationMethod, buffer);
            writeCanonicalizedValue(xPathQueryHolder.XPATH_SIGNATURE_VALUE, canonicalizationMethod, buffer);
            writeCanonicalizedValue(xPathQueryHolder.XPATH_KEY_INFO, canonicalizationMethod, buffer);
            /**
             * 4) Take the unsigned signature properties that appear before the current xadesv141:ArchiveTimeStamp in the order they appear within the
             * xades:UnsignedSignatureProperties, canonicalize each one and concatenate each resulting octet stream to the final octet stream. While
             * concatenating the following rules apply:
             */
            final Element unsignedSignaturePropertiesDom = getUnsignedSignaturePropertiesDom();
            if (unsignedSignaturePropertiesDom == null) {
                throw new NullPointerException(xPathQueryHolder.XPATH_UNSIGNED_SIGNATURE_PROPERTIES);
            }
            final NodeList unsignedProperties = unsignedSignaturePropertiesDom.getChildNodes();
            for (int ii = 0; ii < unsignedProperties.getLength(); ii++) {

                final Node node = unsignedProperties.item(ii);
                if (node.getNodeType() != Node.ELEMENT_NODE) { // This can
                    // happened when
                    // there is a
                    // blank line
                    // between tags.
                    continue;
                }
                final String localName = node.getLocalName();
                // In the SD-DSS implementation when validating the signature
                // the framework will not add missing data. To do so the
                // signature must be extended.
                // if (localName.equals("CertificateValues")) {
                /*
                 * - The xades:CertificateValues property MUST be added if it is not already present and the ds:KeyInfo element does not contain the
                 * full set of certificates used to validate the electronic signature.
                 */
                // } else if (localName.equals("RevocationValues")) {
                /*
                 * - The xades:RevocationValues property MUST be added if it is not already present and the ds:KeyInfo element does not contain the
                 * revocation information that has to be shipped with the electronic signature
                 */
                // } else if (localName.equals("AttrAuthoritiesCertValues")) {
                /*
                 * - The xades:AttrAuthoritiesCertValues property MUST be added if not already present and the following conditions are true: there
                 * exist an attribute certificate in the signature AND a number of certificates that have been used in its validation do not appear in
                 * CertificateValues. Its content will satisfy with the rules specified in clause 7.6.3.
                 */
                // } else if (localName.equals("AttributeRevocationValues")) {
                /*
                 * - The xades:AttributeRevocationValues property MUST be added if not already present and there the following conditions are true:
                 * there exist an attribute certificate AND some revocation data that have been used in its validation do not appear in
                 * RevocationValues. Its content will satisfy with the rules specified in clause 7.6.4.
                 */
                // } else
                if (isArchiveTimestamp(localName)) {

                    if ((timestampToken != null) && (timestampToken.getHashCode() == node.hashCode())) {
                        break;
                    }
                } else if ("TimeStampValidationData".equals(localName)) {

                    /**
                     * ETSI TS 101 903 V1.4.2 (2010-12) 8.1 The new XAdESv141:TimeStampValidationData element ../.. This element is specified to serve
                     * as an optional container for validation data required for carrying a full verification of time-stamp tokens embedded within any
                     * of the different time-stamp containers defined in the present document. ../.. 8.1.1 Use of URI attribute ../.. a new
                     * xadesv141:TimeStampValidationData element SHALL be created containing the missing validation data information and it SHALL be
                     * added as a child of UnsignedSignatureProperties elements immediately after the respective time-stamp certificateToken container
                     * element.
                     */
                }
                byte[] canonicalizedValue;
                if (timestampToken == null) { // Creation of the timestamp

                    /**
                     * This is the work around for the name space problem: The issue was reported on:
                     * https://issues.apache.org/jira/browse/SANTUARIO-139 and considered as close. But for me (Bob) it still does not work!
                     */
                    final byte[] bytesToCanonicalize = DSSXMLUtils.serializeNode(node);
                    canonicalizedValue = DSSXMLUtils.canonicalize(canonicalizationMethod, bytesToCanonicalize);
                } else {
                    canonicalizedValue = DSSXMLUtils.canonicalizeSubtree(canonicalizationMethod, node);
                }
                if (LOG.isTraceEnabled()) {
                    LOG.trace(localName + ": Canonicalization: " + canonicalizationMethod);
                    LOG.trace(new String(canonicalizedValue) + "\n");
                }
                buffer.write(canonicalizedValue);
            }
            /**
             * 5) Take all the ds:Object elements except the one containing xades:QualifyingProperties element. Canonicalize each one and concatenate
             * each resulting octet stream to the final octet stream. If ds:Canonicalization is present, the algorithm indicated by this element is
             * used. If not, the standard canonicalization method specified by XMLDSIG is used.
             */
            boolean xades141 = (timestampToken == null)
                    || !ArchiveTimestampType.XAdES.equals(timestampToken.getArchiveTimestampType());

            final NodeList objects = getObjects();
            for (int ii = 0; ii < objects.getLength(); ii++) {

                final Node node = objects.item(ii);
                final Node qualifyingProperties = DSSXMLUtils.getElement(node,
                        xPathQueryHolder.XPATH__QUALIFYING_PROPERTIES);
                if (qualifyingProperties != null) {
                    continue;
                }
                if (!xades141) {
                    /**
                     * !!! ETSI TS 101 903 V1.3.2 (2006-03) 5) Take any ds:Object element in the signature that is not referenced by any ds:Reference
                     * within ds:SignedInfo, except that one containing the QualifyingProperties element. Canonicalize each one and concatenate each
                     * resulting octet stream to the final octet stream. If ds:Canonicalization is present, the algorithm indicated by this element is
                     * used. If not, the standard canonicalization method specified by XMLDSIG is used.
                     */
                    final NamedNodeMap attributes = node.getAttributes();
                    final int length = attributes.getLength();
                    String id = "";
                    for (int jj = 0; jj < length; jj++) {
                        final Node item = attributes.item(jj);
                        final String nodeName = item.getNodeName();
                        if ("ID".equals(nodeName.toUpperCase())) {
                            id = item.getNodeValue();
                            break;
                        }
                    }
                    final boolean contains = referenceURIs.contains(id);
                    if (contains) {
                        continue;
                    }
                }
                byte[] canonicalizedValue = DSSXMLUtils.canonicalizeSubtree(canonicalizationMethod, node);
                buffer.write(canonicalizedValue);
            }
            final byte[] bytes = buffer.toByteArray();
            return bytes;
        } catch (IOException e) {
            throw new DSSException("Error when computing the archive data", e);
        }
    }

    private void writeCanonicalizedValue(final String xPathString, final String canonicalizationMethod,
            final ByteArrayOutputStream buffer) throws IOException {

        final Element element = DSSXMLUtils.getElement(signatureElement, xPathString);
        if (element != null) {

            final byte[] canonicalizedValue = DSSXMLUtils.canonicalizeSubtree(canonicalizationMethod, element);
            buffer.write(canonicalizedValue);
        }
    }

    private boolean isArchiveTimestamp(final String localName) {
        return XPathQueryHolder.XMLE_ARCHIVE_TIME_STAMP.equals(localName)
                || XPathQueryHolder.XMLE_ARCHIVE_TIME_STAMP_V2.equals(localName);
    }

    @Override
    public String getId() {

        if (signatureId == null) {

            String idValue = DSSXMLUtils.getIDIdentifier(signatureElement);
            if (idValue != null) {

                signatureId = idValue;
            } else {

                final CertificateToken certificateToken = getSigningCertificateToken();
                TokenIdentifier identifier = (certificateToken == null ? null : certificateToken.getDSSId());
                signatureId = DSSUtils.getDeterministicId(getSigningTime(), identifier);
            }
        }
        return signatureId;
    }

    @Override
    public List<TimestampReference> getTimestampedReferences() {

        final List<TimestampReference> references = new ArrayList<TimestampReference>();

        final Node completeCertificateRefsNode = DSSXMLUtils.getElement(signatureElement,
                xPathQueryHolder.XPATH_COMPLETE_CERTIFICATE_REFS);
        if (completeCertificateRefsNode != null) {

            final NodeList nodes = DSSXMLUtils.getNodeList(completeCertificateRefsNode,
                    xPathQueryHolder.XPATH__COMPLETE_CERTIFICATE_REFS__CERT_DIGEST);
            for (int ii = 0; ii < nodes.getLength(); ii++) {

                final Element certDigestElement = (Element) nodes.item(ii);
                final TimestampReference certificateReference = createCertificateTimestampReference(
                        certDigestElement);
                references.add(certificateReference);
            }
        }
        final Node completeRevocationRefsNode = DSSXMLUtils.getElement(signatureElement,
                xPathQueryHolder.XPATH_COMPLETE_REVOCATION_REFS);
        if (completeRevocationRefsNode != null) {

            final NodeList nodes = DSSXMLUtils.getNodeList(completeRevocationRefsNode,
                    "./*/*/xades:DigestAlgAndValue");
            for (int ii = 0; ii < nodes.getLength(); ii++) {

                final Element element = (Element) nodes.item(ii);
                final TimestampReference revocationReference = createRevocationTimestampReference(element);
                references.add(revocationReference);
            }
        }
        return references;
    }

    private TimestampReference createRevocationTimestampReference(Element element) {

        String digestAlgorithm = DSSXMLUtils.getNode(element, xPathQueryHolder.XPATH__DIGEST_METHOD_ALGORITHM)
                .getTextContent();
        digestAlgorithm = DigestAlgorithm.forXML(digestAlgorithm).getName();
        final String digestValue = DSSXMLUtils.getElement(element, xPathQueryHolder.XPATH__DIGEST_VALUE)
                .getTextContent();
        final TimestampReference revocationReference = new TimestampReference(digestAlgorithm, digestValue);
        return revocationReference;
    }

    /**
     * Retrieves the name of each node found under the unsignedSignatureProperties element
     *
     * @return an ArrayList containing the retrieved node names
     */
    public List<String> getUnsignedSignatureProperties() {

        final List<String> childrenNames = DSSXMLUtils.getChildrenNames(signatureElement,
                xPathQueryHolder.XPATH_UNSIGNED_SIGNATURE_PROPERTIES);
        return childrenNames;
    }

    public List<String> getSignedSignatureProperties() {

        final List<String> childrenNames = DSSXMLUtils.getChildrenNames(signatureElement,
                xPathQueryHolder.XPATH_SIGNED_SIGNATURE_PROPERTIES);
        return childrenNames;
    }

    public List<String> getSignedProperties() {

        final List<String> childrenNames = DSSXMLUtils.getChildrenNames(signatureElement,
                xPathQueryHolder.XPATH_SIGNED_PROPERTIES);
        return childrenNames;
    }

    public List<String> getUnsignedProperties() {

        final List<String> childrenNames = DSSXMLUtils.getChildrenNames(signatureElement,
                xPathQueryHolder.XPATH_UNSIGNED_PROPERTIES);
        return childrenNames;
    }

    public List<String> getSignedDataObjectProperties() {

        final List<String> childrenNames = DSSXMLUtils.getChildrenNames(signatureElement,
                xPathQueryHolder.XPATH_SIGNED_DATA_OBJECT_PROPERTIES);
        return childrenNames;
    }

    /**
     * This method creates
     *
     * @param element
     * @return
     * @throws eu.europa.esig.dss.DSSException
     */
    private TimestampReference createCertificateTimestampReference(final Element element) throws DSSException {

        final String xmlDigestAlgorithm = DSSXMLUtils
                .getNode(element, xPathQueryHolder.XPATH__DIGEST_METHOD_ALGORITHM).getTextContent();
        final DigestAlgorithm digestAlgorithm = DigestAlgorithm.forXML(xmlDigestAlgorithm);
        usedCertificatesDigestAlgorithms.add(digestAlgorithm);
        final Element digestValueElement = DSSXMLUtils.getElement(element, xPathQueryHolder.XPATH__DIGEST_VALUE);
        final String digestValue = (digestValueElement == null) ? "" : digestValueElement.getTextContent();
        final TimestampReference reference = new TimestampReference(digestAlgorithm.name(), digestValue);
        return reference;
    }

    private TimestampReference createCertificateTimestampReference(final CertificateToken certificateToken)
            throws DSSException {

        usedCertificatesDigestAlgorithms.add(DigestAlgorithm.SHA1);

        final TimestampReference reference = new TimestampReference(DigestAlgorithm.SHA1.name(),
                DSSUtils.digest(DigestAlgorithm.SHA1, certificateToken));
        return reference;
    }

    @Override
    public Set<DigestAlgorithm> getUsedCertificatesDigestAlgorithms() {

        return usedCertificatesDigestAlgorithms;
    }

    @Override
    public boolean isDataForSignatureLevelPresent(final SignatureLevel signatureLevel) {

        boolean dataForLevelPresent = true;
        switch (signatureLevel) {
        case XML_NOT_ETSI:
            break;
        case XAdES_BASELINE_LTA:
            dataForLevelPresent = hasLTAProfile();
            break;
        case XAdES_BASELINE_LT:
            dataForLevelPresent &= hasLTProfile();
            break;
        case XAdES_BASELINE_T:
            dataForLevelPresent &= hasTProfile();
            break;
        case XAdES_BASELINE_B:
            dataForLevelPresent &= hasBProfile();
            break;
        case XAdES_X:
            dataForLevelPresent &= hasXProfile();
            break;
        case XAdES_C:
            dataForLevelPresent &= hasCProfile();
            break;
        default:
            throw new IllegalArgumentException("Unknown level " + signatureLevel);
        }
        return dataForLevelPresent;
    }

    @Override
    public SignatureLevel[] getSignatureLevels() {

        return signatureLevels;
    }

    @Override
    public String validateStructure() {

        final String string = DSSXMLUtils.xmlToString(signatureElement);
        StringReader stringReader = new StringReader(string);
        final String validated = DSSXMLUtils.validateAgainstXSD(new StreamSource(stringReader));
        return validated;
    }

    /**
     * This method returns the last timestamp validation data for an archive timestamp.
     *
     * @return
     */
    public Element getLastTimestampValidationData() {

        final List<TimestampToken> archiveTimestamps = getArchiveTimestamps();
        TimestampToken mostRecentTimestamp = null;
        for (final TimestampToken archiveTimestamp : archiveTimestamps) {

            if (mostRecentTimestamp == null) {

                mostRecentTimestamp = archiveTimestamp;
                continue;
            }
            final Date generationTime = archiveTimestamp.getGenerationTime();
            final Date mostRecentGenerationTime = mostRecentTimestamp.getGenerationTime();
            if (generationTime.after(mostRecentGenerationTime)) {

                mostRecentTimestamp = archiveTimestamp;
            }
        }
        final int timestampHashCode = mostRecentTimestamp.getHashCode();
        final NodeList nodeList = DSSXMLUtils.getNodeList(signatureElement,
                xPathQueryHolder.XPATH_UNSIGNED_SIGNATURE_PROPERTIES + "/*");
        boolean found = false;
        for (int ii = 0; ii < nodeList.getLength(); ii++) {

            final Element unsignedSignatureElement = (Element) nodeList.item(ii);
            final int nodeHashCode = unsignedSignatureElement.hashCode();
            if (nodeHashCode == timestampHashCode) {

                found = true;
            } else if (found) {

                final String nodeName = unsignedSignatureElement.getLocalName();
                if ("TimeStampValidationData".equals(nodeName)) {

                    return unsignedSignatureElement;
                }
            }
        }
        return null;
    }

    @Override
    public CommitmentType getCommitmentTypeIndication() {
        return null;
    }

    /**
     * // TODO (11/09/2014): to be deleted, eu.europa.esig.dss.xades.validation.XAdESSignature#getReferences() to be used
     *
     * @return
     */
    public List<Element> getSignatureReferences() {

        final NodeList list = DSSXMLUtils.getNodeList(signatureElement, xPathQueryHolder.XPATH_REFERENCE);
        List<Element> references = new ArrayList<Element>(list.getLength());
        for (int ii = 0; ii < list.getLength(); ii++) {

            final Node node = list.item(ii);
            references.add((Element) node);
        }
        return references;
    }

    public List<Reference> getReferences() {
        return references;
    }

    /**
     * @return
     */
    public List<Element> getSignatureObjects() {

        final NodeList list = DSSXMLUtils.getNodeList(signatureElement, XPathQueryHolder.XPATH_OBJECT);
        final List<Element> references = new ArrayList<Element>(list.getLength());
        for (int ii = 0; ii < list.getLength(); ii++) {

            final Node node = list.item(ii);
            final Element element = (Element) node;
            if (DSSXMLUtils.getElement(element,
                    xPathQueryHolder.XPATH__QUALIFYING_PROPERTIES_SIGNED_PROPERTIES) != null) {
                // ignore signed properties
                continue;
            }
            references.add(element);
        }
        return references;
    }

    /**
     * This method allows to register a new {@code XPathQueryHolder}.
     *
     * @param xPathQueryHolder
     *            {@code XPathQueryHolder} to register
     */
    public void registerXPathQueryHolder(final XPathQueryHolder xPathQueryHolder) {
        xPathQueryHolders.add(xPathQueryHolder);
    }

    public Element getUnsignedSignaturePropertiesDom() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_UNSIGNED_SIGNATURE_PROPERTIES);
    }

    public Element getUnsignedPropertiesDom() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_UNSIGNED_PROPERTIES);
    }

    public Element getQualifyingPropertiesDom() {
        return DSSXMLUtils.getElement(signatureElement, xPathQueryHolder.XPATH_QUALIFYING_PROPERTIES);
    }
}