//$Header: /cvsroot-fuse/mec-as2/39/mendelson/util/security/BCCryptoHelper.java,v 1.1 2012/04/18 14:10:45 heller Exp $ package de.mendelson.util.security; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.security.DigestInputStream; import java.security.GeneralSecurityException; import java.security.Key; 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.SignatureException; import java.security.cert.CertStore; import java.security.cert.Certificate; import java.security.cert.CollectionCertStoreParameters; import java.security.cert.X509Certificate; import java.util.Arrays; import java.util.Collection; import java.util.Iterator; import java.util.List; import javax.activation.CommandMap; import javax.activation.MailcapCommandMap; import javax.crypto.Cipher; import javax.crypto.spec.SecretKeySpec; import javax.mail.BodyPart; import javax.mail.MessagingException; import javax.mail.Multipart; import javax.mail.Part; import javax.mail.Session; import javax.mail.internet.ContentType; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMultipart; import javax.mail.util.ByteArrayDataSource; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.cms.AttributeTable; import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute; import org.bouncycastle.asn1.smime.SMIMECapability; import org.bouncycastle.asn1.smime.SMIMECapabilityVector; import org.bouncycastle.cert.jcajce.JcaCertStore; import org.bouncycastle.cms.*; import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder; import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.mail.smime.SMIMEEnveloped; import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator; import org.bouncycastle.mail.smime.SMIMEException; import org.bouncycastle.mail.smime.SMIMESigned; import org.bouncycastle.mail.smime.SMIMESignedGenerator; import org.bouncycastle.mail.smime.SMIMEUtil; import org.bouncycastle.util.Store; import org.bouncycastle.util.encoders.Base64; /* * Copyright (C) mendelson-e-commerce GmbH Berlin Germany * * This software is subject to the license agreement set forth in the license. * Please read and agree to all terms before using this software. * Other product and brand names are trademarks of their respective owners. */ /** * Utility class to handle bouncycastle cryptography * @author S.Heller * @version $Revision: 1.1 $ */ public class BCCryptoHelper { public static final String ALGORITHM_3DES = "3des"; public static final String ALGORITHM_DES = "des"; public static final String ALGORITHM_RC2 = "rc2"; public static final String ALGORITHM_RC4 = "rc4"; public static final String ALGORITHM_AES_128 = "aes128"; public static final String ALGORITHM_AES_192 = "aes192"; public static final String ALGORITHM_AES_256 = "aes256"; public static final String ALGORITHM_MD5 = "md5"; public static final String ALGORITHM_SHA1 = "sha1"; public static final String ALGORITHM_IDEA = "idea"; public static final String ALGORITHM_CAST5 = "cast5"; public static final String KEYSTORE_PKCS12 = "PKCS12"; public static final String KEYSTORE_JKS = "JKS"; public BCCryptoHelper() { } public boolean isEncrypted(MimeBodyPart part) throws MessagingException { if (part == null) { throw new MessagingException("Part is null"); } ContentType contentType = new ContentType(part.getContentType()); String baseType = contentType.getBaseType().toLowerCase(); if (baseType.equalsIgnoreCase("application/pkcs7-mime")) { String smimeType = contentType.getParameter("smime-type"); return smimeType != null && smimeType.equalsIgnoreCase("enveloped-data"); } else { return false; } } /**Performs an encryption with a 192 bit key, this will fail if the unlimited strength policy files * have not been installed for the VM * This method will throw an exception if there is a general security problem, e.g. the provider is not found * @return true if the VM is patched, false if the VM is not pached */ public boolean performUnlimitedStrengthJurisdictionPolicyTest() { byte[] data = {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07}; SecretKeySpec key192 = new SecretKeySpec( new byte[]{0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17 }, "Blowfish"); try { Cipher cipher = Cipher.getInstance("Blowfish/ECB/NoPadding"); cipher.init(Cipher.ENCRYPT_MODE, key192); cipher.doFinal(data); } catch (Exception e) { return (false); } return (true); } /**Returns the signed part of this container if it exists, else null. If the container itself *is signed it is returned. You could use this method if you are not sure if the main container *of a message is the signed part or if there are some unused MIME wrappers around it *that embedd the signed part */ public Part getSignedEmbeddedPart(Part part) throws MessagingException, IOException { if (part == null) { throw new MessagingException("Part is null"); } if (part.isMimeType("multipart/signed")) { return (part); } if (part.isMimeType("multipart/*")) { Multipart multiPart = (Multipart) part.getContent(); int count = multiPart.getCount(); for (int i = 0; i < count; i++) { BodyPart bodyPart = multiPart.getBodyPart(i); Part signedEmbeddedPart = this.getSignedEmbeddedPart(bodyPart); if (signedEmbeddedPart != null) { return (signedEmbeddedPart); } } } return (null); } /**Checks if two mics in the format <base64>, <digest> are equal * * @param mic1 * @param mic2 * @return */ public boolean micIsEqual(String mic1, String mic2) { try { mic1 = mic1.trim(); mic2 = mic2.trim(); if (mic1.equals(mic2)) { return (true); } //parse the mics int index1 = mic1.lastIndexOf(','); int index2 = mic2.lastIndexOf(','); String digest1 = mic1.substring(index1 + 1).trim(); String digest2 = mic2.substring(index2 + 1).trim(); String oid1 = this.convertAlgorithmNameToOID(digest1); String oid2 = this.convertAlgorithmNameToOID(digest2); String hashbase641 = mic1.substring(0, index1); String hashbase642 = mic2.substring(0, index2); byte[] bytes1 = Base64.decode(hashbase641); byte[] bytes2 = Base64.decode(hashbase642); DigestInputStream inStream1 = new DigestInputStream(new ByteArrayInputStream(bytes1), MessageDigest.getInstance(oid1, "BC")); DigestInputStream inStream2 = new DigestInputStream(new ByteArrayInputStream(bytes2), MessageDigest.getInstance(oid2, "BC")); ByteArrayOutputStream hashValueStream1 = new ByteArrayOutputStream(); ByteArrayOutputStream hashValueStream2 = new ByteArrayOutputStream(); this.copyStreams(inStream1, hashValueStream1); this.copyStreams(inStream2, hashValueStream2); inStream1.close(); inStream2.close(); hashValueStream1.close(); hashValueStream2.close(); byte[] bytesHashValue1 = hashValueStream1.toByteArray(); byte[] bytesHashValue2 = hashValueStream2.toByteArray(); if (bytesHashValue1.length != bytesHashValue2.length) { return (false); } for (int i = 0; i < bytesHashValue1.length; i++) { if (bytesHashValue1[i] != bytesHashValue2[i]) { return (false); } } return (true); } catch (Exception e) { e.printStackTrace(); return (false); } } /**Copies all data from one stream to another*/ private void copyStreams(InputStream in, OutputStream out) throws IOException { BufferedInputStream inStream = new BufferedInputStream(in); BufferedOutputStream outStream = new BufferedOutputStream(out); //copy the contents to an output stream byte[] buffer = new byte[2048]; int read = 2048; //a read of 0 must be allowed, sometimes it takes time to //extract data from the input while (read != -1) { read = inStream.read(buffer); if (read > 0) { outStream.write(buffer, 0, read); } } outStream.flush(); } /**Displays a bundle of byte arrays as hex string, for debug purpose only*/ private String toHexDisplay(byte[] data) { StringBuilder result = new StringBuilder(); for (int i = 0; i < data.length; i++) { result.append(Integer.toString((data[i] & 0xff) + 0x100, 16).substring(1)); result.append(" "); } return result.toString(); } /**Calculates the hash value for a passed byte array, base 64 encoded *@param digestAlgOID digest OID algorithm, e.g. "1.3.14.3.2.26" */ public String calculateMIC(byte[] data, String digestAlgOID) throws GeneralSecurityException, MessagingException, IOException { if (data == null) { throw new GeneralSecurityException("calculateMIC: Data is null"); } MessageDigest messageDigest = MessageDigest.getInstance(digestAlgOID, "BC"); DigestInputStream digestInputStream = new DigestInputStream(new ByteArrayInputStream(data), messageDigest); for (byte buf[] = new byte[4096]; digestInputStream.read(buf) >= 0;) { } byte mic[] = digestInputStream.getMessageDigest().digest(); digestInputStream.close(); String micString = new String(Base64.encode(mic)); return (micString); } /**Calculates the hash value for a passed body part, base 64 encoded *@param digestAlgOID digest OID algorithm, e.g. "1.3.14.3.2.26" */ public String calculateMIC(Part part, String digestAlgOID) throws GeneralSecurityException, MessagingException, IOException { if (part == null) { throw new GeneralSecurityException("calculateMIC: Part is null"); } ByteArrayOutputStream bOut = new ByteArrayOutputStream(); part.writeTo(bOut); bOut.flush(); bOut.close(); byte data[] = bOut.toByteArray(); return (this.calculateMIC(data, digestAlgOID)); } /**Used for mendelson rosettaNet*/ public MimeBodyPart decrypt(MimeBodyPart part, Certificate cert, Key key) throws GeneralSecurityException, MessagingException, CMSException, IOException, SMIMEException { if (!this.isEncrypted(part)) { throw new GeneralSecurityException("decrypt: Content-Type indicates data isn't encrypted"); } X509Certificate x509Cert = castCertificate(cert); SMIMEEnveloped envelope = new SMIMEEnveloped(part); RecipientId recipientId = new JceKeyTransRecipientId(x509Cert); RecipientInformation recipient = envelope.getRecipientInfos().get(recipientId); if (recipient == null) { throw new GeneralSecurityException("decrypt: Wrong key used to decrypt the data."); } else { MimeBodyPart bodyPart = SMIMEUtil.toMimeBodyPart( recipient.getContentStream(new JceKeyTransEnvelopedRecipient(this.getPrivateKey(key)).setProvider("BC"))); return (bodyPart); } } public void deinitialize() { } /**@param algorith a algorith alias name, e.g. "3des", wil be translated into the *right IOD number internal */ public MimeBodyPart encrypt(MimeMessage part, Certificate cert, String algorithm) throws GeneralSecurityException, SMIMEException { X509Certificate x509Cert = castCertificate(cert); String encAlg = this.convertAlgorithmNameToOID(algorithm); SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator(); generator.addKeyTransRecipient(x509Cert); if (part == null) { throw new GeneralSecurityException("encrypt: Part is null"); } MimeBodyPart encData = generator.generate(part, encAlg, "BC"); return encData; } /**@param algorith a algorith alias name, e.g. "3des", will be translated into the *right IOD number internal */ public MimeBodyPart encrypt(MimeBodyPart part, Certificate cert, String algorithm) throws GeneralSecurityException, SMIMEException { X509Certificate x509Cert = castCertificate(cert); String encAlg = this.convertAlgorithmNameToOID(algorithm); SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addKeyTransRecipient(x509Cert); if (part == null) { throw new GeneralSecurityException("encrypt: Part is null"); } MimeBodyPart encData = gen.generate(part, encAlg, "BC"); return encData; } public void initialize() { Security.addProvider(new BouncyCastleProvider()); MailcapCommandMap mc = (MailcapCommandMap) CommandMap.getDefaultCommandMap(); mc.addMailcap("application/pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature"); mc.addMailcap("application/pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime"); mc.addMailcap("application/x-pkcs7-signature;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature"); mc.addMailcap("application/x-pkcs7-mime;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime"); mc.addMailcap("multipart/signed;; x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed"); CommandMap.setDefaultCommandMap(mc); //As of JavaMail 1.4.1 and later caching was introduced for Multipart objects, //this can cause some issues for signature verification as occasionally the cache does not produce exactly //the same message as was read in. System.setProperty("mail.mime.cachemultipart", "false"); } /**Create a pkcs7-signature of the passed content and returns it *@param chain certificate chain, chain[0] is the signers certificate itself * @param embeddOriginalData Indicates if the original data should be embedded in the signature * */ public byte[] sign(byte[] content, Certificate[] chain, Key key, String digest, boolean embeddOriginalData) throws Exception { X509Certificate x509Cert = this.castCertificate(chain[0]); PrivateKey privKey = this.getPrivateKey(key); CMSSignedDataGenerator generator = new CMSSignedDataGenerator(); //add dont know ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); caps.addCapability(SMIMECapability.dES_CBC); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); if (digest.equalsIgnoreCase(ALGORITHM_SHA1)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("SHA1withRSA", privKey, x509Cert)); } else if (digest.equalsIgnoreCase(ALGORITHM_MD5)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("MD5withRSA", privKey, x509Cert)); } else { throw new Exception("sign: Signing digest " + digest + " not supported."); } //add cert store List<Certificate> certList = Arrays.asList(chain); CertStore certStore = CertStore.getInstance("Collection", new CollectionCertStoreParameters(certList), "BC"); generator.addCertificatesAndCRLs(certStore); if (content == null) { throw new Exception("sign: content is null"); } CMSTypedData processable = new CMSProcessableByteArray(content); CMSSignedData signatureData = generator.generate(processable, embeddOriginalData); return (signatureData.getEncoded()); } /**Create a pkcs7-signature of the passed content and returns it, without embedding the * original data in the signature *@param chain certificate chain, chain[0] is the signers certificate itself * */ public byte[] sign(byte[] content, Certificate[] chain, Key key, String digest) throws Exception { return( this.sign(content, chain, key, digest, false)); } /**@param chain certificate chain, chain[0] is the signers certificate itself */ public MimeMultipart sign(MimeBodyPart body, Certificate[] chain, Key key, String digest) throws Exception { X509Certificate x509Cert = this.castCertificate(chain[0]); PrivateKey privKey = this.getPrivateKey(key); SMIMESignedGenerator generator = new SMIMESignedGenerator(); //add dont know ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); caps.addCapability(SMIMECapability.dES_CBC); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); if (digest.equalsIgnoreCase(ALGORITHM_SHA1)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("SHA1withRSA", privKey, x509Cert)); } else if (digest.equalsIgnoreCase(ALGORITHM_MD5)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("MD5withRSA", privKey, x509Cert)); } else { throw new Exception("sign: Signing digest " + digest + " not supported."); } //add cert store List<Certificate> certList = Arrays.asList(chain); Store certStore = new JcaCertStore(certList); generator.addCertificates(certStore); MimeMultipart signedPart = generator.generate(body, "BC"); return (signedPart); } /**@param chain certificate chain, chain[0] is the signers certificate itself */ public MimeMultipart sign(MimeMessage message, Certificate[] chain, Key key, String digest) throws Exception { if (message == null) { throw new Exception("sign: message is null"); } X509Certificate x509Cert = this.castCertificate(chain[0]); PrivateKey privKey = this.getPrivateKey(key); SMIMESignedGenerator generator = new SMIMESignedGenerator(); //add dont know ASN1EncodableVector signedAttrs = new ASN1EncodableVector(); SMIMECapabilityVector caps = new SMIMECapabilityVector(); caps.addCapability(SMIMECapability.dES_EDE3_CBC); caps.addCapability(SMIMECapability.rC2_CBC, 128); caps.addCapability(SMIMECapability.dES_CBC); signedAttrs.add(new SMIMECapabilitiesAttribute(caps)); if (digest.equalsIgnoreCase(ALGORITHM_SHA1)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("SHA1withRSA", privKey, x509Cert)); } else if (digest.equalsIgnoreCase(ALGORITHM_MD5)) { generator.addSignerInfoGenerator( new JcaSimpleSignerInfoGeneratorBuilder().setProvider("BC").setSignedAttributeGenerator( new AttributeTable(signedAttrs)).build("MD5withRSA", privKey, x509Cert)); } else { throw new Exception("sign: Signing digest " + digest + " not supported."); } //add cert store List<Certificate> certList = Arrays.asList(chain); Store certStore = new JcaCertStore(certList); generator.addCertificates(certStore); MimeMultipart multipart = generator.generate(message, "BC"); return (multipart); } /**@param chain certificate chain, chain[0] is the signers certificate itself */ public MimeMessage signToMessage(MimeMessage message, Certificate[] chain, Key key, String digest) throws Exception { MimeMultipart multipart = this.sign(message, chain, key, digest); MimeMessage signedMessage = new MimeMessage(Session.getInstance(System.getProperties(), null)); signedMessage.setContent(multipart, multipart.getContentType()); signedMessage.saveChanges(); return (signedMessage); } /**Returns the digest OID algorithm from a signature that signes the passed message part *The return value for sha1 is e.g. "1.3.14.3.2.26". */ public String getDigestAlgOIDFromSignature(Part part) throws Exception { if (part == null) { throw new GeneralSecurityException("getDigestAlgOIDFromSignature: Part is null"); } if (part.isMimeType("multipart/signed")) { MimeMultipart signedMultiPart = null; if (part.getContent() instanceof MimeMultipart) { signedMultiPart = (MimeMultipart) part.getContent(); } else { //assuming it is an inputstream now signedMultiPart = new MimeMultipart(new ByteArrayDataSource((InputStream) part.getContent(), part.getContentType())); } SMIMESigned signed = new SMIMESigned(signedMultiPart); SignerInformationStore signerStore = signed.getSignerInfos(); Iterator iterator = signerStore.getSigners().iterator(); while (iterator.hasNext()) { SignerInformation signerInfo = (SignerInformation) iterator.next(); return (signerInfo.getDigestAlgOID()); } throw new GeneralSecurityException("getDigestAlgOIDFromSignature: Unable to identify signature algorithm."); } throw new GeneralSecurityException("Content-Type indicates data isn't signed"); } /**Returns the digest OID algorithm from a pkcs7 signature *The return value for sha1 is e.g. "1.3.14.3.2.26". */ public String getDigestAlgOIDFromSignature(byte[] signature) throws Exception { if (signature == null) { throw new GeneralSecurityException("getDigestAlgOIDFromSignature: signature is null"); } CMSSignedData signedData = new CMSSignedData(signature); SignerInformationStore signers = signedData.getSignerInfos(); Collection signerCollection = signers.getSigners(); Iterator iterator = signerCollection.iterator(); while (iterator.hasNext()) { SignerInformation signerInfo = (SignerInformation) iterator.next(); return (signerInfo.getDigestAlgOID()); } throw new GeneralSecurityException("getDigestAlgOIDFromSignature: Unable to identify signature algorithm."); } /**Verifies a signature of a passed content against the passed certificate */ public boolean verify(byte[] content, byte[] signature, Certificate cert) throws Exception { if (content == null) { throw new GeneralSecurityException("verify: content is null"); } if (signature == null) { throw new GeneralSecurityException("verify: signature is null"); } if (signature.length == 0) { throw new Exception("verify: signature length is 0"); } X509Certificate x509 = this.castCertificate(cert); CMSTypedStream signedContent = new CMSTypedStream(new ByteArrayInputStream(content)); CMSSignedDataParser dataParser = new CMSSignedDataParser( signedContent, new ByteArrayInputStream(signature)); dataParser.getSignedContent().drain(); SignerInformationStore signers = dataParser.getSignerInfos(); Collection signerCollection = signers.getSigners(); Iterator it = signerCollection.iterator(); boolean verified = false; while (it.hasNext()) { SignerInformation signer = (SignerInformation) it.next(); if (!verified) { verified = signer.verify(x509, "BC"); } } return (verified); } /**Verifies a signature against the passed certificate *@param encoding one of 7bit quoted-printable base64 8bit binary */ public MimeBodyPart verify(Part part, Certificate cert) throws Exception { return (this.verify(part, null, cert)); } /**Verifies a signature against the passed certificate *@param contentTransferEncoding one of 7bit quoted-printable base64 8bit binary */ public MimeBodyPart verify(Part part, String contentTransferEncoding, Certificate cert) throws Exception { if (part == null) { throw new GeneralSecurityException("verify: Mime part is null"); } if (part.isMimeType("multipart/signed")) { MimeMultipart signedMultiPart = (MimeMultipart) part.getContent(); //possible encoding: 7bit quoted-printable base64 8bit binary SMIMESigned signed = null; if (contentTransferEncoding == null) { //the default encoding in BC is 7bit but the default content transfer encoding in AS2 is binary. signed = new SMIMESigned(signedMultiPart, "binary"); } else { signed = new SMIMESigned(signedMultiPart, contentTransferEncoding); } X509Certificate x509Cert = this.castCertificate(cert); SignerInformationStore signerStore = signed.getSignerInfos(); Iterator<SignerInformation> iterator = signerStore.getSigners().iterator(); while (iterator.hasNext()) { SignerInformation signerInfo = iterator.next(); if (!signerInfo.verify(x509Cert.getPublicKey(), "BC")) { StringBuilder signatureCertInfo = new StringBuilder(); //try to gain more information about the problem if (signerInfo.getSID() != null) { if (signerInfo.getSID().getSerialNumber() != null) { signatureCertInfo.append("Serial number (dec): "); signatureCertInfo.append(signerInfo.getSID().getSerialNumber()); } if (signerInfo.getSID().getIssuerAsString() != null) { if (signatureCertInfo.length() > 0) { signatureCertInfo.append("\n"); } signatureCertInfo.append("Issuer: "); signatureCertInfo.append(signerInfo.getSID().getIssuerAsString()); } } if (signatureCertInfo.length() > 0) { signatureCertInfo.insert(0, "Signature certificate information:\n"); } StringBuilder checkCertInfo = new StringBuilder(); checkCertInfo.append("Verification certificate information:\n"); checkCertInfo.append("Serial number (dec): "); checkCertInfo.append(x509Cert.getSerialNumber()); checkCertInfo.append("\n"); checkCertInfo.append("Issuer: "); checkCertInfo.append(x509Cert.getIssuerX500Principal().toString()); StringBuilder message = new StringBuilder("Verification failed"); if (signatureCertInfo != null) { message.append("\n\n"); message.append(signatureCertInfo); message.append("\n\n"); message.append(checkCertInfo); } throw new SignatureException(message.toString()); } } return signed.getContent(); } else { throw new GeneralSecurityException("Content-Type indicates data isn't signed"); } } private X509Certificate castCertificate(Certificate cert) throws GeneralSecurityException { if (cert == null) { throw new GeneralSecurityException("castCertificate: Certificate is null"); } if (!(cert instanceof X509Certificate)) { throw new GeneralSecurityException("castCertificate: Certificate must be an instance of X509Certificate"); } else { return (X509Certificate) cert; } } private PrivateKey getPrivateKey(Key key) throws GeneralSecurityException { if (key == null) { throw new GeneralSecurityException("getPrivateKey: Key is null"); } if (!(key instanceof PrivateKey)) { throw new GeneralSecurityException("getPrivateKey: Key must implement PrivateKey interface"); } else { return (PrivateKey) key; } } /**Converts the passed algorithm or OID*/ public String convertAlgorithmNameToOID(String algorithm) throws NoSuchAlgorithmException { if (algorithm == null) { throw new NoSuchAlgorithmException("convertAlgorithmNameToOID: Algorithm is null"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_MD5)) { return ("1.2.840.113549.2.5"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_SHA1)) { return ("1.3.14.3.2.26"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_3DES)) { return ("1.2.840.113549.3.7"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_DES)) { return ("1.3.14.3.2.7"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_CAST5)) { return (CMSEnvelopedDataGenerator.CAST5_CBC); } else if (algorithm.equalsIgnoreCase(ALGORITHM_IDEA)) { return (CMSEnvelopedDataGenerator.IDEA_CBC); } else if (algorithm.equalsIgnoreCase(ALGORITHM_RC2)) { return (CMSEnvelopedDataGenerator.RC2_CBC); } else if (algorithm.equalsIgnoreCase(ALGORITHM_RC4)) { return ("1.2.840.113549.3.4"); } else if (algorithm.equalsIgnoreCase(ALGORITHM_AES_128)) { return (CMSEnvelopedDataGenerator.AES128_CBC); } else if (algorithm.equalsIgnoreCase(ALGORITHM_AES_192)) { return (CMSEnvelopedDataGenerator.AES192_CBC); } else if (algorithm.equalsIgnoreCase(ALGORITHM_AES_256)) { return (CMSEnvelopedDataGenerator.AES256_CBC); } else { throw new NoSuchAlgorithmException("Unsupported algorithm: " + algorithm); } } /**Converts the passed algorithm or OID*/ public String convertOIDToAlgorithmName(String oid) throws NoSuchAlgorithmException { if (oid == null) { throw new NoSuchAlgorithmException("convertOIDToAlgorithmName: OID is null"); } else if (oid.equalsIgnoreCase("1.2.840.113549.2.5")) { return (ALGORITHM_MD5); } else if (oid.equalsIgnoreCase("1.3.14.3.2.26")) { return (ALGORITHM_SHA1); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.CAST5_CBC)) { return (ALGORITHM_CAST5); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.DES_EDE3_CBC)) { return (ALGORITHM_3DES); } else if (oid.equalsIgnoreCase("1.3.14.3.2.7")) { return (ALGORITHM_DES); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.IDEA_CBC)) { return (ALGORITHM_IDEA); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.RC2_CBC)) { return (ALGORITHM_RC2); } else if (oid.equalsIgnoreCase("1.2.840.113549.3.4")) { return (ALGORITHM_RC4); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.AES128_CBC)) { return (ALGORITHM_AES_128); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.AES192_CBC)) { return (ALGORITHM_AES_192); } else if (oid.equalsIgnoreCase(CMSEnvelopedDataGenerator.AES256_CBC)) { return (ALGORITHM_AES_256); } else { throw new NoSuchAlgorithmException("Unsupported algorithm: OID " + oid); } } /** * * @param type Keystore type which should be one of the class constants * @return * @throws java.security.KeyStoreException * @throws java.security.NoSuchProviderException */ public KeyStore createKeyStoreInstance(String type) throws KeyStoreException, NoSuchProviderException { if (type.equals(KEYSTORE_PKCS12)) { return KeyStore.getInstance(type, "BC"); } else { return KeyStore.getInstance(type); } } /**returns a CMS encrypted byte array*/ public byte[] encryptCMS(byte[] data, final String ALGORITHM_NAME, Certificate cert) throws Exception { ByteArrayInputStream dataMem = new ByteArrayInputStream(data); ByteArrayOutputStream encryptedMem = new ByteArrayOutputStream(); this.encryptCMS(dataMem, encryptedMem, ALGORITHM_NAME, cert, true); dataMem.close(); encryptedMem.close(); return (encryptedMem.toByteArray()); } /**Encrypts data to a stream*/ public void encryptCMS(InputStream rawStream, OutputStream encryptedStream, final String ALGORITHM_NAME, Certificate cert, boolean inMemory) throws Exception { X509Certificate x509Cert = this.castCertificate(cert); CMSEnvelopedDataStreamGenerator generator = new CMSEnvelopedDataStreamGenerator(); generator.addKeyTransRecipient(x509Cert); String oid = this.convertAlgorithmNameToOID(ALGORITHM_NAME); if (inMemory) { ByteArrayOutputStream memBuffer = new ByteArrayOutputStream(); OutputStream cmsEnveloped = generator.open(memBuffer, oid, "BC"); this.copyStreams(rawStream, cmsEnveloped); cmsEnveloped.flush(); cmsEnveloped.close(); encryptedStream.write(memBuffer.toByteArray()); } else { File tempFile = File.createTempFile("encrypt", ".temp"); FileOutputStream fileBuffer = new FileOutputStream(tempFile); OutputStream cmsEnveloped = generator.open(fileBuffer, oid, "BC"); this.copyStreams(rawStream, cmsEnveloped); cmsEnveloped.flush(); cmsEnveloped.close(); fileBuffer.flush(); fileBuffer.close(); FileInputStream fileIn = new FileInputStream(tempFile); this.copyStreams(fileIn, encryptedStream); fileIn.close(); boolean deleted = tempFile.delete(); } } /**Decrypts a formerly encrypted byte array*/ public byte[] decryptCMS(byte[] encrypted, Certificate cert, Key key) throws Exception { ByteArrayInputStream encryptedMem = new ByteArrayInputStream(encrypted); ByteArrayOutputStream decryptedMem = new ByteArrayOutputStream(); this.decryptCMS(encryptedMem, decryptedMem, cert, key); encryptedMem.close(); decryptedMem.close(); return (decryptedMem.toByteArray()); } /**Decrypts a formerly encrypted stream. An exception will be thrown if decryption is not possible*/ public void decryptCMS(InputStream encrypted, OutputStream decrypted, Certificate cert, Key key) throws Exception { BufferedInputStream bufferedEncrypted = new BufferedInputStream(encrypted); BufferedOutputStream bufferedDecrypted = new BufferedOutputStream(decrypted); X509Certificate x509Cert = this.castCertificate(cert); CMSEnvelopedDataParser parser = new CMSEnvelopedDataParser(bufferedEncrypted); RecipientId recipientId = new JceKeyTransRecipientId(x509Cert); RecipientInformation recipient = parser.getRecipientInfos().get(recipientId); if (recipient != null) { CMSTypedStream cmsEncrypted = recipient.getContentStream( new JceKeyTransEnvelopedRecipient(this.getPrivateKey(key)).setProvider("BC")); InputStream encryptedContent = cmsEncrypted.getContentStream(); this.copyStreams(encryptedContent, bufferedDecrypted); bufferedDecrypted.flush(); } else { throw new GeneralSecurityException("Wrong key used to decrypt the data."); } } /**Uncompresses a data stream*/ public void uncompressCMS(InputStream compressed, OutputStream uncompressed) throws Exception { CMSCompressedDataParser compressedParser = new CMSCompressedDataParser(new BufferedInputStream(compressed)); this.copyStreams(compressedParser.getContent().getContentStream(), uncompressed); uncompressed.flush(); } /**Compress a data stream*/ public void compressCMS(InputStream uncompressed, OutputStream compressed, boolean inMemory) throws Exception { //streamed compression does not work without a stream buffer in bc 1.45 and before CMSCompressedDataStreamGenerator generator = new CMSCompressedDataStreamGenerator(); if (inMemory) { ByteArrayOutputStream memBuffer = new ByteArrayOutputStream(); OutputStream cOut = generator.open(memBuffer, CMSCompressedDataStreamGenerator.ZLIB); this.copyStreams(uncompressed, cOut); cOut.flush(); cOut.close(); compressed.write(memBuffer.toByteArray()); } else { File tempFile = File.createTempFile("compress", ".temp"); FileOutputStream fileBuffer = new FileOutputStream(tempFile); OutputStream cOut = generator.open(fileBuffer, CMSCompressedDataStreamGenerator.ZLIB); this.copyStreams(uncompressed, cOut); cOut.flush(); cOut.close(); fileBuffer.flush(); fileBuffer.close(); FileInputStream fileIn = new FileInputStream(tempFile); this.copyStreams(fileIn, compressed); fileIn.close(); boolean deleted = tempFile.delete(); } } public void signCMS(InputStream unsigned, OutputStream signed, final String ALGORITHM_NAME, Certificate cert, Key key, boolean inMemory) throws Exception { X509Certificate x509Cert = this.castCertificate(cert); String oid = this.convertAlgorithmNameToOID(ALGORITHM_NAME); CMSSignedDataStreamGenerator generator = new CMSSignedDataStreamGenerator(); PrivateKey privKey = this.getPrivateKey(key); generator.addSigner(privKey, x509Cert, oid, "BC"); if (inMemory) { ByteArrayOutputStream memBuffer = new ByteArrayOutputStream(); OutputStream signedOut = generator.open(memBuffer, true); this.copyStreams(unsigned, signedOut); signedOut.flush(); signedOut.close(); signed.write(memBuffer.toByteArray()); } else { File tempFile = File.createTempFile("sign", ".temp"); FileOutputStream fileBuffer = new FileOutputStream(tempFile); OutputStream signedOut = generator.open(fileBuffer, true); this.copyStreams(unsigned, signedOut); signedOut.flush(); signedOut.close(); fileBuffer.flush(); fileBuffer.close(); FileInputStream fileIn = new FileInputStream(tempFile); this.copyStreams(fileIn, signed); fileIn.close(); boolean deleted = tempFile.delete(); } } public boolean verifySignatureCMS(InputStream signed, Certificate cert) throws Exception { CMSSignedDataParser parser = new CMSSignedDataParser(signed); parser.getSignedContent().drain(); SignerInformationStore signers = parser.getSignerInfos(); Collection signerCollection = signers.getSigners(); X509Certificate x509Cert = this.castCertificate(cert); Iterator it = signerCollection.iterator(); boolean verified = false; while (it.hasNext()) { SignerInformation signer = (SignerInformation) it.next(); if (!verified) { verified = signer.verify(x509Cert, "BC"); } } return (verified); } public void removeSignatureCMS(InputStream signed, OutputStream unsigned, Certificate cert) throws Exception { CMSSignedDataParser parser = new CMSSignedDataParser(signed); InputStream signedContent = parser.getSignedContent().getContentStream(); this.copyStreams(signedContent, unsigned); unsigned.flush(); } /**Generates a hash of a passed input stream*/ public byte[] generateFileHash(MessageDigest digest, InputStream in) throws IOException { BufferedInputStream inStream = new BufferedInputStream(in); byte[] buffer = new byte[4096]; int sizeRead = -1; while ((sizeRead = inStream.read(buffer)) != -1) { digest.update(buffer, 0, sizeRead); } inStream.close(); byte[] hash = null; hash = new byte[digest.getDigestLength()]; hash = digest.digest(); return hash; } // public static final void main( String[] args ){ // Security.addProvider(new BouncyCastleProvider()); // String mic1 = "kvIEoILs3pE05Xn2J31ICOdqeT4=, sha1"; // String mic2 = "kvIEoILs3pE05Xn2J31ICOdqeT4=,sha1"; // BCCryptoHelper helper = new BCCryptoHelper(); // System.out.println( helper.micIsEqual(mic1, mic2)); // } }