package org.bouncycastle.mail.smime.test; import java.io.ByteArrayOutputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStreamReader; import java.security.KeyPair; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.PrivateKey; import java.security.Security; import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.Arrays; import javax.crypto.Cipher; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import junit.framework.Test; import junit.framework.TestCase; import junit.framework.TestSuite; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo; import org.bouncycastle.cms.CMSAlgorithm; import org.bouncycastle.cms.CMSEnvelopedDataGenerator; import org.bouncycastle.cms.KeyTransRecipientId; import org.bouncycastle.cms.RecipientId; import org.bouncycastle.cms.RecipientInformation; import org.bouncycastle.cms.RecipientInformationStore; import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; import org.bouncycastle.cms.jcajce.JceKeyAgreeEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientId; import org.bouncycastle.cms.jcajce.JceKeyAgreeRecipientInfoGenerator; import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId; import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.bouncycastle.mail.smime.SMIMEEnveloped; import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator; import org.bouncycastle.mail.smime.SMIMEEnvelopedParser; import org.bouncycastle.mail.smime.SMIMEUtil; import org.bouncycastle.mail.smime.util.FileBackedMimeBodyPart; import org.bouncycastle.openssl.PEMKeyPair; import org.bouncycastle.openssl.PEMParser; import org.bouncycastle.openssl.jcajce.JcaPEMKeyConverter; import org.bouncycastle.util.encoders.Base64; public class NewSMIMEEnvelopedTest extends TestCase { private static final String BC = BouncyCastleProvider.PROVIDER_NAME; private static String _signDN; private static KeyPair _signKP; private static String _reciDN; private static KeyPair _reciKP; private static X509Certificate _reciCert; private static String _reciDN2; private static KeyPair _reciKP2; private static X509Certificate _reciCert2; private static KeyPair _origEcKP; private static KeyPair _reciEcKP; private static X509Certificate _reciEcCert; private static KeyPair _reciEcKP2; private static X509Certificate _reciEcCert2; private static boolean _initialised = false; private static final byte[] testMessage = Base64.decode( "TUlNRS1WZXJzaW9uOiAxLjANCkNvbnRlbnQtVHlwZTogbXVsdGlwYXJ0L21peGVkOyANCglib3VuZGFye" + "T0iLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyIg0KQ29udGVudC1MYW5ndWFnZTogZW" + "4NCkNvbnRlbnQtRGVzY3JpcHRpb246IEEgbWFpbCBmb2xsb3dpbmcgdGhlIERJUkVDVCBwcm9qZWN0IHN" + "wZWNpZmljYXRpb25zDQoNCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzUwMTMyDQpDb250" + "ZW50LVR5cGU6IHRleHQvcGxhaW47IG5hbWU9bnVsbDsgY2hhcnNldD11cy1hc2NpaQ0KQ29udGVudC1Uc" + "mFuc2Zlci1FbmNvZGluZzogN2JpdA0KQ29udGVudC1EaXNwb3NpdGlvbjogaW5saW5lOyBmaWxlbmFtZT" + "1udWxsDQoNCkNpYW8gZnJvbSB2aWVubmENCi0tLS0tLT1fUGFydF8wXzI2MDM5NjM4Ni4xMzUyOTA0NzU" + "wMTMyLS0NCg=="); private static void init() throws Exception { if (!_initialised) { if (Security.getProvider("BC") == null) { Security.addProvider(new BouncyCastleProvider()); } _initialised = true; _signDN = "O=Bouncy Castle, C=AU"; _signKP = CMSTestUtil.makeKeyPair(); _reciDN = "CN=Doug, OU=Sales, O=Bouncy Castle, C=AU"; _reciKP = CMSTestUtil.makeKeyPair(); _reciCert = CMSTestUtil.makeCertificate(_reciKP, _reciDN, _signKP, _signDN); _reciDN2 = "CN=Fred, OU=Sales, O=Bouncy Castle, C=AU"; _reciKP2 = CMSTestUtil.makeKeyPair(); _reciCert2 = CMSTestUtil.makeCertificate(_reciKP2, _reciDN2, _signKP, _signDN); _origEcKP = CMSTestUtil.makeEcDsaKeyPair(); _reciEcKP = CMSTestUtil.makeEcDsaKeyPair(); _reciEcCert = CMSTestUtil.makeCertificate(_reciEcKP, _reciDN, _signKP, _signDN); _reciEcKP2 = CMSTestUtil.makeEcDsaKeyPair(); _reciEcCert2 = CMSTestUtil.makeCertificate(_reciEcKP2, _reciDN2, _signKP, _signDN); } } public NewSMIMEEnvelopedTest( String name) { super(name); } public static void main( String args[]) { junit.textui.TestRunner.run(NewSMIMEEnvelopedTest.class); } public static Test suite() throws Exception { return new SMIMETestSetup(new TestSuite(NewSMIMEEnvelopedTest.class)); } public void setUp() throws Exception { init(); } private MimeMessage loadMessage(String name) throws MessagingException, FileNotFoundException { Session session = Session.getDefaultInstance(System.getProperties(), null); return new MimeMessage(session, getClass().getResourceAsStream(name)); } private X509Certificate loadCert(String name) throws Exception { return (X509Certificate)CertificateFactory.getInstance("X.509", BC).generateCertificate(getClass().getResourceAsStream(name)); } private PrivateKey loadKey(String name) throws Exception { return new JcaPEMKeyConverter().setProvider("BC").getKeyPair((PEMKeyPair)(new PEMParser(new InputStreamReader(getClass().getResourceAsStream(name)))).readObject()).getPrivate(); } public void testHeaders() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build()); assertEquals("application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data", mp.getHeader("Content-Type")[0]); assertEquals("attachment; filename=\"smime.p7m\"", mp.getHeader("Content-Disposition")[0]); assertEquals("S/MIME Encrypted Message", mp.getHeader("Content-Description")[0]); } public void testDESEDE3Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC; verifyAlgorithm(algorithm, msg); } public void testParserDESEDE3Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.DES_EDE3_CBC; verifyParserAlgorithm(algorithm, msg); } public void testIDEAEncrypted() throws Exception { if (isPresent("IDEA")) { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.IDEA_CBC; verifyAlgorithm(algorithm, msg); } } private boolean isPresent(String algorithm) throws Exception { try { Cipher.getInstance(algorithm, BC); return true; } catch (NoSuchAlgorithmException e) { return false; } } public void testRC2Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.RC2_CBC; verifyAlgorithm(algorithm, msg); } public void testCASTEncrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.CAST5_CBC; verifyAlgorithm(algorithm, msg); } public void testAES128Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.AES128_CBC; verifyAlgorithm(algorithm, msg); } public void testAES192Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.AES192_CBC; verifyAlgorithm(algorithm, msg); } public void testAES256Encrypted() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); String algorithm = SMIMEEnvelopedGenerator.AES256_CBC; verifyAlgorithm(algorithm, msg); } public void testSubKeyId() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); // // create a subject key id - this has to be done the same way as // it is done in the certificate associated with the private key // MessageDigest dig = MessageDigest.getInstance("SHA1", BC); dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes()); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(BC).build()); SMIMEEnveloped m = new SMIMEEnveloped(mp); dig.update(SubjectPublicKeyInfo.getInstance(_reciCert.getPublicKey().getEncoded()).getPublicKeyData().getBytes()); RecipientId recId = new KeyTransRecipientId(dig.digest()); RecipientInformationStore recipients = m.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(msg, res); } public void testDotNetEncMailMatch() throws Exception { MimeMessage message = loadMessage("dotnet_encrypted_mail.eml"); SMIMEEnveloped env = new SMIMEEnveloped(message); RecipientInformationStore store = env.getRecipientInfos(); assertNotNull(store.get(new JceKeyTransRecipientId(loadCert("dotnet_enc_cert.pem")))); } public void testAES128() throws Exception { MimeMessage message = loadMessage("test128.message"); SMIMEEnveloped env = new SMIMEEnveloped(message); RecipientInformationStore store = env.getRecipientInfos(); RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem"))); assertNotNull(recipInfo); byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem"))); assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content)); } public void testAES192() throws Exception { MimeMessage message = loadMessage("test192.message"); SMIMEEnveloped env = new SMIMEEnveloped(message); RecipientInformationStore store = env.getRecipientInfos(); RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem"))); assertNotNull(recipInfo); byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem"))); assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content)); } public void testAES256() throws Exception { MimeMessage message = loadMessage("test256.message"); SMIMEEnveloped env = new SMIMEEnveloped(message); RecipientInformationStore store = env.getRecipientInfos(); RecipientInformation recipInfo = store.get(new JceKeyTransRecipientId(loadCert("cert.pem"))); assertNotNull(recipInfo); byte[] content = recipInfo.getContent(new JceKeyTransEnvelopedRecipient(loadKey("key.pem"))); assertTrue(org.bouncycastle.util.Arrays.areEqual(testMessage, content)); } public void testCapEncrypt() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); // // create a subject key id - this has to be done the same way as // it is done in the certificate associated with the private key // MessageDigest dig = MessageDigest.getInstance("SHA1", BC); dig.update(_reciCert.getPublicKey().getEncoded()); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(dig.digest(), _reciCert.getPublicKey()).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build()); SMIMEEnveloped m = new SMIMEEnveloped(mp); dig.update(_reciCert.getPublicKey().getEncoded()); RecipientId recId = new KeyTransRecipientId(dig.digest()); RecipientInformationStore recipients = m.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(msg, res); } public void testTwoRecipients() throws Exception { MimeBodyPart _msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC)); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert2).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(_msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider(BC).build()); SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(mp); RecipientId recId = getRecipientId(_reciCert2); RecipientInformationStore recipients = m.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); FileBackedMimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP2.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(_msg, res); m = new SMIMEEnvelopedParser(mp); res.dispose(); recId = getRecipientId(_reciCert); recipients = m.getRecipientInfos(); recipient = recipients.get(recId); res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(_msg, res); res.dispose(); } private void verifyAlgorithm( String algorithmOid, MimeBodyPart msg) throws Exception { SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build()); SMIMEEnveloped m = new SMIMEEnveloped(mp); RecipientId recId = getRecipientId(_reciCert); RecipientInformationStore recipients = m.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(msg, res); } private void verifyParserAlgorithm( String algorithmOid, MimeBodyPart msg) throws Exception { SMIMEEnvelopedGenerator gen = new SMIMEEnvelopedGenerator(); gen.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(_reciCert).setProvider(BC)); // // generate a MimeBodyPart object which encapsulates the content // we want encrypted. // MimeBodyPart mp = gen.generate(msg, new JceCMSContentEncryptorBuilder(new ASN1ObjectIdentifier(algorithmOid)).setProvider(BC).build()); SMIMEEnvelopedParser m = new SMIMEEnvelopedParser(mp); RecipientId recId = getRecipientId(_reciCert); RecipientInformationStore recipients = m.getRecipientInfos(); RecipientInformation recipient = recipients.get(recId); MimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContent(new JceKeyTransEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); SMIMETestUtil.verifyMessageBytes(msg, res); } private RecipientId getRecipientId( X509Certificate cert) throws IOException, CertificateEncodingException { RecipientId recId = new JceKeyTransRecipientId(cert); return recId; } public void testKDFAgreements() throws Exception { MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); doTryAgreement(msg, CMSAlgorithm.ECDH_SHA1KDF); doTryAgreement(msg, CMSAlgorithm.ECDH_SHA224KDF); doTryAgreement(msg, CMSAlgorithm.ECDH_SHA256KDF); doTryAgreement(msg, CMSAlgorithm.ECDH_SHA384KDF); doTryAgreement(msg, CMSAlgorithm.ECDH_SHA512KDF); doTryAgreement(msg, CMSAlgorithm.ECCDH_SHA1KDF); doTryAgreement(msg, CMSAlgorithm.ECCDH_SHA224KDF); doTryAgreement(msg, CMSAlgorithm.ECCDH_SHA256KDF); doTryAgreement(msg, CMSAlgorithm.ECCDH_SHA384KDF); doTryAgreement(msg, CMSAlgorithm.ECCDH_SHA512KDF); doTryAgreement(msg, CMSAlgorithm.ECMQV_SHA1KDF); doTryAgreement(msg, CMSAlgorithm.ECMQV_SHA224KDF); doTryAgreement(msg, CMSAlgorithm.ECMQV_SHA256KDF); doTryAgreement(msg, CMSAlgorithm.ECMQV_SHA384KDF); doTryAgreement(msg, CMSAlgorithm.ECMQV_SHA512KDF); } private void doTryAgreement(MimeBodyPart data, ASN1ObjectIdentifier algorithm) throws Exception { SMIMEEnvelopedGenerator edGen = new SMIMEEnvelopedGenerator(); edGen.addRecipientInfoGenerator(new JceKeyAgreeRecipientInfoGenerator(algorithm, _origEcKP.getPrivate(), _origEcKP.getPublic(), CMSAlgorithm.AES128_WRAP).addRecipient(_reciEcCert).setProvider(BC)); MimeBodyPart res = edGen.generate( data, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES128_CBC).setProvider(BC).build()); SMIMEEnveloped ed = new SMIMEEnveloped(res); assertEquals(ed.getEncryptionAlgOID(), CMSEnvelopedDataGenerator.AES128_CBC); RecipientInformationStore recipients = ed.getRecipientInfos(); confirmDataReceived(recipients, data, _reciEcCert, _reciEcKP.getPrivate(), BC); confirmNumberRecipients(recipients, 1); } private static void confirmDataReceived(RecipientInformationStore recipients, MimeBodyPart expectedData, X509Certificate reciCert, PrivateKey reciPrivKey, String provider) throws Exception { RecipientId rid = new JceKeyAgreeRecipientId(reciCert); RecipientInformation recipient = recipients.get(rid); assertNotNull(recipient); ByteArrayOutputStream bOut = new ByteArrayOutputStream(); expectedData.writeTo(bOut); byte[] actualData = recipient.getContent(new JceKeyAgreeEnvelopedRecipient(reciPrivKey).setProvider(provider)); assertEquals(true, Arrays.equals(bOut.toByteArray(), actualData)); } private static void confirmNumberRecipients(RecipientInformationStore recipients, int count) { assertEquals(count, recipients.getRecipients().size()); } }