diff --git a/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnveloped.java b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnveloped.java new file mode 100644 index 0000000000..7b6b9b8bc9 --- /dev/null +++ b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnveloped.java @@ -0,0 +1,41 @@ +package org.bouncycastle.mail.smime; + +import org.bouncycastle.cms.CMSAuthEnvelopedData; +import org.bouncycastle.cms.CMSException; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimePart; + +/** + * containing class for an S/MIME pkcs7-mime encrypted MimePart. + */ +public class SMIMEAuthEnveloped + extends CMSAuthEnvelopedData +{ + MimePart message; + + public SMIMEAuthEnveloped( + MimeBodyPart message) + throws MessagingException, CMSException + { + super(SMIMEUtil.getInputStream(message)); + + this.message = message; + } + + public SMIMEAuthEnveloped( + MimeMessage message) + throws MessagingException, CMSException + { + super(SMIMEUtil.getInputStream(message)); + + this.message = message; + } + + public MimePart getEncryptedContent() + { + return message; + } +} diff --git a/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedGenerator.java b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedGenerator.java new file mode 100644 index 0000000000..2a23826729 --- /dev/null +++ b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedGenerator.java @@ -0,0 +1,191 @@ +package org.bouncycastle.mail.smime; + +import org.bouncycastle.asn1.ASN1EncodableVector; +import org.bouncycastle.asn1.ASN1ObjectIdentifier; +import org.bouncycastle.cms.*; +import org.bouncycastle.operator.OutputAEADEncryptor; +import org.bouncycastle.operator.OutputEncryptor; + +import javax.activation.CommandMap; +import javax.activation.MailcapCommandMap; +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import java.io.IOException; +import java.io.OutputStream; +import java.security.AccessController; +import java.security.PrivilegedAction; + +/** + * General class for generating a pkcs7-mime message using AEAD algorithm. + * + * A simple example of usage. + * + *
+ *      SMIMEAuthEnvelopedGenerator fact = new SMIMEAuthEnvelopedGenerator();
+ *
+ *      fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
+ *
+ *      MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_GCM).setProvider("BC").build());
+ * 
+ * + * Note: Most clients expect the MimeBodyPart to be in a MimeMultipart + * when it's sent. + */ +public class SMIMEAuthEnvelopedGenerator + extends SMIMEEnvelopedGenerator +{ + public static final String AES128_GCM = CMSAuthEnvelopedDataGenerator.AES128_GCM; + public static final String AES192_GCM = CMSAuthEnvelopedDataGenerator.AES192_GCM; + public static final String AES256_GCM = CMSAuthEnvelopedDataGenerator.AES256_GCM; + + private static final String AUTH_ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=authEnveloped-data"; + + final private AuthEnvelopedGenerator authFact; + + static + { + AccessController.doPrivileged(new PrivilegedAction() + { + public Object run() + { + CommandMap commandMap = CommandMap.getDefaultCommandMap(); + + if (commandMap instanceof MailcapCommandMap) + { + CommandMap.setDefaultCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap)); + } + + return null; + } + }); + } + + /** + * base constructor + */ + public SMIMEAuthEnvelopedGenerator() + { + authFact = new AuthEnvelopedGenerator(); + } + + /** + * add a recipientInfoGenerator. + */ + public void addRecipientInfoGenerator( + RecipientInfoGenerator recipientInfoGen) + throws IllegalArgumentException + { + authFact.addRecipientInfoGenerator(recipientInfoGen); + } + + /** + * Use a BER Set to store the recipient information + */ + public void setBerEncodeRecipients( + boolean berEncodeRecipientSet) + { + authFact.setBEREncodeRecipients(berEncodeRecipientSet); + } + + /** + * return encrypted content type for enveloped data. + */ + protected String getEncryptedContentType() { + return AUTH_ENCRYPTED_CONTENT_TYPE; + } + + /** + * return content encryptor. + */ + protected SMIMEStreamingProcessor getContentEncryptor( + MimeBodyPart content, + OutputEncryptor encryptor) + throws SMIMEException + { + if (encryptor instanceof OutputAEADEncryptor) { + return new ContentEncryptor(content, (OutputAEADEncryptor)encryptor); + } + // this would happen if the encryption algorithm is not AEAD algorithm + throw new SMIMEException("encryptor is not AEAD encryptor"); + } + + private static class AuthEnvelopedGenerator + extends CMSAuthEnvelopedDataStreamGenerator + { + private ASN1ObjectIdentifier dataType; + private ASN1EncodableVector recipientInfos; + + protected OutputStream open( + ASN1ObjectIdentifier dataType, + OutputStream out, + ASN1EncodableVector recipientInfos, + OutputAEADEncryptor encryptor) + throws IOException + { + this.dataType = dataType; + this.recipientInfos = recipientInfos; + + return super.open(dataType, out, recipientInfos, encryptor); + } + + OutputStream regenerate( + OutputStream out, + OutputAEADEncryptor encryptor) + throws IOException + { + return super.open(dataType, out, recipientInfos, encryptor); + } + } + + private class ContentEncryptor + implements SMIMEStreamingProcessor + { + private final MimeBodyPart _content; + private OutputAEADEncryptor _encryptor; + + private boolean _firstTime = true; + + ContentEncryptor( + MimeBodyPart content, + OutputAEADEncryptor encryptor) + { + _content = content; + _encryptor = encryptor; + } + + public void write(OutputStream out) + throws IOException + { + OutputStream encrypted; + + try + { + if (_firstTime) + { + encrypted = authFact.open(out, _encryptor); + + _firstTime = false; + } + else + { + encrypted = authFact.regenerate(out, _encryptor); + } + + CommandMap commandMap = CommandMap.getDefaultCommandMap(); + + if (commandMap instanceof MailcapCommandMap) + { + _content.getDataHandler().setCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap)); + } + + _content.writeTo(encrypted); + + encrypted.close(); + } + catch (MessagingException | CMSException e) + { + throw new WrappingIOException(e.toString(), e); + } + } + } +} diff --git a/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedParser.java b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedParser.java new file mode 100644 index 0000000000..e44f69c2d0 --- /dev/null +++ b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEAuthEnvelopedParser.java @@ -0,0 +1,72 @@ +package org.bouncycastle.mail.smime; + +import org.bouncycastle.cms.CMSAuthEnvelopedDataParser; +import org.bouncycastle.cms.CMSException; + +import javax.mail.MessagingException; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimePart; +import java.io.IOException; + +/** + * Stream based containing class for an S/MIME pkcs7-mime encrypted MimePart using AEAD algorithm. + */ +public class SMIMEAuthEnvelopedParser + extends CMSAuthEnvelopedDataParser +{ + private final MimePart message; + + public SMIMEAuthEnvelopedParser( + MimeBodyPart message) + throws IOException, MessagingException, CMSException + { + this(message, 0); + } + + public SMIMEAuthEnvelopedParser( + MimeMessage message) + throws IOException, MessagingException, CMSException + { + this(message, 0); + } + + /** + * Create a parser from a MimeBodyPart using the passed in buffer size + * for reading it. + * + * @param message body part to be parsed. + * @param bufferSize bufferSoze to be used. + */ + public SMIMEAuthEnvelopedParser( + MimeBodyPart message, + int bufferSize) + throws IOException, MessagingException, CMSException + { + super(SMIMEUtil.getInputStream(message, bufferSize)); + + this.message = message; + } + + /** + * Create a parser from a MimeMessage using the passed in buffer size + * for reading it. + * + * @param message message to be parsed. + * @param bufferSize bufferSize to be used. + */ + public SMIMEAuthEnvelopedParser( + MimeMessage message, + int bufferSize) + throws IOException, MessagingException, CMSException + { + super(SMIMEUtil.getInputStream(message, bufferSize)); + + this.message = message; + } + + public MimePart getEncryptedContent() + { + return message; + } +} diff --git a/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java index e431726567..a30864c3d2 100644 --- a/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java +++ b/mail/src/main/java/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java @@ -112,7 +112,25 @@ public void setBerEncodeRecipients( fact.setBEREncodeRecipients(berEncodeRecipientSet); } - /** + /** + * return encrypted content type for enveloped data. + */ + protected String getEncryptedContentType() { + return ENCRYPTED_CONTENT_TYPE; + } + + /** + * return content encryptor. + */ + protected SMIMEStreamingProcessor getContentEncryptor( + MimeBodyPart content, + OutputEncryptor encryptor) + throws SMIMEException + { + return new ContentEncryptor(content, encryptor); + } + + /** * if we get here we expect the Mime body part to be well defined. */ private MimeBodyPart make( @@ -124,8 +142,8 @@ private MimeBodyPart make( { MimeBodyPart data = new MimeBodyPart(); - data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE); - data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE); + data.setContent(getContentEncryptor(content, encryptor), getEncryptedContentType()); + data.addHeader("Content-Type", getEncryptedContentType()); data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\""); data.addHeader("Content-Description", "S/MIME Encrypted Message"); data.addHeader("Content-Transfer-Encoding", encoding); @@ -210,11 +228,7 @@ public void write(OutputStream out) encrypted.close(); } - catch (MessagingException e) - { - throw new WrappingIOException(e.toString(), e); - } - catch (CMSException e) + catch (MessagingException | CMSException e) { throw new WrappingIOException(e.toString(), e); } @@ -249,7 +263,7 @@ OutputStream regenerate( } } - private static class WrappingIOException + protected static class WrappingIOException extends IOException { private Throwable cause; diff --git a/mail/src/test/java/org/bouncycastle/mail/smime/test/AllTests.java b/mail/src/test/java/org/bouncycastle/mail/smime/test/AllTests.java index 65305772f1..662fc75958 100644 --- a/mail/src/test/java/org/bouncycastle/mail/smime/test/AllTests.java +++ b/mail/src/test/java/org/bouncycastle/mail/smime/test/AllTests.java @@ -49,6 +49,7 @@ public static Test suite() suite.addTestSuite(NewSMIMESignedTest.class); suite.addTestSuite(SignedMailValidatorTest.class); + suite.addTestSuite(NewSMIMEAuthEnvelopedTest.class); suite.addTestSuite(NewSMIMEEnvelopedTest.class); suite.addTestSuite(SMIMECompressedTest.class); suite.addTestSuite(SMIMEMiscTest.class); diff --git a/mail/src/test/java/org/bouncycastle/mail/smime/test/NewSMIMEAuthEnvelopedTest.java b/mail/src/test/java/org/bouncycastle/mail/smime/test/NewSMIMEAuthEnvelopedTest.java new file mode 100644 index 0000000000..b7708797c2 --- /dev/null +++ b/mail/src/test/java/org/bouncycastle/mail/smime/test/NewSMIMEAuthEnvelopedTest.java @@ -0,0 +1,501 @@ +package org.bouncycastle.mail.smime.test; + +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.*; +import org.bouncycastle.cms.jcajce.*; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.mail.smime.*; +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; + +import javax.crypto.Cipher; +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStreamReader; +import java.security.*; +import java.security.cert.CertificateEncodingException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Arrays; + +public class NewSMIMEAuthEnvelopedTest + 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; + + 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 NewSMIMEAuthEnvelopedTest(String name) + { + super(name); + } + + public static void main(String[] args) + { + junit.textui.TestRunner.run(NewSMIMEAuthEnvelopedTest.class); + } + + public static Test suite() + throws Exception + { + return new SMIMETestSetup(new TestSuite(NewSMIMEAuthEnvelopedTest.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"); + + SMIMEAuthEnvelopedGenerator gen = new SMIMEAuthEnvelopedGenerator(); + + 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.AES256_GCM).setProvider(BC).build()); + + assertEquals("application/pkcs7-mime; name=\"smime.p7m\"; smime-type=authEnveloped-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 testAES128Encrypted() + throws Exception + { + MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); + String algorithm = SMIMEAuthEnvelopedGenerator.AES256_GCM; + + verifyAlgorithm(algorithm, msg); + } + + public void testAES192Encrypted() + throws Exception + { + MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); + String algorithm = SMIMEAuthEnvelopedGenerator.AES256_GCM; + + verifyAlgorithm(algorithm, msg); + } + + public void testAES256Encrypted() + throws Exception + { + MimeBodyPart msg = SMIMETestUtil.makeMimeBodyPart("WallaWallaWashington"); + String algorithm = SMIMEAuthEnvelopedGenerator.AES256_GCM; + + 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"); + + SMIMEAuthEnvelopedGenerator gen = new SMIMEAuthEnvelopedGenerator(); + + // + // 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("SHA256", 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.AES256_GCM).setProvider(BC).build()); + + SMIMEAuthEnveloped m = new SMIMEAuthEnveloped(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"); + + SMIMEAuthEnvelopedGenerator gen = new SMIMEAuthEnvelopedGenerator(); + + 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.AES256_GCM).setProvider(BC).build()); + + SMIMEAuthEnvelopedParser m = new SMIMEAuthEnvelopedParser(mp); + + RecipientId recId = getRecipientId(_reciCert2); + + RecipientInformationStore recipients = m.getRecipientInfos(); + RecipientInformation recipient = recipients.get(recId); + + FileBackedMimeBodyPart res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransAuthEnvelopedRecipient(_reciKP2.getPrivate()).setProvider(BC))); + + SMIMETestUtil.verifyMessageBytes(_msg, res); + + mp = gen.generate(_msg, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_GCM).setProvider(BC).build()); + m = new SMIMEAuthEnvelopedParser(mp); + + res.dispose(); + + recId = getRecipientId(_reciCert); + + recipients = m.getRecipientInfos(); + recipient = recipients.get(recId); + + res = SMIMEUtil.toMimeBodyPart(recipient.getContentStream(new JceKeyTransAuthEnvelopedRecipient(_reciKP.getPrivate()).setProvider(BC))); + + SMIMETestUtil.verifyMessageBytes(_msg, res); + + res.dispose(); + } + + private void verifyAlgorithm( + String algorithmOid, + MimeBodyPart msg) + throws Exception + { + SMIMEAuthEnvelopedGenerator gen = new SMIMEAuthEnvelopedGenerator(); + + 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()); + SMIMEAuthEnveloped m = new SMIMEAuthEnveloped(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 + { + SMIMEAuthEnvelopedGenerator gen = new SMIMEAuthEnvelopedGenerator(); + + 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()); + SMIMEAuthEnvelopedParser m = new SMIMEAuthEnvelopedParser(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 + { + SMIMEAuthEnvelopedGenerator edGen = new SMIMEAuthEnvelopedGenerator(); + + 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_GCM).setProvider(BC).build()); + + SMIMEAuthEnveloped ed = new SMIMEAuthEnveloped(res); + + assertEquals(ed.getEncryptionAlgOID(), CMSAuthEnvelopedDataGenerator.AES128_GCM); + + 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()); + } +} diff --git a/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java b/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java index f1edc92993..322ef043ea 100644 --- a/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java +++ b/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedData.java @@ -134,6 +134,15 @@ public byte[] getMAC() recipientInfos, this.authEncAlg, secureReadable); } + + /** + * return the object identifier for the content encryption algorithm. + */ + public String getEncryptionAlgOID() + { + return authEncAlg.getAlgorithm().getId(); + } + /** * Return the originator information associated with this message if present. * diff --git a/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java b/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java index 92f93a2f63..f7de40cf35 100644 --- a/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java +++ b/pkix/src/main/java/org/bouncycastle/cms/CMSAuthEnvelopedGenerator.java @@ -4,6 +4,7 @@ import java.util.List; import org.bouncycastle.asn1.cms.OriginatorInfo; +import org.bouncycastle.asn1.nist.NISTObjectIdentifiers; /** * General class for generating a CMS enveloped-data message. @@ -11,7 +12,9 @@ public class CMSAuthEnvelopedGenerator extends CMSEnvelopedGenerator { - final List recipientInfoGenerators = new ArrayList(); + public static final String AES128_GCM = NISTObjectIdentifiers.id_aes128_GCM.getId(); + public static final String AES192_GCM = NISTObjectIdentifiers.id_aes192_GCM.getId(); + public static final String AES256_GCM = NISTObjectIdentifiers.id_aes256_GCM.getId(); protected CMSAttributeTableGenerator authAttrsGenerator = null; protected CMSAttributeTableGenerator unauthAttrsGenerator = null;