/*
* 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 java.security.cert.CertificateEncodingException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.security.auth.x500.X500Principal;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableByteArray;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.DefaultSignedAttributeTableGenerator;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
import org.bouncycastle.cms.SimpleAttributeTableGenerator;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.util.Store;
import eu.europa.ec.markt.dss.DSSUtils;
import eu.europa.ec.markt.dss.exception.DSSException;
import eu.europa.ec.markt.dss.parameter.ChainCertificate;
import eu.europa.ec.markt.dss.parameter.SignatureParameters;
import eu.europa.ec.markt.dss.validation102853.CertificateVerifier;
import eu.europa.ec.markt.dss.validation102853.TrustedCertificateSource;
import static org.bouncycastle.asn1.cms.CMSObjectIdentifiers.id_ri_ocsp_response;
import static org.bouncycastle.asn1.ocsp.OCSPObjectIdentifiers.id_pkix_ocsp_basic;
/**
* <p/>
* <p/>
* DISCLAIMER: Project owner DG-MARKT.
*
* @author <a href="mailto:dgmarkt.Project-DSS@arhs-developments.com">ARHS Developments</a>
* @version $Revision: 1016 $ - $Date: 2011-06-17 15:30:45 +0200 (Fri, 17 Jun 2011) $
*/
public class CMSSignedDataBuilder {
private CertificateVerifier certificateVerifier;
/**
* This is the default constructor for {@code CMSSignedDataGeneratorBuilder}. The {@code CertificateVerifier} is used to find the trusted certificates.
*
* @param certificateVerifier {@code CertificateVerifier} provides information on the sources to be used in the validation process in the context of a signature.
*/
public CMSSignedDataBuilder(final CertificateVerifier certificateVerifier) {
this.certificateVerifier = certificateVerifier;
}
/**
* Note:
* Section 5.1 of RFC 3852 [4] requires that, the CMS SignedData version be set to 3 if certificates from
* SignedData is present AND (any version 1 attribute certificates are present OR any SignerInfo structures
* are version 3 OR eContentType from encapContentInfo is other than id-data). Otherwise, the CMS
* SignedData version is required to be set to 1.
* ---> CMS SignedData Version is handled automatically by BouncyCastle.
*
* @param parameters set of the driving signing parameters
* @param contentSigner the contentSigned to get the hash of the data to be signed
* @param signerInfoGeneratorBuilder true if the unsigned attributes must be included
* @param originalSignedData the original signed data if extending an existing signature. null otherwise.
* @return the bouncycastle signed data generator which signs the document and adds the required signed and unsigned CMS attributes
* @throws eu.europa.ec.markt.dss.exception.DSSException
*/
protected CMSSignedDataGenerator createCMSSignedDataGenerator(final SignatureParameters parameters, final ContentSigner contentSigner,
final SignerInfoGeneratorBuilder signerInfoGeneratorBuilder,
final CMSSignedData originalSignedData) throws DSSException {
try {
final X509Certificate signingCertificate = parameters.getSigningCertificate();
final CMSSignedDataGenerator generator = new CMSSignedDataGenerator();
final X509CertificateHolder certHolder = DSSUtils.getX509CertificateHolder(signingCertificate);
final SignerInfoGenerator signerInfoGenerator = signerInfoGeneratorBuilder.build(contentSigner, certHolder);
generator.addSignerInfoGenerator(signerInfoGenerator);
final Set<X509Certificate> newCertificateChain = new HashSet<X509Certificate>();
if (originalSignedData != null) {
generator.addSigners(originalSignedData.getSignerInfos());
generator.addAttributeCertificates(originalSignedData.getAttributeCertificates());
generator.addCRLs(originalSignedData.getCRLs());
generator.addOtherRevocationInfo(id_pkix_ocsp_basic, originalSignedData.getOtherRevocationInfo(id_pkix_ocsp_basic));
generator.addOtherRevocationInfo(id_ri_ocsp_response, originalSignedData.getOtherRevocationInfo(id_ri_ocsp_response));
final Store certificates = originalSignedData.getCertificates();
final Collection<X509CertificateHolder> certificatesMatches = certificates.getMatches(null);
for (final X509CertificateHolder certificatesMatch : certificatesMatches) {
final X509Certificate x509Certificate = DSSUtils.getCertificate(certificatesMatch);
newCertificateChain.add(x509Certificate);
}
}
final List<ChainCertificate> certificateChain = parameters.getCertificateChain();
for (final ChainCertificate chainCertificate : certificateChain) {
final X509Certificate x509Certificate = chainCertificate.getX509Certificate();
newCertificateChain.add(x509Certificate);
}
final boolean trustAnchorBPPolicy = parameters.bLevel().isTrustAnchorBPPolicy();
final Store jcaCertStore = getJcaCertStore(newCertificateChain, trustAnchorBPPolicy);
generator.addCertificates(jcaCertStore);
return generator;
} catch (CMSException e) {
throw new DSSException(e);
} catch (OperatorCreationException e) {
throw new DSSException(e);
}
}
/**
* @param parameters the parameters of the signature containing values for the attributes
* @param includeUnsignedAttributes true if the unsigned attributes must be included
* @return a SignerInfoGeneratorBuilder that generate the signed and unsigned attributes according to the CAdESLevelBaselineB
*/
SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(final SignatureParameters parameters, final boolean includeUnsignedAttributes) {
final CAdESLevelBaselineB cadesProfile = new CAdESLevelBaselineB();
final AttributeTable signedAttributes = cadesProfile.getSignedAttributes(parameters);
AttributeTable unsignedAttributes = null;
if (includeUnsignedAttributes) {
unsignedAttributes = cadesProfile.getUnsignedAttributes();
}
return getSignerInfoGeneratorBuilder(signedAttributes, unsignedAttributes);
}
/**
* @param signedAttributes the signedAttributes
* @param unsignedAttributes the unsignedAttributes
* @return a SignerInfoGeneratorBuilder that generate the signed and unsigned attributes according to the parameters
*/
private SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(AttributeTable signedAttributes, AttributeTable unsignedAttributes) {
if (signedAttributes != null && signedAttributes.size() == 0) {
signedAttributes = null;
}
final DefaultSignedAttributeTableGenerator signedAttributeGenerator = new DefaultSignedAttributeTableGenerator(signedAttributes);
if (unsignedAttributes != null && unsignedAttributes.size() == 0) {
unsignedAttributes = null;
}
final SimpleAttributeTableGenerator unsignedAttributeGenerator = new SimpleAttributeTableGenerator(unsignedAttributes);
return getSignerInfoGeneratorBuilder(signedAttributeGenerator, unsignedAttributeGenerator);
}
/**
* @param signedAttributeGenerator the signedAttribute generator
* @param unsignedAttributeGenerator the unsignedAttribute generator
* @return a SignerInfoGeneratorBuilder that generate the signed and unsigned attributes according to the parameters
*/
private SignerInfoGeneratorBuilder getSignerInfoGeneratorBuilder(DefaultSignedAttributeTableGenerator signedAttributeGenerator,
SimpleAttributeTableGenerator unsignedAttributeGenerator) {
final DigestCalculatorProvider digestCalculatorProvider = new BcDigestCalculatorProvider();
SignerInfoGeneratorBuilder sigInfoGeneratorBuilder = new SignerInfoGeneratorBuilder(digestCalculatorProvider);
sigInfoGeneratorBuilder.setSignedAttributeGenerator(signedAttributeGenerator);
sigInfoGeneratorBuilder.setUnsignedAttributeGenerator(unsignedAttributeGenerator);
return sigInfoGeneratorBuilder;
}
/**
* The order of the certificates is important, the fist one must be the signing certificate.
*
* @return a store with the certificate chain of the signing certificate. The {@code Collection} is unique.
* @throws CertificateEncodingException
*/
private JcaCertStore getJcaCertStore(final Collection<X509Certificate> certificateChain, boolean trustAnchorBPPolicy) {
try {
final Collection<X509Certificate> certs = new ArrayList<X509Certificate>();
for (final X509Certificate certificateInChain : certificateChain) {
// CAdES-Baseline-B: do not include certificates found in the trusted list
if (trustAnchorBPPolicy) {
final X500Principal subjectX500Principal = certificateInChain.getSubjectX500Principal();
final TrustedCertificateSource trustedCertSource = certificateVerifier.getTrustedCertSource();
if (trustedCertSource != null) {
if (!trustedCertSource.get(subjectX500Principal).isEmpty()) {
continue;
}
}
}
certs.add(certificateInChain);
}
return new JcaCertStore(certs);
} catch (CertificateEncodingException e) {
throw new DSSException(e);
}
}
protected CMSSignedData regenerateCMSSignedData(CMSSignedData cmsSignedData, SignatureParameters parameters, Store certificatesStore, Store attributeCertificatesStore,
Store crlsStore, Store otherRevocationInfoFormatStoreBasic, Store otherRevocationInfoFormatStoreOcsp) {
try {
final CMSSignedDataGenerator cmsSignedDataGenerator = new CMSSignedDataGenerator();
cmsSignedDataGenerator.addSigners(cmsSignedData.getSignerInfos());
cmsSignedDataGenerator.addAttributeCertificates(attributeCertificatesStore);
cmsSignedDataGenerator.addCertificates(certificatesStore);
cmsSignedDataGenerator.addCRLs(crlsStore);
cmsSignedDataGenerator.addOtherRevocationInfo(id_pkix_ocsp_basic, otherRevocationInfoFormatStoreBasic);
cmsSignedDataGenerator.addOtherRevocationInfo(id_ri_ocsp_response, otherRevocationInfoFormatStoreOcsp);
final boolean encapsulate = cmsSignedData.getSignedContent() != null;
if (!encapsulate) {
final InputStream inputStream = parameters.getDetachedContent().openStream();
final CMSProcessableByteArray content = new CMSProcessableByteArray(DSSUtils.toByteArray(inputStream));
cmsSignedData = cmsSignedDataGenerator.generate(content, encapsulate);
} else {
cmsSignedData = cmsSignedDataGenerator.generate(cmsSignedData.getSignedContent(), encapsulate);
}
return cmsSignedData;
} catch (CMSException e) {
throw new DSSException(e);
}
}
//TODO Vincent: regeneration of SignedData -> Content-TS
}