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