org.dihedron.crypto.operations.sign.pkcs7.PKCS7Signer.java Source code

Java tutorial

Introduction

Here is the source code for org.dihedron.crypto.operations.sign.pkcs7.PKCS7Signer.java

Source

/**
 * Copyright (c) 2012-2014, Andrea Funto'. All rights reserved. See LICENSE for details.
 */
package org.dihedron.crypto.operations.sign.pkcs7;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateExpiredException;
import java.security.cert.CertificateNotYetValidException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cert.jcajce.JcaX509CertificateHolder;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSSignedDataStreamGenerator;
import org.bouncycastle.cms.SignerInfoGenerator;
import org.bouncycastle.cms.SignerInfoGeneratorBuilder;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.DigestCalculatorProvider;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import org.dihedron.core.License;
import org.dihedron.core.streams.Streams;
import org.dihedron.crypto.KeyRing;
import org.dihedron.crypto.constants.SignatureAlgorithm;
import org.dihedron.crypto.exceptions.CryptoException;
import org.dihedron.crypto.operations.sign.Signer;
import org.dihedron.crypto.providers.AutoCloseableProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author Andrea Funto'
 */
@License
public class PKCS7Signer extends Signer {

    /**
     * The logger.
     */
    private static Logger logger = LoggerFactory.getLogger(PKCS7Signer.class);

    /**
     * The underlying BouncyCastle stream data signer.
     */
    private CMSSignedDataStreamGenerator generator = null;

    /**
     * Constructor.
     * 
     * @param alias
     *   the alias of the certificate to be used for signing.
     * @param keyring
     *   the key ring containing the private key used for signing.
     * @param provider
     *   the security provider backing up the key ring functionalities.
     * @param algorithm
     *   the digest and encryption algorithm combination used to create the 
     *   signature.
     * @throws CryptoException
     *   if any among alias, key ring and provider is null. 
     * @throws KeyStoreException 
     * @throws CertificateEncodingException 
     * @throws CertificateNotYetValidException 
     * @throws CertificateExpiredException 
     */
    public PKCS7Signer(String alias, KeyRing keyring, Provider provider, SignatureAlgorithm algorithm)
            throws CryptoException, KeyStoreException, CertificateEncodingException,
            CertificateNotYetValidException, CertificateExpiredException {
        super(alias, keyring, provider);
        logger.debug("creating PKCS#7 signer with '{}' signature algorithm", algorithm);
        try {
            logger.info("signing with alias '{}'", alias);

            // retrieve key and certificate
            Key key = keyring.getPrivateKey(alias);
            X509Certificate x509certificate = (X509Certificate) keyring.getCertificate(alias);

            // this may throw a CertificateExpiredException or CertificateNotYetValidException
            x509certificate.checkValidity();
            logger.info("certificate is valid at current date");

            // TODO: check CRL

            logger.info("certificate is active at current date (CRL check successful)");

            // prepare the certificates store
            List<Certificate> certificates = new ArrayList<>();
            certificates.add(x509certificate);
            Store store = new JcaCertStore(certificates);

            logger.info("certificate store is ready");

            ContentSigner signer = new JcaContentSignerBuilder(algorithm.toBouncyCastleCode())
                    .setProvider((provider instanceof AutoCloseableProvider)
                            ? ((AutoCloseableProvider) provider).getWrappedProvider()
                            : provider)
                    .build((PrivateKey) key);

            DigestCalculatorProvider digest = new JcaDigestCalculatorProviderBuilder().setProvider("BC").build();

            SignerInfoGenerator signerinfo = new SignerInfoGeneratorBuilder(digest).setDirectSignature(false) // include signed attributes; if true it signs data only
                    .setSignedAttributeGenerator(
                            new PKCS7AttributeTableGenerator(algorithm.getDigestAlgorithm(), x509certificate)) // this generates the attributes that will be signed along with the data
                    .build(signer, new JcaX509CertificateHolder(x509certificate)); // and then we build the generator

            logger.info("signer info generator is ready");

            generator = new CMSSignedDataStreamGenerator();
            generator.addSignerInfoGenerator(signerinfo);
            generator.addCertificates(store);
            //generator.addCRLs(crlStore);

            logger.debug("signed data stream generator for PKCS#7 is ready");

        } catch (OperatorCreationException e) {
            logger.error("error creating operator", e);
            throw new CryptoException("error creating signing operator (BouncyCastle)", e);
        } catch (CertificateEncodingException e) {
            logger.error("invalid certificate encoding", e);
            throw e;
        } catch (CertificateExpiredException e) {
            logger.error("expired certificate", e);
            throw e;
        } catch (CertificateNotYetValidException e) {
            logger.error("certificate is not yet valid (may still need to be activated?)", e);
            throw e;
        } catch (CMSException e) {
            logger.error("error adding certificates to signature generator", e);
            throw new CryptoException("CMS error", e);
        }
    }

    /**
     * Constructor.
     * 
     * @param alias
     *   the alias of the certificate to be used for signing.
     * @param keyring
     *   the key ring containing the private key used for signing.
     * @param provider
     *   the security provider backing up the key ring functionalities.
     * @param digest
     *   the algorithm used to hash the data.
     * @param encryption
     *   the algorithm used to encrypt the hash.
     * @throws CryptoException
     *   if any among alias, key ring and provider is null. 
     * @throws KeyStoreException 
     * @throws CertificateNotYetValidException 
     * @throws CertificateEncodingException 
     * @throws CertificateExpiredException 
     */
    public PKCS7Signer(String alias, KeyRing keyring, Provider provider, String digest, String encryption)
            throws CryptoException, CertificateEncodingException, CertificateNotYetValidException,
            KeyStoreException, CertificateExpiredException {
        this(alias, keyring, provider, SignatureAlgorithm.fromAlgorithmDescriptions(digest, encryption));
    }

    /**
     * Signs the given byte array, returning the signed version.
     * 
     * @param data
     *   the array of bytes to sign.
     * @return 
     *   the signed data, as a byte array.
     */
    @Override
    public byte[] sign(byte[] data) throws CryptoException {
        try (ByteArrayInputStream input = new ByteArrayInputStream(data);
                ByteArrayOutputStream output = new ByteArrayOutputStream()) {
            sign(input, output);
            return output.toByteArray();
        } catch (IOException e) {
            logger.error("error copying data from input stream to signature generator wrapper stream");
            throw new CryptoException("error copying data from input stream to signature generator wrapper stream",
                    e);
        }
    }

    /**
     * Signs whatever can be read from the given input stream, writing the signed 
     * version into the given output stream.
     * 
     * @param input
     *   an input stream from which data to be signed can be read.
     * @param output
     *   an output stream into which signed data will be written.
     */
    @Override
    public void sign(InputStream input, OutputStream output) throws CryptoException {
        try (OutputStream stream = generator.open(output, encapsulate)) {
            logger.trace("copying data into generator filter stream...");
            long copied = Streams.copy(input, stream);
            logger.trace("... done copying {} bytes into generator filter stream", copied);
        } catch (IOException e) {
            logger.error("error opening signature generator wrapper output stream", e);
            throw new CryptoException("error opening signature generator wrapper output stream", e);
        }
    }
}