/* * 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.cades; import java.io.InputStream; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.CMSProcessableByteArray; import org.bouncycastle.cms.CMSSignedData; import org.bouncycastle.cms.CMSSignedDataGenerator; import org.bouncycastle.cms.CMSTypedData; import org.bouncycastle.cms.SignerId; import org.bouncycastle.cms.SignerInfoGeneratorBuilder; import org.bouncycastle.cms.SignerInformation; import org.bouncycastle.cms.SignerInformationStore; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.europa.ec.markt.dss.DSSASN1Utils; import eu.europa.ec.markt.dss.DSSUtils; import eu.europa.ec.markt.dss.SignatureAlgorithm; 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.signature.AbstractSignatureService; import eu.europa.ec.markt.dss.signature.DSSDocument; import eu.europa.ec.markt.dss.signature.InMemoryDocument; import eu.europa.ec.markt.dss.signature.SignatureExtension; import eu.europa.ec.markt.dss.signature.SignatureLevel; import eu.europa.ec.markt.dss.signature.SignaturePackaging; import eu.europa.ec.markt.dss.signature.token.SignatureTokenConnection; import eu.europa.ec.markt.dss.validation102853.CertificateVerifier; /** * CAdES implementation of DocumentSignatureService * * @version $Revision$ - $Date$ */ public class CAdESService extends AbstractSignatureService { private static final Logger LOG = LoggerFactory.getLogger(CAdESService.class); private final CMSSignedDataBuilder cmsSignedDataBuilder; /** * This is the constructor to create an instance of the {@code CAdESService}. A certificate verifier must be provided. * * @param certificateVerifier {@code CertificateVerifier} provides information on the sources to be used in the validation process in the context of a signature. */ public CAdESService(final CertificateVerifier certificateVerifier) { super(certificateVerifier); cmsSignedDataBuilder = new CMSSignedDataBuilder(certificateVerifier); LOG.debug("+ CAdESService created"); } @Override public byte[] getDataToSign(final DSSDocument toSignDocument, final SignatureParameters parameters) throws DSSException { assertSigningDateInCertificateValidityRange(parameters); final SignaturePackaging packaging = parameters.getSignaturePackaging(); assertSignaturePackaging(packaging); final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm(); final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId()); final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = cmsSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, false); final CMSSignedData originalCmsSignedData = getCmsSignedData(toSignDocument, parameters); final CMSSignedDataGenerator cmsSignedDataGenerator = cmsSignedDataBuilder .createCMSSignedDataGenerator(parameters, customContentSigner, signerInfoGeneratorBuilder, originalCmsSignedData); final DSSDocument toSignData = getToSignData(toSignDocument, parameters, originalCmsSignedData); final CMSProcessableByteArray content = new CMSProcessableByteArray(toSignData.getBytes()); final boolean encapsulate = !SignaturePackaging.DETACHED.equals(packaging); DSSASN1Utils.generateCMSSignedData(cmsSignedDataGenerator, content, encapsulate); final byte[] bytes = customContentSigner.getOutputStream().toByteArray(); return bytes; } @Override public DSSDocument signDocument(final DSSDocument toSignDocument, final SignatureParameters parameters, final byte[] signatureValue) throws DSSException { assertSigningDateInCertificateValidityRange(parameters); final SignaturePackaging packaging = parameters.getSignaturePackaging(); assertSignaturePackaging(packaging); final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm(); final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue); final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder = cmsSignedDataBuilder.getSignerInfoGeneratorBuilder(parameters, true); final CMSSignedData originalCmsSignedData = getCmsSignedData(toSignDocument, parameters); if (originalCmsSignedData == null && SignaturePackaging.DETACHED.equals(packaging) && parameters.getDetachedContent() == null) { parameters.setDetachedContent(toSignDocument); } final CMSSignedDataGenerator cmsSignedDataGenerator = cmsSignedDataBuilder .createCMSSignedDataGenerator(parameters, customContentSigner, signerInfoGeneratorBuilder, originalCmsSignedData); final DSSDocument toSignData = getToSignData(toSignDocument, parameters, originalCmsSignedData); final CMSProcessableByteArray content = new CMSProcessableByteArray(toSignData.getBytes()); final boolean encapsulate = !SignaturePackaging.DETACHED.equals(packaging); final CMSSignedData cmsSignedData = DSSASN1Utils.generateCMSSignedData(cmsSignedDataGenerator, content, encapsulate); final CMSSignedDocument signature = new CMSSignedDocument(cmsSignedData); final SignatureLevel signatureLevel = parameters.getSignatureLevel(); if (!SignatureLevel.CAdES_BASELINE_B.equals(signatureLevel)) { // true: Only the last signature will be extended final SignatureExtension extension = getExtensionProfile(parameters, true); final DSSDocument extendSignature = extension.extendSignatures(signature, parameters); parameters.setDeterministicId(null); return extendSignature; } parameters.setDeterministicId(null); return signature; } @Override public DSSDocument signDocument(final DSSDocument toSignDocument, final SignatureParameters parameters) throws DSSException { final SignatureTokenConnection token = parameters.getSigningToken(); if (token == null) { throw new DSSNullException(SignatureTokenConnection.class, "", "The connection through available API to the SSCD must be set."); } final byte[] dataToSign = getDataToSign(toSignDocument, parameters); byte[] signatureValue = token.sign(dataToSign, parameters.getDigestAlgorithm(), parameters.getPrivateKeyEntry()); final DSSDocument document = signDocument(toSignDocument, parameters, signatureValue); return document; } /** * This method countersigns a signature identified through its SignerId * * @param toCounterSignDocument the original signature document containing the signature to countersign * @param parameters the signature parameters * @param selector the SignerId identifying the signature to countersign * @return the updated signature document, in which the countersignature has been embedded */ public DSSDocument counterSignDocument(final DSSDocument toCounterSignDocument, final SignatureParameters parameters, SignerId selector) { final SignatureTokenConnection token = parameters.getSigningToken(); if (token == null) { throw new DSSNullException(SignatureTokenConnection.class, "", "The connection through available API to the SSCD must be set."); } try { //Retrieve the original signature final InputStream inputStream = toCounterSignDocument.openStream(); final CMSSignedData cmsSignedData = new CMSSignedData(inputStream); DSSUtils.closeQuietly(inputStream); SignerInformationStore signerInfos = cmsSignedData.getSignerInfos(); SignerInformation signerInformation = signerInfos.get(selector); //Generate a signed digest on the contents octets of the signature octet String in the identified SignerInfo value //of the original signature's SignedData byte[] dataToSign = signerInformation.getSignature(); byte[] signatureValue = token.sign(dataToSign, parameters.getDigestAlgorithm(), parameters.getPrivateKeyEntry()); //Set the countersignature builder CounterSignatureBuilder builder = new CounterSignatureBuilder(certificateVerifier); builder.setCmsSignedData(cmsSignedData); builder.setSelector(selector); final SignatureAlgorithm signatureAlgorithm = parameters.getSignatureAlgorithm(); final CustomContentSigner customContentSigner = new CustomContentSigner(signatureAlgorithm.getJCEId(), signatureValue); SignerInfoGeneratorBuilder signerInformationGeneratorBuilder = builder.getSignerInfoGeneratorBuilder(parameters, true); CMSSignedDataGenerator cmsSignedDataGenerator = builder.createCMSSignedDataGenerator(parameters, customContentSigner, signerInformationGeneratorBuilder, null); CMSTypedData content = cmsSignedData.getSignedContent(); CMSSignedData signedData = cmsSignedDataGenerator.generate(content); final CMSSignedData countersignedCMSData = builder.signDocument(signedData); final CMSSignedDocument signature = new CMSSignedDocument(countersignedCMSData); return signature; } catch (CMSException e) { throw new DSSException("Cannot parse CMS data", e); } } @Override public DSSDocument extendDocument(final DSSDocument toExtendDocument, final SignatureParameters parameters) { // false: All signature are extended final SignatureExtension extension = getExtensionProfile(parameters, false); final DSSDocument dssDocument = extension.extendSignatures(toExtendDocument, parameters); return dssDocument; } /** * This method retrieves the data to be signed. It this data is located within a signature then it is extracted. * * @param toSignDocument document to sign * @param parameters set of the driving signing parameters * @param originalCmsSignedData the signed data extracted from an existing signature or null * @return */ private DSSDocument getToSignData(final DSSDocument toSignDocument, final SignatureParameters parameters, final CMSSignedData originalCmsSignedData) { final DSSDocument detachedContent = parameters.getDetachedContent(); if (detachedContent != null) { return detachedContent; } else { if (originalCmsSignedData == null) { return toSignDocument; } else { return getSignedContent(originalCmsSignedData); } } } /** * This method returns the signed content of CMSSignedData. * * @param cmsSignedData the already signed {@code CMSSignedData} * @return the original toSignDocument or null */ private DSSDocument getSignedContent(final CMSSignedData cmsSignedData) { if (cmsSignedData != null) { final CMSTypedData signedContent = cmsSignedData.getSignedContent(); final byte[] documentBytes = (signedContent != null) ? (byte[]) signedContent.getContent() : null; final InMemoryDocument inMemoryDocument = new InMemoryDocument(documentBytes); return inMemoryDocument; } return null; } /** * @param parameters set of driving signing parameters * @param onlyLastCMSSignature indicates if only the last CSM signature should be extended * @return {@code SignatureExtension} related to the predefine profile */ private SignatureExtension getExtensionProfile(final SignatureParameters parameters, final boolean onlyLastCMSSignature) { final SignatureLevel signatureLevel = parameters.getSignatureLevel(); switch (signatureLevel) { case CAdES_BASELINE_T: return new CAdESLevelBaselineT(tspSource, certificateVerifier, onlyLastCMSSignature); case CAdES_BASELINE_LT: return new CAdESLevelBaselineLT(tspSource, certificateVerifier, onlyLastCMSSignature); case CAdES_BASELINE_LTA: return new CAdESLevelBaselineLTA(tspSource, certificateVerifier, onlyLastCMSSignature); default: throw new DSSException("Unsupported signature format " + signatureLevel); } } /** * In case of an enveloping signature if the signed content's content is null then the null is returned. * * @param dssDocument {@code DSSDocument} containing the data to be signed or {@code CMSSignedData} * @param parameters set of driving signing parameters * @return the {@code CMSSignedData} if the dssDocument is an CMS signed message. Null otherwise. */ private CMSSignedData getCmsSignedData(final DSSDocument dssDocument, final SignatureParameters parameters) { CMSSignedData cmsSignedData = null; try { // check if input dssDocument is already signed cmsSignedData = new CMSSignedData(dssDocument.getBytes()); final SignaturePackaging signaturePackaging = parameters.getSignaturePackaging(); if (signaturePackaging == SignaturePackaging.ENVELOPING) { if (cmsSignedData.getSignedContent().getContent() == null) { cmsSignedData = null; } } } catch (Exception e) { // not a parallel signature } return cmsSignedData; } /** * @param packaging {@code SignaturePackaging} to be checked * @throws DSSException if the packaging is not supported for this kind of signature */ private void assertSignaturePackaging(final SignaturePackaging packaging) throws DSSException { if (packaging != SignaturePackaging.ENVELOPING && packaging != SignaturePackaging.DETACHED) { throw new DSSException("Unsupported signature packaging: " + packaging); } } }