package eu.europa.esig.dss.xades.signature;
/**
* 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
*/
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.apache.xml.security.signature.Reference;
import org.apache.xml.security.signature.XMLSignatureException;
import org.bouncycastle.tsp.TimeStampToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import eu.europa.esig.dss.DSSDocument;
import eu.europa.esig.dss.DSSException;
import eu.europa.esig.dss.DSSUtils;
import eu.europa.esig.dss.DigestAlgorithm;
import eu.europa.esig.dss.InMemoryDocument;
import eu.europa.esig.dss.SignatureLevel;
import eu.europa.esig.dss.SignaturePackaging;
import eu.europa.esig.dss.TimestampParameters;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.validation.AdvancedSignature;
import eu.europa.esig.dss.validation.CommonCertificateVerifier;
import eu.europa.esig.dss.validation.SignedDocumentValidator;
import eu.europa.esig.dss.validation.TimestampInclude;
import eu.europa.esig.dss.validation.TimestampToken;
import eu.europa.esig.dss.x509.CertificatePool;
import eu.europa.esig.dss.x509.TimestampType;
import eu.europa.esig.dss.x509.tsp.TSPSource;
import eu.europa.esig.dss.xades.DSSReference;
import eu.europa.esig.dss.xades.XAdESSignatureParameters;
import eu.europa.esig.dss.xades.XPathQueryHolder;
import eu.europa.esig.dss.xades.validation.XAdESSignature;
import eu.europa.esig.dss.xades.validation.XMLDocumentValidator;
/**
* Class providing (content) timestamp generating methods
*/
public class TimestampService {
private static final Logger LOG = LoggerFactory.getLogger(TimestampService.class);
private final TSPSource tspSource;
private final CertificatePool certificatePool;
private final XPathQueryHolder xPathQueryHolder;
private final CommonCertificateVerifier commonCertificateVerifier = new CommonCertificateVerifier(true);
// TODO (12/09/2014): To be replaced for the new release (4.2.0)
private final String fakeSignatureValue = "kKk8sPvKC4RN1/W8Uqan2zgqNCH2Uh6I4/uQPha25W6Lz6poWuxmi9y8/iCR2anbFb1k4n3d0eJxzWzdD4ubz478it9J0jhFi/4ANFJG+FVrWqH9gw/nXnfy2nULQOY466HE172mIAjKjWdPrpo6z1IRWHYbzNbL4iSO8BxqMx0=";
/**
* Basic constructor, new CertificatePool created
*
* @param tspSource
* The TSPSource to be used for the Timestamp generation
*/
public TimestampService(final TSPSource tspSource) {
if (tspSource == null) {
throw new NullPointerException();
}
this.tspSource = tspSource;
certificatePool = new CertificatePool();
xPathQueryHolder = new XPathQueryHolder();
}
/**
* Alternative constructor
*
* @param tspSource
* The TSPSource to be used for the Timestamp generation
* @param certificatePool
* The CertificatePool to be used for the TimestampToken
*/
public TimestampService(final TSPSource tspSource, final CertificatePool certificatePool) {
if (tspSource == null) {
throw new NullPointerException();
}
this.tspSource = tspSource;
if (certificatePool == null) {
throw new NullPointerException();
}
this.certificatePool = certificatePool;
xPathQueryHolder = new XPathQueryHolder();
}
/**
* Method that generates a ContentTimestamp as a DSSDocument
*
* @param externalParameters
* the original signature parameters
* @return contentTimestamp as an InMemoryDocument
*/
public DSSDocument generateCAdESContentTimestamp(final XAdESSignatureParameters externalParameters) {
final TimestampToken contentTimestampToken = generateCAdESContentTimestampAsTimestampToken(externalParameters);
final InMemoryDocument document = new InMemoryDocument(contentTimestampToken.getEncoded());
return document;
}
/**
* Method that generates a ContentTimestamp as a DSS TimestampToken
* *
*
* @param externalParameters
* the original signature parameters
* @return the ContentTimestamp as a DSS TimestampToken
*/
public TimestampToken generateCAdESContentTimestampAsTimestampToken(final XAdESSignatureParameters externalParameters) {
final byte[] bytes = DSSUtils.toByteArray(externalParameters.getDetachedContents().get(0).openStream());
final TimestampToken token = generateTimestampToken(TimestampType.CONTENT_TIMESTAMP, externalParameters, bytes);
return token;
}
/**
* Method that generates a XAdES ContentTimestamp (either an ALL DATA OBJECTS TIMESTAMP or an INDIVIDUAL DATA
* OBJECTS TIMESTAMP) and returns
* it as a TimestampToken
*
* @param toSignDocument
* @param externalParameters
* @param timestampType
* @return
*/
public TimestampToken generateXAdESContentTimestampAsTimestampToken(final DSSDocument toSignDocument, final XAdESSignatureParameters externalParameters,
final TimestampType timestampType) {
if (externalParameters == null) {
throw new NullPointerException();
}
// 1. Set initial parameters
final XAdESSignatureParameters signatureParameters = setSignatureParameters(externalParameters);
// 2. Build temporary signature structure
final XAdESLevelBaselineB levelBaselineB = new XAdESLevelBaselineB(commonCertificateVerifier);
byte[] signatureValueBytes = Utils.fromBase64(fakeSignatureValue);
final DSSDocument fullSignature = levelBaselineB.signDocument(toSignDocument, signatureParameters, signatureValueBytes);
final List<Reference> references = getReferencesFromValidatedSignature(toSignDocument, fullSignature);
// 4. Concatenate byte value of references, excluding references of type SignedProperties
byte[] concatenatedReferences = concatenateReferencesAsByteArray(references);
// 5. Generate ContentTimestamp using the concatenated references
switch (timestampType) {
case ALL_DATA_OBJECTS_TIMESTAMP:
case INDIVIDUAL_DATA_OBJECTS_TIMESTAMP:
return generateTimestampToken(timestampType, externalParameters, concatenatedReferences);
default:
throw new DSSException("Incompatible timestamp type");
}
}
/**
* Concatenates a set of given {@code Reference} into a byte array
*
* @param references
* the references to concatenate
* @return the concatenated references as a byte array
*/
private byte[] concatenateReferencesAsByteArray(final List<Reference> references) {
LOG.debug("Building ContentTimestamp - Concatenating references...");
final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
for (final Reference reference : references) {
// References of type "SignedProperties" are excluded
if (!xPathQueryHolder.XADES_SIGNED_PROPERTIES.equals(reference.getType())) {
try {
final byte[] referencedBytes = reference.getReferencedBytes();
buffer.write(referencedBytes);
} catch (XMLSignatureException e) {
throw new DSSException(e);
} catch (IOException e) {
throw new DSSException(e);
}
}
}
LOG.debug("Result: " + new String(buffer.toByteArray()));
return buffer.toByteArray();
}
/**
* Method that generates a XAdES ContentTimestamp (either an ALL DATA OBJECTS TIMESTAMP or an INDIVIDUAL DATA
* OBJECTS TIMESTAMP) and returns
* it as a DSSDocument
*
* @param toSignDocument
* the document for which a content timestamp must be generated
* @param externalParameters
* the original signature parameters
* @param timestampType
* the contentTimestamp type, either ALL_DATA_OBJECTS_TIMESTAMP or INDIVIDUAL_DATA_OBJECTS_TIMESTAMP
* @return a ContentTimestamp as a DSSDocument
*/
public DSSDocument generateXAdESContentTimestampAsDSSDocument(final DSSDocument toSignDocument, final XAdESSignatureParameters externalParameters,
final TimestampType timestampType) {
final TimestampToken timestampToken = generateXAdESContentTimestampAsTimestampToken(toSignDocument, externalParameters, timestampType);
return new InMemoryDocument(timestampToken.getEncoded());
}
/**
* Method that generates a TimestampToken given a TimestampType, a set of signature parameters and a byte array
* containing the concatenated references
*
* @param timestampType
* The TimestampType for the TimestampToken
* @param signatureParameters
* The signature parameters from which the contentTimestamp parameters must be retrieved
* @param references
* @return
*/
public TimestampToken generateTimestampToken(final TimestampType timestampType, final XAdESSignatureParameters signatureParameters,
final byte[] references) {
if (timestampType == null) {
throw new NullPointerException();
}
if (signatureParameters == null) {
throw new NullPointerException();
}
final TimestampParameters contentTimestampParameters = signatureParameters.getContentTimestampParameters();
if (contentTimestampParameters == null) {
throw new NullPointerException();
}
final DigestAlgorithm digestAlgorithm = contentTimestampParameters.getDigestAlgorithm();
if (digestAlgorithm == null) {
throw new NullPointerException();
}
byte[] digest = DSSUtils.digest(digestAlgorithm, references);
if (LOG.isTraceEnabled()) {
LOG.trace("Bytes to digest : [" + new String(references) + "]");
LOG.trace("Digest to timestamp: " + Utils.toBase64(digest));
}
final TimeStampToken timeStampResponse = tspSource.getTimeStampResponse(digestAlgorithm, digest);
final TimestampToken token = new TimestampToken(timeStampResponse, timestampType, certificatePool);
token.setCanonicalizationMethod(contentTimestampParameters.getCanonicalizationMethod());
// Case of XAdES INDIVIDUAL DATA OBJECTS TIMESTAMP: Timestamp Includes must be generated for each reference
if (TimestampType.INDIVIDUAL_DATA_OBJECTS_TIMESTAMP.equals(timestampType)) {
addTimestampTokenIncludes(signatureParameters.getReferences(), token);
}
return token;
}
/**
* Method setting the signature parameters used to generate the intermediary signature (XAdES-specific)
*
* @param externalParameters
* the original signature parameters
* @return a set of signature parameters
*/
private XAdESSignatureParameters setSignatureParameters(final XAdESSignatureParameters externalParameters) {
final XAdESSignatureParameters signatureParameters = new XAdESSignatureParameters();
signatureParameters.setReferences(externalParameters.getReferences());
signatureParameters.setSignatureTimestampParameters(externalParameters.getSignatureTimestampParameters());
signatureParameters.setSigningCertificate(externalParameters.getSigningCertificate());
signatureParameters.setSignaturePackaging(SignaturePackaging.DETACHED);
signatureParameters.setSignatureLevel(SignatureLevel.XAdES_BASELINE_B);
return signatureParameters;
}
/**
* @param toSignDocument
* @param signature
* @return
*/
private SignedDocumentValidator validateTemporarySignature(final DSSDocument toSignDocument, final DSSDocument signature) {
final SignedDocumentValidator validator = XMLDocumentValidator.fromDocument(signature);
validator.setCertificateVerifier(commonCertificateVerifier);
final List<DSSDocument> detachedContents = new ArrayList<DSSDocument>();
detachedContents.add(toSignDocument);
validator.setDetachedContents(detachedContents);
return validator;
}
/**
* Retrieves the references from a validated signature
*
* @param toSignDocument
* the document for which a content timestamp must be generated
* @param signature
* the signature value
* @return
*/
private List<Reference> getReferencesFromValidatedSignature(final DSSDocument toSignDocument, final DSSDocument signature) {
final SignedDocumentValidator validator = validateTemporarySignature(toSignDocument, signature);
// validator.validateDocument();
final List<AdvancedSignature> signatures = validator.getSignatures();
final XAdESSignature xAdESSignature = (XAdESSignature) signatures.get(0);
xAdESSignature.checkSignatureIntegrity();
final List<Reference> references = xAdESSignature.getReferences();
return references;
}
/**
* Adds a set of Timestamp Includes to a given Timestamp Token, based on the references that the Timestamp Token was
* built upon
*
* @param references
* the references the timestamp token was built upon
* @param token
* the timestamp token to which the includes must be added
* @return the updated Timestamp token, containing the set of Includes
*/
private TimestampToken addTimestampTokenIncludes(final List<DSSReference> references, final TimestampToken token) {
final List<TimestampInclude> includes = new ArrayList<TimestampInclude>();
for (DSSReference reference : references) {
TimestampInclude include = new TimestampInclude(reference.getUri(), "true");
includes.add(include);
}
token.setTimestampIncludes(includes);
return token;
}
}