org.roda.common.certification.SignatureUtility.java Source code

Java tutorial

Introduction

Here is the source code for org.roda.common.certification.SignatureUtility.java

Source

/**
 * The contents of this file are subject to the license and copyright
 * detailed in the LICENSE file at the root of the source
 * tree and available online at
 *
 * https://github.com/keeps/roda
 */
package org.roda.common.certification;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertStoreException;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;

import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cms.CMSException;
import org.bouncycastle.cms.CMSProcessableFile;
import org.bouncycastle.cms.CMSSignedData;
import org.bouncycastle.cms.CMSSignedDataGenerator;
import org.bouncycastle.cms.DefaultCMSSignatureAlgorithmNameGenerator;
import org.bouncycastle.cms.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.SignerInformationVerifier;
import org.bouncycastle.cms.bc.BcRSASignerInfoVerifierBuilder;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.DefaultDigestAlgorithmIdentifierFinder;
import org.bouncycastle.operator.DefaultSignatureAlgorithmIdentifierFinder;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.bc.BcDigestCalculatorProvider;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;

/**
 * Utility class to help create and validate digital signatures
 * 
 * @author Luis Faria
 * 
 */
public class SignatureUtility {

    private static final String SIGN_ALGORITHM = "SHA256WITHRSA";
    private static final String SIGN_PROVIDER = BouncyCastleProvider.PROVIDER_NAME;

    private final KeyStore ks;
    private final CMSSignedDataGenerator signGenerator;

    /**
     * Create a new signature utility
     * 
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     */
    public SignatureUtility() throws KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException {
        this(SIGN_PROVIDER);
    }

    protected SignatureUtility(String provider)
            throws KeyStoreException, NoSuchAlgorithmException, NoSuchProviderException {
        this.ks = KeyStore.getInstance(KeyStore.getDefaultType());
        Security.addProvider(new BouncyCastleProvider());
        this.signGenerator = new CMSSignedDataGenerator();

    }

    /**
     * Load a new keystore
     * 
     * @param keystore
     *          input stream to the keystore
     * @param password
     *          keystore password
     * @throws NoSuchAlgorithmException
     * @throws CertificateException
     * @throws IOException
     */
    public void loadKeyStore(InputStream keystore, char[] password)
            throws NoSuchAlgorithmException, CertificateException, IOException {
        ks.load(keystore, password);
    }

    /**
     * Initialize parameters for signing
     * 
     * @param alias
     *          private key alias
     * 
     * @param password
     *          the keystore password
     * @throws KeyStoreException
     * @throws NoSuchAlgorithmException
     * @throws UnrecoverableKeyException
     * @throws OperatorCreationException
     * @throws CertificateEncodingException
     * @throws CMSException
     */
    public void initSign(String alias, char[] password) throws UnrecoverableKeyException, KeyStoreException,
            NoSuchAlgorithmException, CertificateEncodingException, OperatorCreationException, CMSException {
        PrivateKey pk = (PrivateKey) ks.getKey(alias, password);
        Certificate[] certificateChain = ks.getCertificateChain(alias);
        if (certificateChain != null) {
            X509Certificate certificate = (X509Certificate) certificateChain[0];
            List<Certificate> certList = new ArrayList<Certificate>(Arrays.asList(certificateChain));

            signGenerator.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
                    new JcaDigestCalculatorProviderBuilder().setProvider(SIGN_PROVIDER).build()).build(
                            new JcaContentSignerBuilder(SIGN_ALGORITHM).setProvider(SIGN_PROVIDER).build(pk),
                            certificate));

            JcaCertStore certs = new JcaCertStore(certList);
            signGenerator.addCertificates(certs);

        } else {
            System.err.println("Certificate chain for " + alias + " not found");
        }
    }

    /**
     * Sign the file
     * 
     * @param file
     * 
     * @return an array of bytes with the signature
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CMSException
     */
    public byte[] sign(File file)
            throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CMSException {
        CMSProcessableFile cmsFile = new CMSProcessableFile(file);
        CMSSignedData data = signGenerator.generate(cmsFile);
        return data.getEncoded();
    }

    /**
     * Sign the file
     * 
     * @param file
     *          the file to sign
     * @param signature
     *          the file where to save the signature. If the file exists it will
     *          be overridden
     * @throws IOException
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CMSException
     */
    public void sign(File file, File signature)
            throws IOException, NoSuchAlgorithmException, NoSuchProviderException, CMSException {
        if (signature.exists()) {
            signature.delete();
        }
        signature.createNewFile();
        FileOutputStream out = new FileOutputStream(signature);
        byte[] signatureArray = sign(file);
        out.write(signatureArray);
        out.close();

    }

    /**
     * Verify detached signature
     * 
     * @param file
     * @param signature
     * @return true if valid
     * @throws NoSuchAlgorithmException
     * @throws NoSuchProviderException
     * @throws CertStoreException
     * @throws CMSException
     * @throws FileNotFoundException
     * @throws IOException
     * @throws CertificateException
     * @throws OperatorCreationException
     */
    public boolean verify(File file, File signature)
            throws NoSuchAlgorithmException, NoSuchProviderException, CertStoreException, CMSException,
            FileNotFoundException, IOException, CertificateException, OperatorCreationException {
        CMSProcessableFile cmsFile = new CMSProcessableFile(file);
        CMSSignedData signedData = new CMSSignedData(cmsFile, new FileInputStream(signature));

        return verifySignatures(signedData, null);
    }

    @SuppressWarnings("unchecked")
    private boolean verifySignatures(CMSSignedData s, byte[] contentDigest)
            throws NoSuchAlgorithmException, NoSuchProviderException, CMSException, CertStoreException,
            CertificateException, OperatorCreationException {
        boolean valid = true;

        // CertStore certStore = s.getCertificatesAndCRLs("Collection", provider);
        Store<?> certStore = s.getCertificates();
        SignerInformationStore signers = s.getSignerInfos();

        Collection<SignerInformation> c = signers.getSigners();
        Iterator<SignerInformation> it = c.iterator();

        while (it.hasNext()) {
            SignerInformation signer = it.next();
            Collection<?> certCollection = certStore.getMatches(signer.getSID());

            Iterator<?> certIt = certCollection.iterator();
            X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();

            SignerInformationVerifier signerVerifierInformation = new BcRSASignerInfoVerifierBuilder(
                    new DefaultCMSSignatureAlgorithmNameGenerator(),
                    new DefaultSignatureAlgorithmIdentifierFinder(), new DefaultDigestAlgorithmIdentifierFinder(),
                    new BcDigestCalculatorProvider()).build(certHolder);
            boolean certValid = signer.verify(signerVerifierInformation);

            valid &= certValid;

            if (!certValid) {
                System.err.println("Invalid certificate " + certHolder);
            }

            if (contentDigest != null) {
                boolean digestValid = MessageDigest.isEqual(contentDigest, signer.getContentDigest());

                valid &= digestValid;

                if (!digestValid) {
                    System.err.println("Invalid digest " + contentDigest);
                }
            }

        }

        return valid;

    }

}