eu.europa.ec.markt.dss.signature.cades.CAdESService.java Source code

Java tutorial

Introduction

Here is the source code for eu.europa.ec.markt.dss.signature.cades.CAdESService.java

Source

/*
 * 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);
        }
    }
}