All Downloads are FREE. Search and download functionalities are using the official Maven repository.

eu.europa.esig.dss.cades.validation.CAdESTimestampDataBuilder Maven / Gradle / Ivy

Go to download

DSS CAdES contains the code for the creation and validation of CAdES signatures.

There is a newer version: 6.0.d4j.2
Show newest version
/**
 * 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.cades.validation;

import static eu.europa.esig.dss.spi.OID.id_aa_ets_archiveTimestampV2;
import static eu.europa.esig.dss.spi.OID.id_aa_ets_archiveTimestampV3;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_certificateRefs;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_ets_revocationRefs;
import static org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers.id_aa_signatureTimeStampToken;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.List;

import org.bouncycastle.asn1.ASN1EncodableVector;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Sequence;
import org.bouncycastle.asn1.ASN1Set;
import org.bouncycastle.asn1.BEROctetString;
import org.bouncycastle.asn1.BERSequence;
import org.bouncycastle.asn1.BERSet;
import org.bouncycastle.asn1.BERTaggedObject;
import org.bouncycastle.asn1.DERSequence;
import org.bouncycastle.asn1.DERTaggedObject;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.ContentInfo;
import org.bouncycastle.asn1.cms.SignedData;
import org.bouncycastle.asn1.cms.SignerInfo;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.tsp.TimeStampToken;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import eu.europa.esig.dss.cades.CMSUtils;
import eu.europa.esig.dss.cades.signature.CadesLevelBaselineLTATimestampExtractor;
import eu.europa.esig.dss.enumerations.ArchiveTimestampType;
import eu.europa.esig.dss.enumerations.DigestAlgorithm;
import eu.europa.esig.dss.model.DSSDocument;
import eu.europa.esig.dss.model.DSSException;
import eu.europa.esig.dss.model.InMemoryDocument;
import eu.europa.esig.dss.spi.DSSASN1Utils;
import eu.europa.esig.dss.spi.DSSUtils;
import eu.europa.esig.dss.utils.Utils;
import eu.europa.esig.dss.validation.timestamp.TimestampDataBuilder;
import eu.europa.esig.dss.validation.timestamp.TimestampToken;

public class CAdESTimestampDataBuilder implements TimestampDataBuilder {

	private static final Logger LOG = LoggerFactory.getLogger(CAdESTimestampDataBuilder.class);
	
	private final List detachedDocuments;
	private final SignerInformation signerInformation;

	private CMSSignedData cmsSignedData;
	private CadesLevelBaselineLTATimestampExtractor timestampExtractor;
	
	protected CAdESTimestampDataBuilder(final SignerInformation signerInformation, final List detachedDocuments) {
		this.signerInformation = signerInformation;
		this.detachedDocuments = detachedDocuments;
	}
	
	public CAdESTimestampDataBuilder(final CMSSignedData cmsSignedData, final SignerInformation signerInformation, 
			List detachedDocuments, final CadesLevelBaselineLTATimestampExtractor timestampExtractor) {
		this.cmsSignedData = cmsSignedData;
		this.signerInformation = signerInformation;
		this.detachedDocuments = detachedDocuments;
		this.timestampExtractor = timestampExtractor;
	}
	
	@Override
	public DSSDocument getContentTimestampData(TimestampToken timestampToken) {
		return getOriginalDocument();
	}

	@Override
	public DSSDocument getSignatureTimestampData(TimestampToken timestampToken) {
		byte[] signature = signerInformation.getSignature();
		return new InMemoryDocument(signature);
	}

	@Override
	public DSSDocument getTimestampX1Data(TimestampToken timestampToken) {
		try (ByteArrayOutputStream data = new ByteArrayOutputStream()) {
			data.write(signerInformation.getSignature());
			// We don't include the outer SEQUENCE, only the attrType and
			// attrValues as stated by the TS §6.3.5, NOTE 2

			final Attribute attribute = CMSUtils.getUnsignedAttribute(signerInformation, id_aa_signatureTimeStampToken);
			if (attribute != null) {
				data.write(DSSASN1Utils.getDEREncoded(attribute.getAttrType()));
				data.write(DSSASN1Utils.getDEREncoded(attribute.getAttrValues()));
			}
			// Method is common to Type 1 and Type 2
			data.write(getTimestampX2DataBytes(timestampToken));
			byte[] byteArray = data.toByteArray();
			return new InMemoryDocument(byteArray);
		} catch (IOException e) {
			throw new DSSException(e);
		}
	}

	@Override
	public DSSDocument getTimestampX2Data(final TimestampToken timestampToken) {
		byte[] timestampX2DataBytes = getTimestampX2DataBytes(timestampToken);
		return new InMemoryDocument(timestampX2DataBytes);
	}
	
	private byte[] getTimestampX2DataBytes(final TimestampToken timestampToken) {
		try (ByteArrayOutputStream data = new ByteArrayOutputStream()) {
			// Those are common to Type 1 and Type 2
			final Attribute certAttribute = CMSUtils.getUnsignedAttribute(signerInformation, id_aa_ets_certificateRefs);
			final Attribute revAttribute = CMSUtils.getUnsignedAttribute(signerInformation, id_aa_ets_revocationRefs);
			if (certAttribute != null) {
				data.write(DSSASN1Utils.getDEREncoded(certAttribute.getAttrType()));
				data.write(DSSASN1Utils.getDEREncoded(certAttribute.getAttrValues()));
			}
			if (revAttribute != null) {
				data.write(DSSASN1Utils.getDEREncoded(revAttribute.getAttrType()));
				data.write(DSSASN1Utils.getDEREncoded(revAttribute.getAttrValues()));
			}

			return data.toByteArray();
		} catch (IOException e) {
			throw new DSSException(e);
		}
	}

	@Override
	public DSSDocument getArchiveTimestampData(final TimestampToken timestampToken) throws DSSException {

		final ArchiveTimestampType archiveTimestampType = timestampToken.getArchiveTimestampType();
		DSSDocument archiveTimestampData;
		switch (archiveTimestampType) {
		case CAdES_V2:
			/**
			 * There is a difference between message imprint calculation in ETSI TS 101 733 version 1.8.3 and version 2.2.1.
			 * So we first check the message imprint according to 2.2.1 version and then if it fails get the message imprint
			 * data for the 1.8.3 version message imprint calculation. 
			 */
			archiveTimestampData = getArchiveTimestampDataV2(timestampToken, true);
			if (!timestampToken.matchData(archiveTimestampData, true)) {
				if (LOG.isDebugEnabled()) {
					LOG.debug("Unable to match message imprint for an Archive TimestampToken V2 with Id '{}' "
							+ "by including unsigned attribute tags and length, try to compute the data without...", timestampToken.getDSSIdAsString());
				}
				archiveTimestampData = getArchiveTimestampDataV2(timestampToken, false);
			}
			break;
		case CAdES_V3:
			archiveTimestampData = getArchiveTimestampDataV3(timestampToken);
			break;
		default:
			throw new DSSException("Unsupported ArchiveTimestampType " + archiveTimestampType);
		}
		return archiveTimestampData;
	}

	private DSSDocument getArchiveTimestampDataV3(final TimestampToken timestampToken) throws DSSException {

		final Attribute atsHashIndexAttribute = timestampExtractor.getVerifiedAtsHashIndex(signerInformation, timestampToken);

        final DigestAlgorithm messageImprintDigestAlgorithm = timestampToken.getMessageImprint().getAlgorithm();
        byte[] originalDocumentDigest = getOriginalDocumentDigest(messageImprintDigestAlgorithm);
        if (originalDocumentDigest != null) {
            byte[] archiveTimestampDataV3 = timestampExtractor.getArchiveTimestampDataV3(signerInformation, atsHashIndexAttribute, originalDocumentDigest);
            return new InMemoryDocument(archiveTimestampDataV3);
        }
		LOG.error("The original document is not found for TimestampToken with Id '{}'! "
				+ "Unable to compute message imprint.", timestampToken.getDSSIdAsString());
        return null;
	}
	
	private byte[] getOriginalDocumentDigest(DigestAlgorithm algo) {
		DSSDocument originalDocument = getOriginalDocument();
		if (originalDocument != null) {
			return Utils.fromBase64(originalDocument.getDigest(algo));
		} else {
			return null;
		}
	}
	
	/**
	 * There is a difference in ETSI TS 101 733 version 1.8.3 and version 2.2.1 in archive-timestamp-v2 hash calculation.
	 * In the 1.8.3 version the calculation did not include the tag and the length octets of the unsigned attributes set.
	 * The hash calculation is described in Annex K in both versions of ETSI TS 101 733.
	 * The differences are in TableK.3: Signed Data in rows 22 and 23.
	 * However, there is a note in 2.2.1 version (Annex K, Table K.3: SignedData, Note 3) that says:
	 * "A previous version of CAdES did not include the tag and length octets of this SET OF type
	 * of unsignedAttrs element in this annex, which contradicted the normative section. To maximize
	 * interoperability, it is recommended to simultaneously compute the two hash values
	 * (including and not including the tag and length octets of SET OF type) and to test
	 * the value of the timestamp against both."
	 * The includeUnsignedAttrsTagAndLength parameter decides whether the tag and length octets are included.
	 * 
	 * According to RFC 5652 it is possible to use DER or BER encoding for SignedData structure.
	 * The exception is the signed attributes attribute and authenticated attributes attributes which
	 * have to be DER encoded. 
	 * 
	 * @param timestampToken
	 * @param includeUnsignedAttrsTagAndLength
	 * @return
	 * @throws DSSException
	 */
	private DSSDocument getArchiveTimestampDataV2(TimestampToken timestampToken, boolean includeUnsignedAttrsTagAndLength) throws DSSException {

		try (ByteArrayOutputStream data = new ByteArrayOutputStream()) {

			final ContentInfo contentInfo = cmsSignedData.toASN1Structure();
			final SignedData signedData = SignedData.getInstance(contentInfo.getContent());
			
			byte[] contentInfoBytes = getContentInfoBytes(signedData);
			data.write(contentInfoBytes);
			
			if (CMSUtils.isDetachedSignature(cmsSignedData)) {
				byte[] originalDocumentBinaries = getOriginalDocumentBinaries();
				if (originalDocumentBinaries == null) {
					LOG.warn("The detached content is not provided for a TimestampToken with Id '{}'. "
							+ "Not possible to compute message imprint!", timestampToken.getDSSIdAsString());
					return null;
				}
				data.write(originalDocumentBinaries);
			}
			
			byte[] certificateBytes = getCertificateDataBytes(signedData);
			if (Utils.isArrayNotEmpty(certificateBytes)) {
				data.write(certificateBytes);
			}
			
			byte[] crlDataBytes = getCRLDataBytes(signedData);
			if (Utils.isArrayNotEmpty(crlDataBytes)) {
				data.write(crlDataBytes);
			}

			final SignerInfo signerInfo = signerInformation.toASN1Structure();
			byte[] signerInfoBytes = getSignerInfoBytes(timestampToken, includeUnsignedAttrsTagAndLength, signerInfo);
			data.write(signerInfoBytes);

			final byte[] result = data.toByteArray();
			return new InMemoryDocument(result);
		} catch (IOException e) {
			throw new DSSException(e);
		} catch (Exception e) {
			// When error in computing or in format the algorithm just continues.
			LOG.error("An error in computing of message impring for a TimestampToken with Id : {}. Reason : {}", 
					timestampToken.getDSSIdAsString(), e.getMessage(), e);
			return null;
		}
	}
	
	private byte[] getContentInfoBytes(final SignedData signedData) {
		final ContentInfo content = signedData.getEncapContentInfo();
		byte[] contentInfoBytes;
		if (content.getContent() instanceof BEROctetString) {
			contentInfoBytes = DSSASN1Utils.getBEREncoded(content);
		} else {
			contentInfoBytes = DSSASN1Utils.getDEREncoded(content);
		}
		if (LOG.isTraceEnabled()) {
			LOG.trace("Content Info: {}", DSSUtils.toHex(contentInfoBytes));
		}
		return contentInfoBytes;
	}
	
	private byte[] getOriginalDocumentBinaries() {
		/*
		 * Detached signatures have either no encapContentInfo in signedData, or it
		 * exists but has no eContent
		 */
		DSSDocument originalDocument = getOriginalDocument();
		if (originalDocument != null) {
			return DSSUtils.toByteArray(getOriginalDocument());
		}
		return null;
	}
	
	private byte[] getCertificateDataBytes(final SignedData signedData) throws IOException {
		byte[] certificatesBytes = null;
		
		final ASN1Set certificates = signedData.getCertificates();
		if (certificates != null) {
			/*
			 * In order to calculate correct message imprint it is important
			 * to use the correct encoding.
			 */
			if (certificates instanceof BERSet) {
				certificatesBytes = new BERTaggedObject(false, 0, new BERSequence(certificates.toArray())).getEncoded();
			} else {
				certificatesBytes = new DERTaggedObject(false, 0, new DERSequence(certificates.toArray())).getEncoded();
			}
			
			if (LOG.isTraceEnabled()) {
				LOG.trace("Certificates: {}", DSSUtils.toHex(certificatesBytes));
			}
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("Certificates are not present in the SignedData.");
		}
		return certificatesBytes;
	}
	
	private byte[] getCRLDataBytes(final SignedData signedData) throws IOException {
		byte[] crlBytes = null;
		
		final ASN1Set crLs = signedData.getCRLs();
		if (crLs != null) {
			
			if (signedData.getCRLs() instanceof BERSet) {
				crlBytes = new BERTaggedObject(false, 1, new BERSequence(crLs.toArray())).getEncoded();
			} else {
				crlBytes = new DERTaggedObject(false, 1, new DERSequence(crLs.toArray())).getEncoded();
			}
			if (LOG.isTraceEnabled()) {
				LOG.trace("CRLs: {}", DSSUtils.toHex(crlBytes));
			}
		}
		if (LOG.isDebugEnabled()) {
			LOG.debug("CRLs are not present in the SignedData.");
		}
		return crlBytes;
	}
	
	private byte[] getSignerInfoBytes(final TimestampToken timestampToken, boolean includeUnsignedAttrsTagAndLength, 
			final SignerInfo signerInfo) throws IOException {
		try (ByteArrayOutputStream signerByteArrayOutputStream = new ByteArrayOutputStream()) {
			
			final ASN1Set unauthenticatedAttributes = signerInfo.getUnauthenticatedAttributes();
			final ASN1Sequence filteredUnauthenticatedAttributes = filterUnauthenticatedAttributes(unauthenticatedAttributes, timestampToken);
			final ASN1Sequence asn1Object = getSignerInfoEncoded(signerInfo, filteredUnauthenticatedAttributes, includeUnsignedAttrsTagAndLength);
			for (int ii = 0; ii < asn1Object.size(); ii++) {
				final byte[] signerInfoBytes = DSSASN1Utils.getDEREncoded(asn1Object.getObjectAt(ii).toASN1Primitive());
				signerByteArrayOutputStream.write(signerInfoBytes);
			}
			final byte[] signerInfoBytes = signerByteArrayOutputStream.toByteArray();
			if (LOG.isTraceEnabled()) {
				LOG.trace("SignerInfoBytes: {}", DSSUtils.toHex(signerInfoBytes));
			}
			return signerInfoBytes;
			
		}
	}

	/**
	 * Remove any archive-timestamp-v2/3 attribute added after the
	 * timestampToken
	 */
	private ASN1Sequence filterUnauthenticatedAttributes(ASN1Set unauthenticatedAttributes, TimestampToken timestampToken) {
		ASN1EncodableVector result = new ASN1EncodableVector();
		for (int ii = 0; ii < unauthenticatedAttributes.size(); ii++) {

			final Attribute attribute = Attribute.getInstance(unauthenticatedAttributes.getObjectAt(ii));
			final ASN1ObjectIdentifier attrType = attribute.getAttrType();
			if (id_aa_ets_archiveTimestampV2.equals(attrType) || id_aa_ets_archiveTimestampV3.equals(attrType)) {
				try {

					TimeStampToken token = DSSASN1Utils.getTimeStampToken(attribute);
					if (!token.getTimeStampInfo().getGenTime().before(timestampToken.getGenerationTime())) {
						continue;
					}
				} catch (Exception e) {
					throw new DSSException(e);
				}
			}
			result.add(unauthenticatedAttributes.getObjectAt(ii));
		}
		return new DERSequence(result);
	}

	/**
	 * Copied from org.bouncycastle.asn1.cms.SignerInfo#toASN1Object() and
	 * adapted to be able to use the custom unauthenticatedAttributes
	 * 
	 * There is a difference in ETSI TS 101 733 version 1.8.3 and version 2.2.1 in archive-timestamp-v2 hash calculation.
	 * In the 1.8.3 version the calculation did not include the tag and the length octets of the unsigned attributes set.
	 * The hash calculation is described in Annex K in both versions of ETSI TS 101 733.
	 * The differences are in TableK.3: Signed Data in rows 22 and 23.
	 * However, there is a note in 2.2.1 version (Annex K, Table K.3: SignedData, Note 3) that says:
	 * "A previous version of CAdES did not include the tag and length octets of this SET OF type
	 * of unsignedAttrs element in this annex, which contradicted the normative section. To maximize
	 * interoperability, it is recommended to imultaneously compute the two hash values
	 * (including and not including the tag and length octets of SET OF type) and to test
	 * the value of the timestamp against both."
	 * The includeUnsignedAttrsTagAndLength parameter decides whether the tag and length octets are included.
	 *
	 * @param signerInfo
	 * @param signerInfo
	 * @param unauthenticatedAttributes
	 * @param includeUnsignedAttrsTagAndLength
	 * @return
	 */
	private ASN1Sequence getSignerInfoEncoded(final SignerInfo signerInfo, final ASN1Sequence unauthenticatedAttributes, final boolean includeUnsignedAttrsTagAndLength) {

		ASN1EncodableVector v = new ASN1EncodableVector();

		v.add(signerInfo.getVersion());
		v.add(signerInfo.getSID());
		v.add(signerInfo.getDigestAlgorithm());

		final DERTaggedObject signedAttributes = CMSUtils.getDERSignedAttributes(signerInformation);
		if (signedAttributes != null) {
			v.add(signedAttributes);
		}

		v.add(signerInfo.getDigestEncryptionAlgorithm());
		v.add(signerInfo.getEncryptedDigest());

		if (unauthenticatedAttributes != null) {
			if (includeUnsignedAttrsTagAndLength) {
				v.add(new DERTaggedObject(false, 1, unauthenticatedAttributes));
			} else {
				for (int i = 0; i < unauthenticatedAttributes.size(); i++) {
					v.add(unauthenticatedAttributes.getObjectAt(i));
				}
			}
		}
		
		return new DERSequence(v);
	}
	
	private DSSDocument getOriginalDocument() {
		try {
			return CMSUtils.getOriginalDocument(cmsSignedData, detachedDocuments);
		} catch (DSSException e) {
			LOG.error("Cannot extract original document! Reason : {}", e.getMessage());
			return null;
		}
	}

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy