/* * DSS - Digital Signature Services * * Copyright (C) 2013 European Commission, Directorate-General Internal Market and Services (DG MARKT), B-1049 Bruxelles/Brussel * * Developed by: 2013 ARHS Developments S.A. (rue Nicolas Bové 2B, L-1253 Luxembourg) http://www.arhs-developments.com * * This file is part of the "DSS - Digital Signature Services" project. * * "DSS - Digital Signature Services" is free software: you can redistribute it and/or modify it under the terms of * the GNU Lesser General Public License as published by the Free Software Foundation, either version 2.1 of the * License, or (at your option) any later version. * * DSS is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License along with * "DSS - Digital Signature Services". If not, see <http://www.gnu.org/licenses/>. */ package eu.europa.ec.markt.dss.signature.xades; import java.io.IOException; import java.util.ArrayList; import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.UUID; import org.bouncycastle.tsp.TimeStampToken; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.w3c.dom.Element; import org.w3c.dom.NodeList; import eu.europa.ec.markt.dss.CertificateIdentifier; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.DSSXMLUtils; import eu.europa.ec.markt.dss.DigestAlgorithm; import eu.europa.ec.markt.dss.exception.DSSException; import eu.europa.ec.markt.dss.exception.DSSNullException; import eu.europa.ec.markt.dss.parameter.SignatureParameters; import eu.europa.ec.markt.dss.parameter.TimestampParameters; import eu.europa.ec.markt.dss.signature.DSSDocument; import eu.europa.ec.markt.dss.signature.InMemoryDocument; import eu.europa.ec.markt.dss.signature.MimeType; import eu.europa.ec.markt.dss.signature.ProfileParameters; import eu.europa.ec.markt.dss.signature.ProfileParameters.Operation; import eu.europa.ec.markt.dss.signature.SignatureLevel; import eu.europa.ec.markt.dss.signature.SignaturePackaging; import eu.europa.ec.markt.dss.validation102853.CertificatePool; import eu.europa.ec.markt.dss.validation102853.CertificateToken; import eu.europa.ec.markt.dss.validation102853.CertificateVerifier; import eu.europa.ec.markt.dss.validation102853.TimestampType; import eu.europa.ec.markt.dss.validation102853.ValidationContext; import eu.europa.ec.markt.dss.validation102853.tsp.TSPSource; import eu.europa.ec.markt.dss.validation102853.xades.XAdESSignature; import static eu.europa.ec.markt.dss.DigestAlgorithm.MD5; import static eu.europa.ec.markt.dss.XAdESNamespaces.XAdES; import static eu.europa.ec.markt.dss.XAdESNamespaces.XAdES141; import static eu.europa.ec.markt.dss.signature.ProfileParameters.Operation.SIGNING; import static eu.europa.ec.markt.dss.signature.SignatureLevel.XAdES_BASELINE_T; import static eu.europa.ec.markt.dss.signature.SignaturePackaging.ENVELOPED; import static eu.europa.ec.markt.dss.validation102853.TimestampType.SIGNATURE_TIMESTAMP; import static javax.xml.crypto.dsig.XMLSignature.XMLNS; /** * -T profile of XAdES signature * * @version $Revision$ - $Date$ */ public class XAdESLevelBaselineT extends ExtensionBuilder implements XAdESSignatureExtension { private static final Logger LOG = LoggerFactory.getLogger(XAdESLevelBaselineT.class); /* * The object encapsulating the Time Stamp Protocol needed to create the level -T, of the signature */ protected TSPSource tspSource; /** * The default constructor for XAdESLevelBaselineT. */ public XAdESLevelBaselineT(final CertificateVerifier certificateVerifier) { super(certificateVerifier); } private void incorporateC14nMethod(final Element parentDom, final String signedInfoC14nMethod) { //<ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"/> final Element canonicalizationMethodDom = documentDom.createElementNS(XMLNS, DS_CANONICALIZATION_METHOD); canonicalizationMethodDom.setAttribute(ALGORITHM, signedInfoC14nMethod); parentDom.appendChild(canonicalizationMethodDom); } @Override public InMemoryDocument extendSignatures(final DSSDocument dssDocument, final SignatureParameters params) throws DSSException { if (dssDocument == null) { throw new DSSNullException(DSSDocument.class); } if (this.tspSource == null) { throw new DSSNullException(TSPSource.class); } this.params = params; final ProfileParameters context = params.getContext(); if (LOG.isInfoEnabled()) { LOG.info("====> Extending: " + (dssDocument.getName() == null ? "IN MEMORY DOCUMENT" : dssDocument.getName())); } documentDom = DSSXMLUtils.buildDOM(dssDocument); final NodeList signatureNodeList = documentDom.getElementsByTagNameNS(XMLNS, SIGNATURE); if (signatureNodeList.getLength() == 0) { throw new DSSException("There is no signature to extend!"); } // In the case of the enveloped signature we have a specific treatment:<br> // we will just extend the signature that is being created (during creation process) String signatureId = null; final SignaturePackaging signaturePackaging = params.getSignaturePackaging(); final Operation operationKind = context.getOperationKind(); if (SIGNING.equals(operationKind) && ENVELOPED.equals(signaturePackaging)) { signatureId = params.getDeterministicId(); } for (int ii = 0; ii < signatureNodeList.getLength(); ii++) { currentSignatureDom = (Element) signatureNodeList.item(ii); final String currentSignatureId = currentSignatureDom.getAttribute(ID); if (signatureId != null && !signatureId.equals(currentSignatureId)) { continue; } final CertificatePool certPool = new CertificatePool(); // TODO-Bob (13/07/2014): The XPath query holder can be inherited from the xadesSignature: to be analysed xadesSignature = new XAdESSignature(currentSignatureDom, certPool); xadesSignature.setDetachedContents(params.getDetachedContent()); extendSignatureTag(); } final byte[] documentBytes = DSSXMLUtils.serializeNode(documentDom); final InMemoryDocument inMemoryDocument = new InMemoryDocument(documentBytes); inMemoryDocument.setMimeType(MimeType.XML); return inMemoryDocument; } /** * Extends the signature to a desired level. This method is overridden by other profiles.<br> * For -T profile adds the SignatureTimeStamp element which contains a single HashDataInfo element that refers to the * ds:SignatureValue element of the [XMLDSIG] signature. The timestamp token is obtained from TSP source.<br> * Adds <SignatureTimeStamp> segment into <UnsignedSignatureProperties> element. * * @throws eu.europa.ec.markt.dss.exception.DSSException */ protected void extendSignatureTag() throws DSSException { assertExtendSignaturePossible(); // We ensure that all XML segments needed for the construction of the extension -T are present. // If a segment does not exist then it is created. ensureUnsignedProperties(); ensureUnsignedSignatureProperties(); ensureSignedDataObjectProperties(); // The timestamp must be added only if there is no one or the extension -T level is being created if (!xadesSignature.hasTProfile() || XAdES_BASELINE_T.equals(params.getSignatureLevel())) { final TimestampParameters signatureTimestampParameters = params.getSignatureTimestampParameters(); final String canonicalizationMethod = signatureTimestampParameters.getCanonicalizationMethod(); final byte[] canonicalisedValue = xadesSignature.getSignatureTimestampData(null, canonicalizationMethod); final DigestAlgorithm timestampDigestAlgorithm = signatureTimestampParameters.getDigestAlgorithm(); final byte[] digestValue = DSSUtils.digest(timestampDigestAlgorithm, canonicalisedValue); createXAdESTimeStampType(SIGNATURE_TIMESTAMP, canonicalizationMethod, digestValue); } } /** * Checks if the extension is possible. */ private void assertExtendSignaturePossible() throws DSSException { final SignatureLevel signatureLevel = params.getSignatureLevel(); if (XAdES_BASELINE_T.equals(signatureLevel) && (xadesSignature.hasLTProfile() || xadesSignature.hasLTAProfile())) { final String exceptionMessage = "Cannot extend signature. The signedData is already extended with [%s]."; throw new DSSException(String.format(exceptionMessage, "XAdES LT")); } } /** * Sets the TSP source to be used when extending the digital signature * * @param tspSource the tspSource to set */ public void setTspSource(final TSPSource tspSource) { this.tspSource = tspSource; } /** * * This method incorporates all certificates passed as parameter. * * @param parentDom * @param toIncludeCertificates */ protected void incorporateCertificateValues(final Element parentDom, final List<CertificateToken> toIncludeCertificates) { // <xades:CertificateValues> // ...<xades:EncapsulatedX509Certificate>MIIC9TC... if (!toIncludeCertificates.isEmpty()) { final Element certificateValuesDom = DSSXMLUtils.addElement(documentDom, parentDom, XAdES, XADES_CERTIFICATE_VALUES); final CertificatePool certificatePool = getCertificatePool(); final boolean trustAnchorBPPolicy = params.bLevel().isTrustAnchorBPPolicy(); boolean trustAnchorIncluded = false; for (final CertificateToken certificateToken : toIncludeCertificates) { if (trustAnchorBPPolicy && certificatePool != null) { final List<CertificateToken> certificateTokens = certificatePool.get(certificateToken.getSubjectX500Principal()); if (certificateTokens.size() > 0) { trustAnchorIncluded = true; } } final byte[] bytes = certificateToken.getEncoded(); final String base64EncodeCertificate = DSSUtils.base64Encode(bytes); DSSXMLUtils.addTextElement(documentDom, certificateValuesDom, XAdES, XADES_ENCAPSULATED_X509_CERTIFICATE, base64EncodeCertificate); } if (trustAnchorBPPolicy && !trustAnchorIncluded) { LOG.warn("The trust anchor is missing but its inclusion is required by the signature policy!"); } } } public Set<CertificateToken> getCertificatesForInclusion(final ValidationContext validationContext) { final Set<CertificateToken> certificates = new HashSet<CertificateToken>(); final List<CertificateToken> certWithinSignatures = xadesSignature.getCertificates(); for (final CertificateToken certificateToken : validationContext.getProcessedCertificates()) { if (certWithinSignatures.contains(certificateToken)) { continue; } certificates.add(certificateToken); } return certificates; } /** * Creates any XAdES TimeStamp object representation. The timestamp token is obtained from TSP source * * @param timestampType {@code TimestampType} * @param timestampC14nMethod canonicalization method * @param digestValue array of {@code byte} representing the digest to timestamp * @throws DSSException in case of any error */ protected void createXAdESTimeStampType(final TimestampType timestampType, final String timestampC14nMethod, final byte[] digestValue) throws DSSException { try { Element timeStampDom = null; final TimestampParameters signatureTimestampParameters = params.getSignatureTimestampParameters(); DigestAlgorithm timestampDigestAlgorithm = signatureTimestampParameters.getDigestAlgorithm(); switch (timestampType) { case SIGNATURE_TIMESTAMP: // <xades:SignatureTimeStamp Id="time-stamp-1dee38c4-8388-40d1-8880-9eeda853fe60"> timeStampDom = DSSXMLUtils.addElement(documentDom, unsignedSignaturePropertiesDom, XAdES, XADES_SIGNATURE_TIME_STAMP); break; case VALIDATION_DATA_REFSONLY_TIMESTAMP: // timeStampDom = DSSXMLUtils.addElement(documentDom, unsignedSignaturePropertiesDom, XAdESNamespaces.XAdES, XADES_); break; case VALIDATION_DATA_TIMESTAMP: // <xades:SigAndRefsTimeStamp Id="time-stamp-a762ab0e-e05c-4cc8-a804-cf2c4ffb5516"> timeStampDom = DSSXMLUtils.addElement(documentDom, unsignedSignaturePropertiesDom, XAdES, XADES_SIG_AND_REFS_TIME_STAMP); break; case ARCHIVE_TIMESTAMP: // <xades141:ArchiveTimeStamp Id="time-stamp-a762ab0e-e05c-4cc8-a804-cf2c4ffb5516"> timeStampDom = DSSXMLUtils.addElement(documentDom, unsignedSignaturePropertiesDom, XAdES141, XADES141_ARCHIVE_TIME_STAMP); timestampDigestAlgorithm = params.getArchiveTimestampParameters().getDigestAlgorithm(); break; case ALL_DATA_OBJECTS_TIMESTAMP: timeStampDom = DSSXMLUtils.addElement(documentDom, signedDataObjectPropertiesDom, XAdES, XADES_ALL_DATA_OBJECTS_TIME_STAMP); break; case INDIVIDUAL_DATA_OBJECTS_TIMESTAMP: timeStampDom = DSSXMLUtils.addElement(documentDom, signedDataObjectPropertiesDom, XAdES, XADES_INDIVIDUAL_DATA_OBJECTS_TIME_STAMP); break; } if (LOG.isDebugEnabled()) { final String encodedDigestValue = DSSUtils.base64Encode(digestValue); LOG.debug("Timestamp generation: " + timestampDigestAlgorithm.getName() + " / " + timestampC14nMethod + " / " + encodedDigestValue); } final TimeStampToken timeStampToken = tspSource.getTimeStampResponse(timestampDigestAlgorithm, digestValue); final byte[] timeStampTokenBytes = timeStampToken.getEncoded(); final String base64EncodedTimeStampToken = DSSUtils.base64Encode(timeStampTokenBytes); final String timestampId = CertificateIdentifier.isUniqueIdentifier() ? tspSource.getUniqueId(digestValue) : UUID.randomUUID().toString(); timeStampDom.setAttribute(ID, "TS-" + timestampId); // <ds:CanonicalizationMethod Algorithm="http://www.w3.org/TR/2001/REC-xml-c14n-20010315"/> incorporateC14nMethod(timeStampDom, timestampC14nMethod); // <xades:EncapsulatedTimeStamp Id="time-stamp-token-6a150419-caab-4615-9a0b-6e239596643a">MIAGCSqGSIb3DQEH final Element encapsulatedTimeStampDom = DSSXMLUtils.addElement(documentDom, timeStampDom, XAdES, XADES_ENCAPSULATED_TIME_STAMP); encapsulatedTimeStampDom.setAttribute(ID, "ETS-" + timestampId); DSSXMLUtils.setTextNode(documentDom, encapsulatedTimeStampDom, base64EncodedTimeStampToken); } catch (IOException e) { throw new DSSException("Error during the creation of the XAdES timestamp!", e); } } protected List<CertificateToken> getToIncludeCertificateTokens(final ValidationContext valContext) { // if the certificate is already present within the KeyInfo then it is ignored. final Set<CertificateToken> processedCertificates = valContext.getProcessedCertificates(); final List<CertificateToken> keyInfoCertificates = xadesSignature.getKeyInfoCertificates(); final List<CertificateToken> toIncludeCertificates = new ArrayList<CertificateToken>(); for (final CertificateToken processedCertificate : processedCertificates) { if (!keyInfoCertificates.contains(processedCertificate)) { toIncludeCertificates.add(processedCertificate); } } return toIncludeCertificates; } }