package org.bouncycastle.mail.smime; import java.io.IOException; import java.io.OutputStream; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.List; import javax.activation.CommandMap; import javax.activation.MailcapCommandMap; import javax.mail.MessagingException; import javax.mail.internet.MimeBodyPart; import javax.mail.internet.MimeMessage; import org.bouncycastle.asn1.ASN1EncodableVector; import org.bouncycastle.asn1.ASN1ObjectIdentifier; import org.bouncycastle.cms.CMSEnvelopedDataGenerator; import org.bouncycastle.cms.CMSEnvelopedDataStreamGenerator; import org.bouncycastle.cms.CMSException; import org.bouncycastle.cms.RecipientInfoGenerator; import org.bouncycastle.operator.OutputEncryptor; /** * General class for generating a pkcs7-mime message. * * A simple example of usage. * * <pre> * SMIMEEnvelopedGenerator fact = new SMIMEEnvelopedGenerator(); * * fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC")); * * MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.RC2_CBC, 40).setProvider("BC").build()); * </pre> * * <b>Note:<b> Most clients expect the MimeBodyPart to be in a MimeMultipart * when it's sent. */ public class SMIMEEnvelopedGenerator extends SMIMEGenerator { public static final String DES_EDE3_CBC = CMSEnvelopedDataGenerator.DES_EDE3_CBC; public static final String RC2_CBC = CMSEnvelopedDataGenerator.RC2_CBC; public static final String IDEA_CBC = CMSEnvelopedDataGenerator.IDEA_CBC; public static final String CAST5_CBC = CMSEnvelopedDataGenerator.CAST5_CBC; public static final String AES128_CBC = CMSEnvelopedDataGenerator.AES128_CBC; public static final String AES192_CBC = CMSEnvelopedDataGenerator.AES192_CBC; public static final String AES256_CBC = CMSEnvelopedDataGenerator.AES256_CBC; public static final String CAMELLIA128_CBC = CMSEnvelopedDataGenerator.CAMELLIA128_CBC; public static final String CAMELLIA192_CBC = CMSEnvelopedDataGenerator.CAMELLIA192_CBC; public static final String CAMELLIA256_CBC = CMSEnvelopedDataGenerator.CAMELLIA256_CBC; public static final String SEED_CBC = CMSEnvelopedDataGenerator.SEED_CBC; public static final String DES_EDE3_WRAP = CMSEnvelopedDataGenerator.DES_EDE3_WRAP; public static final String AES128_WRAP = CMSEnvelopedDataGenerator.AES128_WRAP; public static final String AES256_WRAP = CMSEnvelopedDataGenerator.AES256_WRAP; public static final String CAMELLIA128_WRAP = CMSEnvelopedDataGenerator.CAMELLIA128_WRAP; public static final String CAMELLIA192_WRAP = CMSEnvelopedDataGenerator.CAMELLIA192_WRAP; public static final String CAMELLIA256_WRAP = CMSEnvelopedDataGenerator.CAMELLIA256_WRAP; public static final String SEED_WRAP = CMSEnvelopedDataGenerator.SEED_WRAP; public static final String ECDH_SHA1KDF = CMSEnvelopedDataGenerator.ECDH_SHA1KDF; private static final String ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=enveloped-data"; private EnvelopedGenerator fact; private List recipients = new ArrayList(); static { AccessController.doPrivileged(new PrivilegedAction() { public Object run() { CommandMap commandMap = CommandMap.getDefaultCommandMap(); if (commandMap instanceof MailcapCommandMap) { CommandMap.setDefaultCommandMap(addCommands((MailcapCommandMap)commandMap)); } return null; } }); } private static MailcapCommandMap addCommands(MailcapCommandMap mc) { 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"); return mc; } /** * base constructor */ public SMIMEEnvelopedGenerator() { fact = new EnvelopedGenerator(); } /** * add a recipientInfoGenerator. */ public void addRecipientInfoGenerator( RecipientInfoGenerator recipientInfoGen) throws IllegalArgumentException { fact.addRecipientInfoGenerator(recipientInfoGen); } /** * Use a BER Set to store the recipient information */ public void setBerEncodeRecipients( boolean berEncodeRecipientSet) { fact.setBEREncodeRecipients(berEncodeRecipientSet); } /** * if we get here we expect the Mime body part to be well defined. */ private MimeBodyPart make( MimeBodyPart content, OutputEncryptor encryptor) throws SMIMEException { try { MimeBodyPart data = new MimeBodyPart(); data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE); data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE); data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\""); data.addHeader("Content-Description", "S/MIME Encrypted Message"); data.addHeader("Content-Transfer-Encoding", encoding); return data; } catch (MessagingException e) { throw new SMIMEException("exception putting multi-part together.", e); } } /** * generate an enveloped object that contains an SMIME Enveloped * object using the given content encryptor */ public MimeBodyPart generate( MimeBodyPart content, OutputEncryptor encryptor) throws SMIMEException { return make(makeContentBodyPart(content), encryptor); } /** * generate an enveloped object that contains an SMIME Enveloped * object using the given provider from the contents of the passed in * message */ public MimeBodyPart generate( MimeMessage message, OutputEncryptor encryptor) throws SMIMEException { try { message.saveChanges(); // make sure we're up to date. } catch (MessagingException e) { throw new SMIMEException("unable to save message", e); } return make(makeContentBodyPart(message), encryptor); } private class ContentEncryptor implements SMIMEStreamingProcessor { private final MimeBodyPart _content; private OutputEncryptor _encryptor; private boolean _firstTime = true; ContentEncryptor( MimeBodyPart content, OutputEncryptor encryptor) { _content = content; _encryptor = encryptor; } public void write(OutputStream out) throws IOException { OutputStream encrypted; try { if (_firstTime) { encrypted = fact.open(out, _encryptor); _firstTime = false; } else { encrypted = fact.regenerate(out, _encryptor); } CommandMap commandMap = CommandMap.getDefaultCommandMap(); if (commandMap instanceof MailcapCommandMap) { _content.getDataHandler().setCommandMap(addCommands((MailcapCommandMap)commandMap)); } _content.writeTo(encrypted); encrypted.close(); } catch (MessagingException e) { throw new WrappingIOException(e.toString(), e); } catch (CMSException e) { throw new WrappingIOException(e.toString(), e); } } } private class EnvelopedGenerator extends CMSEnvelopedDataStreamGenerator { private ASN1ObjectIdentifier dataType; private ASN1EncodableVector recipientInfos; protected OutputStream open( ASN1ObjectIdentifier dataType, OutputStream out, ASN1EncodableVector recipientInfos, OutputEncryptor encryptor) throws IOException { this.dataType = dataType; this.recipientInfos = recipientInfos; return super.open(dataType, out, recipientInfos, encryptor); } OutputStream regenerate( OutputStream out, OutputEncryptor encryptor) throws IOException { return super.open(dataType, out, recipientInfos, encryptor); } } private static class WrappingIOException extends IOException { private Throwable cause; WrappingIOException(String msg, Throwable cause) { super(msg); this.cause = cause; } public Throwable getCause() { return cause; } } }