id.govca.detachedsignature.CMSController.java Source code

Java tutorial

Introduction

Here is the source code for id.govca.detachedsignature.CMSController.java

Source

/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package id.govca.detachedsignature;

import java.io.FileNotFoundException;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.NoSuchProviderException;
import java.security.Principal;
import java.security.PublicKey;
import java.security.Security;
import java.security.Signature;
import java.security.SignatureException;
import java.security.cert.CertStore;
import java.security.cert.Certificate;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.security.cert.PKIXCertPathBuilderResult;
import java.security.cert.X509Certificate;
import java.security.spec.InvalidKeySpecException;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
import org.bouncycastle.asn1.ASN1Primitive;
import org.bouncycastle.asn1.cms.Attribute;
import org.bouncycastle.asn1.cms.AttributeTable;
import org.bouncycastle.asn1.cms.CMSAttributes;
import org.bouncycastle.cert.X509CertificateHolder;
import org.bouncycastle.cert.jcajce.JcaCertStore;
import org.bouncycastle.cert.jcajce.JcaCertStoreBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
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.SignerInformation;
import org.bouncycastle.cms.SignerInformationStore;
import org.bouncycastle.cms.jcajce.JcaSignerInfoGeneratorBuilder;
import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.OperatorCreationException;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.bouncycastle.operator.jcajce.JcaDigestCalculatorProviderBuilder;
import org.bouncycastle.util.Store;
import org.bouncycastle.util.encoders.Hex;

/**
 *
 * @author Rachmawan
 */
public class CMSController {

    private static X509Certificate rootCertCandidate;
    //private static final String root_cert_path = "D:\\Tugas PTIK\\Certificate Authority\\E-voting\\RootCA1.cer"; 
    private String root_cert_path;
    private HashMap<String, String> DN_fields;

    public CMSController() {
    }

    /**
     * @return the root_cert_path
     */
    public String getRoot_cert_path() {
        return root_cert_path;
    }

    /**
     * @param aRoot_cert_path the root_cert_path to set
     */
    public void setRoot_cert_path(String aRoot_cert_path) {
        root_cert_path = aRoot_cert_path;
    }

    /**
    * @return the key-value mapping of Distinguished Name string
    */
    public HashMap<String, String> getDN_fields() {
        return DN_fields;
    }

    /**
     * @param DN_fields the DN_fields to set
     */
    public void setDN_fields(HashMap<String, String> DN_fields) {
        this.DN_fields = DN_fields;
    }

    /**
     * Method to digitally sign a binary content in PKCS7 format.
     * Return the CMSSignedData object of a binary content
     *
     * @param content the binary content to be signed
     * @param pkcc the PrivateKey_CertChain object
     * @return
     */
    public CMSSignedData CMSGenerator(byte[] content, PrivateKey_CertChain pkcc) {
        Security.addProvider(new BouncyCastleProvider());

        try {
            //Sign
            Signature signature = Signature.getInstance("SHA256WithRSA", "BC");
            signature.initSign(pkcc.getPriv_key());
            signature.update(content);
            byte[] signed = signature.sign();
            System.out.format("%-32s%s\n", "Signature of digest of content", Hex.toHexString(signed));

            //Digest of Signature
            MessageDigest digest = MessageDigest.getInstance("SHA-256");
            byte[] hash = digest.digest(signed);
            System.out.format("%-32s%s\n", "Digest of Signature", Hex.toHexString(hash));

            //Build CMS
            X509Certificate cert = pkcc.getSingle_cert();
            List certList = new ArrayList();
            CMSTypedData msg = new CMSProcessableByteArray(signed);

            System.out.format("%-32s%s\n", "Length of Certificate Chain", pkcc.getChain().length);

            certList.addAll(Arrays.asList(pkcc.getChain()));

            Store certs = new JcaCertStore(certList);
            CMSSignedDataGenerator gen = new CMSSignedDataGenerator();
            ContentSigner sha1Signer = new JcaContentSignerBuilder("SHA256withRSA").setProvider("BC")
                    .build(pkcc.getPriv_key());
            gen.addSignerInfoGenerator(new JcaSignerInfoGeneratorBuilder(
                    new JcaDigestCalculatorProviderBuilder().setProvider("BC").build()).build(sha1Signer, cert));
            gen.addCertificates(certs);
            CMSSignedData sigData = gen.generate(msg, true);

            return sigData;

        } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException | SignatureException
                | CertificateEncodingException | OperatorCreationException | CMSException ex) {
            Logger.getLogger(CMSController.class.getName()).log(Level.SEVERE, null, ex);
        }

        return null;
    }

    public boolean VerifyCMS(CMSSignedData signedData, String content_digest) throws IOException, CMSException,
            CertificateException, OperatorCreationException, UnmatchedSignatureException, NoSuchAlgorithmException,
            NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException,
            StringFormatException, ParseException, GeneralSecurityException {
        rootCertCandidate = null;

        Security.addProvider(new BouncyCastleProvider());

        byte[] dataku = (byte[]) signedData.getSignedContent().getContent();
        System.out.format("%-32s%s\n", "Base64 of Signed Content", Hex.toHexString(dataku));

        Store store = signedData.getCertificates();

        CertStore certsAndCRLs = new JcaCertStoreBuilder().setProvider("BC")
                .addCertificates(signedData.getCertificates()).build();

        // Verify signature
        SignerInformationStore signers = signedData.getSignerInfos();
        Collection c = signers.getSigners();
        System.out.format("%-32s%s\n", "Number of Signers", c.size());

        Iterator it = c.iterator();
        while (it.hasNext()) {
            SignerInformation signer = (SignerInformation) it.next();
            AttributeTable att = signer.getSignedAttributes();

            Attribute mdAtt = att.get(CMSAttributes.messageDigest);
            ASN1Primitive asp = mdAtt.getAttrValues().getObjectAt(0).toASN1Primitive();
            byte[] hasil = asp.getEncoded("DER");

            System.out.format("%-32s%s\n", "Digest of Signature", Hex.toHexString(hasil));

            Collection certCollection = store.getMatches(signer.getSID());
            JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");

            ArrayList<X509CertificateHolder> listCertDatFirm = new ArrayList(store.getMatches(null));
            System.out.format("%-32s%d\n", "Number of cert Holders All", listCertDatFirm.size());

            try {
                verifyChain(listCertDatFirm);
            } catch (CertificateVerificationException ex) {
                System.out.println("CERTIFICATE CHAIN VERIFICATION FAILED");
                Logger.getLogger(CMSController.class.getName()).log(Level.SEVERE, null, ex);
                throw new UnmatchedSignatureException("Certificate Chain verification failed");
            }
            System.out.println("CERTIFICATE CHAIN VERIFIED");

            Collection<X509CertificateHolder> holders = store.getMatches(signer.getSID());

            Iterator certIt = certCollection.iterator();
            X509CertificateHolder certHolder = (X509CertificateHolder) certIt.next();
            X509Certificate certFromSignedData = new JcaX509CertificateConverter()
                    .setProvider(new BouncyCastleProvider()).getCertificate(certHolder);

            Principal princ = certFromSignedData.getIssuerDN();

            //Get Signer Name
            Principal p = certFromSignedData.getSubjectDN();
            System.out.format("%-32s%s\n", "Signer Distinguished Name", p.getName());

            this.setDN_fields(StringHelper.DNFieldsMapper(p.getName()));

            //Get Signing Time
            org.bouncycastle.asn1.cms.Attribute signingTime = att
                    .get(new ASN1ObjectIdentifier("1.2.840.113549.1.9.5"));
            String asn1time = signingTime.getAttrValues().toString();
            System.out.format("%-32s%s\n", "Signing Time (RAW format)", asn1time);

            Date signtime = StringHelper.ASN1DateParser(asn1time);
            SimpleDateFormat formatter = new SimpleDateFormat("dd MMM yyyy hh:mm:ss zzz");
            String formattedDate = formatter.format(signtime);
            System.out.format("%-32s%s\n", "Signing Time (Pretty format)", formattedDate);

            PublicKey pubkey = certFromSignedData.getPublicKey();

            if (signer.verify(new JcaSimpleSignerInfoVerifierBuilder().setProvider(new BouncyCastleProvider())
                    .build(certFromSignedData))) {
                System.out.println("SIGNATURE VERIFIED <BY BOUNCY CASTLE STANDARD>");
            } else {
                System.out.println("SIGNATURE VERIFICATION <BY BOUNCY CASTLE STANDARD> FAILED");
                throw new UnmatchedSignatureException(
                        "Signature verification failed, probably the signature (CMS) has been altered!");
            }

            Cipher RSADecrypter;

            RSADecrypter = Cipher.getInstance("RSA/ECB/PKCS1Padding", "BC");

            //Initialize the Cipher using our the first key in the keystore  works fine for both
            RSADecrypter.init(Cipher.DECRYPT_MODE, pubkey);
            byte[] try_decrypt = RSADecrypter.doFinal(dataku);

            String decrypt_result = Hex.toHexString(try_decrypt);
            //Because there is magic number for hash algorithm at the beginning of the string,
            //we only need the last 64 characters from the decryption result
            String sanitized_decrypt_result = decrypt_result.substring(decrypt_result.length() - 64);

            System.out.format("%-32s%s\n", "Decryption Result", decrypt_result);
            System.out.format("%-32s%s\n", "Sanitized Decryption Result", sanitized_decrypt_result);

            if (!content_digest.equals(sanitized_decrypt_result)) {
                System.out.println("CONTENT DIGEST VERIFICATION FAILED");
                throw new UnmatchedSignatureException(
                        "Content digest verification failed, probably the content has been altered!");
            }
            System.out.println("CONTENT DIGEST VERIFIED");

            try {
                RootCertChecker rc = new RootCertChecker();

                rc.checkCertificate(rootCertCandidate, getRoot_cert_path());
            } catch (FileNotFoundException | InvalidKeyException | NoSuchAlgorithmException
                    | NoSuchProviderException | SignatureException | CertificateException ex) {
                System.out.println("ROOT CERT VERIFICATION FAILED");
                throw new UnmatchedSignatureException("The System does not recognized this root Certificate");
            }
            System.out.println("ROOT CERTIFICATE VERIFIED");

        }

        return true;
    }

    private static PKIXCertPathBuilderResult verifyChain(ArrayList<X509CertificateHolder> cert_chain)
            throws CertificateException, CertificateVerificationException {
        JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider("BC");

        X509Certificate target_cert = null;
        Set<X509Certificate> additional_cert = new HashSet<>();
        for (int i = 0; i < cert_chain.size(); i++) {
            X509Certificate cert_loop = converter.getCertificate(cert_chain.get(i));

            if (i == 0) {
                target_cert = cert_loop;
            } else {
                additional_cert.add(cert_loop);
                try {
                    if (ChainVerifier.isSelfSigned(cert_loop)) {
                        rootCertCandidate = cert_loop;
                    }
                } catch (NoSuchAlgorithmException | NoSuchProviderException | InvalidKeyException
                        | SignatureException ex) {
                    Logger.getLogger(CMSController.class.getName()).log(Level.SEVERE, null, ex);
                }
            }

        }

        PKIXCertPathBuilderResult my_res = ChainVerifier.verifyCertificate(target_cert, additional_cert);

        return my_res;
    }

}