commit f60f53a70f251d678c0d28afa551961b2332a3be Author: Jan Dittberner Date: Fri Oct 10 19:44:24 2014 +0200 Bouncy Castle S/MIME demo 0.1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dba287f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +target/ +.settings/ +.checkstyle +.project +.classpath diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..79d263d --- /dev/null +++ b/pom.xml @@ -0,0 +1,74 @@ + +4.0.0 + + de.communardo.jdi + bcsmime-demo + 0.1 + jar + + bcsmime-demo + http://maven.apache.org + + + UTF-8 + + + + + junit + junit + 3.8.1 + test + + + org.bouncycastle + bcmail-jdk16 + 1.46 + + + javax.mail + mail + 1.4.4 + compile + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + 1.6 + 1.6 + false + + + + + + + diff --git a/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEDecrypt.java b/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEDecrypt.java new file mode 100644 index 0000000..e5e2023 --- /dev/null +++ b/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEDecrypt.java @@ -0,0 +1,100 @@ +/* + * Copyright (c) 2011 Jan Dittberner + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.communardo.jdi.bcsmime_demo; + +import java.io.ByteArrayInputStream; +import java.security.KeyStore; +import java.security.PrivateKey; +import java.security.cert.X509Certificate; +import java.util.Enumeration; + +import javax.mail.MessagingException; +import javax.mail.Session; +import javax.mail.internet.MimeMessage; + +import org.bouncycastle.cms.RecipientInformation; +import org.bouncycastle.cms.RecipientInformationStore; +import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient; +import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId; +import org.bouncycastle.mail.smime.SMIMEEnveloped; + +/** + * S/MIME encryption using the new BouncyCastle 1.46 APIs. + * + * @author Jan Dittberner + */ +public class SMIMEDecrypt { + private final KeyStore keystore; + + /** + * Create a new SMIMEDecrypt instance. + * + * @param argKeystore + * keystore with private keys. + */ + public SMIMEDecrypt(KeyStore argKeystore) { + this.keystore = argKeystore; + } + + /** + * Decrypt an encrypted S/MIME message. + * + * @param encrypted + * encrypted S/MIME message + * @return decrypted MIME message + * @throws Exception + * if an error occurs + */ + public MimeMessage decryptMessage(MimeMessage encrypted) + throws MessagingException, Exception { + SMIMEEnveloped message = new SMIMEEnveloped(encrypted); + + RecipientInformationStore recinfos = message.getRecipientInfos(); + Enumeration aliases = this.keystore.aliases(); + RecipientInformation recid = null; + String alias = null; + while ((recid == null) && aliases.hasMoreElements()) { + alias = aliases.nextElement(); + if (this.keystore.isKeyEntry(alias)) { + recid = recinfos.get(new JceKeyTransRecipientId( + (X509Certificate) this.keystore.getCertificate(alias))); + } + } + if (recid == null) { + throw new RuntimeException("No decryption key found"); + } + + JceKeyTransEnvelopedRecipient recipient = new JceKeyTransEnvelopedRecipient( + (PrivateKey) this.keystore.getKey(alias, "changeit" + .toCharArray())); + + byte[] content = recid.getContent(recipient); + + MimeMessage decrypted = new MimeMessage(Session + .getDefaultInstance(System.getProperties()), + new ByteArrayInputStream(content)); + decrypted.saveChanges(); + return decrypted; + } + +} diff --git a/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEEncrypt.java b/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEEncrypt.java new file mode 100644 index 0000000..c3f65a1 --- /dev/null +++ b/src/main/java/de/communardo/jdi/bcsmime_demo/SMIMEEncrypt.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2011 Jan Dittberner + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.communardo.jdi.bcsmime_demo; + +import java.security.KeyStore; +import java.security.cert.CertStore; +import java.security.cert.CertStoreParameters; +import java.security.cert.Certificate; +import java.security.cert.CollectionCertStoreParameters; +import java.security.cert.X509CertSelector; +import java.security.cert.X509Certificate; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Enumeration; +import java.util.List; + +import javax.mail.Address; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; + +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.cms.CMSAlgorithm; +import org.bouncycastle.cms.RecipientInfoGenerator; +import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder; +import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator; +import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator; +import org.bouncycastle.operator.OutputEncryptor; + +/** + * S/MIME encryption using the new BouncyCastle 1.46 APIs. + * + * @author Jan Dittberner + */ +public class SMIMEEncrypt { + private final CertStore certs; + + /** + * Creates a new SMIMEEncrypt instance. + * + * @param keystore + * key store to use for recipient certificates + */ + public SMIMEEncrypt(KeyStore keystore) { + List certificates = new ArrayList(); + + try { + Enumeration aliases = keystore.aliases(); + while (aliases.hasMoreElements()) { + Certificate cert = keystore.getCertificate(aliases + .nextElement()); + if (cert != null) { + certificates.add(cert); + } + } + + CertStoreParameters params = new CollectionCertStoreParameters( + certificates); + this.certs = CertStore.getInstance("Collection", params); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + /** + * Encrypts a MimeMessage to all its recipients. + * + * @param message + * MIME message to encrypt + * @return encrypted S/MIME message + * @throws Exception + * if an error occurs + */ + public MimeMessage encryptMessage(MimeMessage message) throws Exception { + SMIMEEnvelopedGenerator smeg = new SMIMEEnvelopedGenerator(); + for (Address recipient : message.getAllRecipients()) { + Collection certificates = getCertificates((InternetAddress) recipient); + for (Certificate cert : certificates) { + RecipientInfoGenerator recipientInfoGen = new JceKeyTransRecipientInfoGenerator( + (X509Certificate) cert); + smeg.addRecipientInfoGenerator(recipientInfoGen); + } + } + OutputEncryptor encryptor = new JceCMSContentEncryptorBuilder( + CMSAlgorithm.AES256_CBC).build(); + MimeBodyPart encryptedContent = smeg.generate(message, encryptor); + MimeMessage result = new MimeMessage(message); + result.setContent(encryptedContent.getContent(), encryptedContent + .getContentType()); + result.saveChanges(); + return result; + } + + /** + * Helper method for getting certificates from a keystore. + * + * @param recipient + * recipient address + * @return X.509 certificate for recipient + * @throws Exception + * if an error occurs + */ + private Collection getCertificates( + InternetAddress recipient) throws Exception { + X509CertSelector selector = new X509CertSelector(); + selector.setMatchAllSubjectAltNames(false); + selector.addSubjectAlternativeName(GeneralName.rfc822Name, recipient + .getAddress()); + + return this.certs.getCertificates(selector); + } +} diff --git a/src/test/java/de/communardo/jdi/bcsmime_demo/EncryptDecryptTest.java b/src/test/java/de/communardo/jdi/bcsmime_demo/EncryptDecryptTest.java new file mode 100644 index 0000000..390c2a6 --- /dev/null +++ b/src/test/java/de/communardo/jdi/bcsmime_demo/EncryptDecryptTest.java @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2011 Jan Dittberner + * + * Permission is hereby granted, free of charge, to any person obtaining + * a copy of this software and associated documentation files (the + * "Software"), to deal in the Software without restriction, including + * without limitation the rights to use, copy, modify, merge, publish, + * distribute, sublicense, and/or sell copies of the Software, and to + * permit persons to whom the Software is furnished to do so, subject to + * the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF + * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. + * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY + * CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, + * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE + * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ +package de.communardo.jdi.bcsmime_demo; + +import java.io.IOException; +import java.math.BigInteger; +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.KeyStore; +import java.security.Security; +import java.security.cert.Certificate; +import java.util.Date; + +import javax.mail.BodyPart; +import javax.mail.MessagingException; +import javax.mail.Multipart; +import javax.mail.Session; +import javax.mail.Message.RecipientType; +import javax.mail.internet.InternetAddress; +import javax.mail.internet.MimeBodyPart; +import javax.mail.internet.MimeMessage; +import javax.mail.internet.MimeMultipart; + +import junit.framework.TestCase; + +import org.bouncycastle.asn1.x500.X500Name; +import org.bouncycastle.asn1.x509.BasicConstraints; +import org.bouncycastle.asn1.x509.ExtendedKeyUsage; +import org.bouncycastle.asn1.x509.GeneralName; +import org.bouncycastle.asn1.x509.GeneralNames; +import org.bouncycastle.asn1.x509.KeyPurposeId; +import org.bouncycastle.asn1.x509.KeyUsage; +import org.bouncycastle.asn1.x509.X509Extension; +import org.bouncycastle.cert.X509CertificateHolder; +import org.bouncycastle.cert.X509v3CertificateBuilder; +import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter; +import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.bouncycastle.operator.ContentSigner; +import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder; + +/** + * Test Encryption and Decryption. + * + * @author Jan Dittberner <jan.dittberner@t-systems.com + * & g t ; + */ +public class EncryptDecryptTest extends TestCase { + private KeyStore keystore; + + /** + * {@inheritDoc} + * + * @see junit.framework.TestCase#setUp() + */ + @Override + public void setUp() throws Exception { + if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) { + Security.addProvider(new BouncyCastleProvider()); + } + + if (this.keystore == null) { + this.keystore = KeyStore.getInstance("JKS"); + keystore.load(null, null); + + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair keypair = kpg.generateKeyPair(); + + X500Name issuer = new X500Name( + "CN=Test Recipient,emailAddress=testrecpt@example.org"); + X500Name subject = issuer; + X509v3CertificateBuilder certbuilder = new JcaX509v3CertificateBuilder( + issuer, BigInteger.valueOf(System.currentTimeMillis()), + new Date(System.currentTimeMillis() - 50000), new Date( + System.currentTimeMillis() + 50000), subject, + keypair.getPublic()); + certbuilder.addExtension(X509Extension.basicConstraints, true, + new BasicConstraints(true)); + certbuilder.addExtension(X509Extension.keyUsage, true, + new KeyUsage(KeyUsage.digitalSignature + | KeyUsage.keyEncipherment)); + certbuilder.addExtension(X509Extension.extendedKeyUsage, true, + new ExtendedKeyUsage(KeyPurposeId.id_kp_emailProtection)); + certbuilder.addExtension(X509Extension.subjectAlternativeName, + false, new GeneralNames(new GeneralName( + GeneralName.rfc822Name, "testrecpt@example.org"))); + + ContentSigner signer = new JcaContentSignerBuilder("SHA256WithRSA") + .build(keypair.getPrivate()); + X509CertificateHolder certholder = certbuilder.build(signer); + + keystore.setKeyEntry("test", keypair.getPrivate(), "changeit" + .toCharArray(), + new Certificate[] { (new JcaX509CertificateConverter()) + .getCertificate(certholder) }); + } + } + + /** + * Test of {@link SMIMEEncrypt} and {@link SMIMEDecrypt}. + */ + public void testEncryptDecryptMail() throws Exception { + MimeMessage message = getNewMultipartMessage(); + assertNotNull(message); + + SMIMEEncrypt encrypt = new SMIMEEncrypt(keystore); + MimeMessage encrypted = encrypt.encryptMessage(message); + assertNotNull(encrypted); + encrypted.writeTo(System.err); + + SMIMEDecrypt decrypt = new SMIMEDecrypt(keystore); + MimeMessage decrypted = decrypt.decryptMessage(encrypted); + assertNotNull(decrypted); + decrypted.writeTo(System.err); + } + + /** + * Creates a new MimeMessage with one Bodypart. + * + * @return MimeMessage instance + * @throws MessagingException + * on error creating the message + */ + private MimeMessage getNewMultipartMessage() throws MessagingException, + IOException { + MimeMessage message = new MimeMessage(Session.getDefaultInstance(System + .getProperties())); + message.setFrom(new InternetAddress("testsender@example.org", + "Test Sender")); + message.addRecipient(RecipientType.TO, new InternetAddress( + "testrecpt@example.org", "Test Recipient")); + message.setSubject("Test subject"); + Multipart multipart = new MimeMultipart(); + BodyPart textpart = new MimeBodyPart(); + textpart.setText("Das ist ein Text"); + multipart.addBodyPart(textpart); + message.setContent(multipart); + return message; + } +}