Skip to content

Commit 79999fe

Browse files
committed
Support AuthEnvelopedData in mail SMIME
This adds SMIMEAuthEnveloped classes that work in a similar way as SMIMEEnveloped but are meant for AuthEnvelopedData. That allows using AES GCM and improves S/MIME 4.0 support where this is required.
1 parent 7324d9a commit 79999fe

File tree

8 files changed

+842
-10
lines changed

8 files changed

+842
-10
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
package org.bouncycastle.mail.smime;
2+
3+
import org.bouncycastle.cms.CMSAuthEnvelopedData;
4+
import org.bouncycastle.cms.CMSException;
5+
6+
import javax.mail.MessagingException;
7+
import javax.mail.internet.MimeBodyPart;
8+
import javax.mail.internet.MimeMessage;
9+
import javax.mail.internet.MimePart;
10+
11+
/**
12+
* containing class for an S/MIME pkcs7-mime encrypted MimePart.
13+
*/
14+
public class SMIMEAuthEnveloped
15+
extends CMSAuthEnvelopedData
16+
{
17+
MimePart message;
18+
19+
public SMIMEAuthEnveloped(
20+
MimeBodyPart message)
21+
throws MessagingException, CMSException
22+
{
23+
super(SMIMEUtil.getInputStream(message));
24+
25+
this.message = message;
26+
}
27+
28+
public SMIMEAuthEnveloped(
29+
MimeMessage message)
30+
throws MessagingException, CMSException
31+
{
32+
super(SMIMEUtil.getInputStream(message));
33+
34+
this.message = message;
35+
}
36+
37+
public MimePart getEncryptedContent()
38+
{
39+
return message;
40+
}
41+
}
Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
1+
package org.bouncycastle.mail.smime;
2+
3+
import org.bouncycastle.asn1.ASN1EncodableVector;
4+
import org.bouncycastle.asn1.ASN1ObjectIdentifier;
5+
import org.bouncycastle.cms.*;
6+
import org.bouncycastle.operator.OutputAEADEncryptor;
7+
import org.bouncycastle.operator.OutputEncryptor;
8+
9+
import javax.activation.CommandMap;
10+
import javax.activation.MailcapCommandMap;
11+
import javax.mail.MessagingException;
12+
import javax.mail.internet.MimeBodyPart;
13+
import java.io.IOException;
14+
import java.io.OutputStream;
15+
import java.security.AccessController;
16+
import java.security.PrivilegedAction;
17+
18+
/**
19+
* General class for generating a pkcs7-mime message using AEAD algorithm.
20+
*
21+
* A simple example of usage.
22+
*
23+
* <pre>
24+
* SMIMEAuthEnvelopedGenerator fact = new SMIMEAuthEnvelopedGenerator();
25+
*
26+
* fact.addRecipientInfoGenerator(new JceKeyTransRecipientInfoGenerator(recipientCert).setProvider("BC"));
27+
*
28+
* MimeBodyPart mp = fact.generate(content, new JceCMSContentEncryptorBuilder(CMSAlgorithm.AES256_GCM).setProvider("BC").build());
29+
* </pre>
30+
*
31+
* <b>Note:</b> Most clients expect the MimeBodyPart to be in a MimeMultipart
32+
* when it's sent.
33+
*/
34+
public class SMIMEAuthEnvelopedGenerator
35+
extends SMIMEEnvelopedGenerator
36+
{
37+
public static final String AES128_GCM = CMSAuthEnvelopedDataGenerator.AES128_GCM;
38+
public static final String AES192_GCM = CMSAuthEnvelopedDataGenerator.AES192_GCM;
39+
public static final String AES256_GCM = CMSAuthEnvelopedDataGenerator.AES256_GCM;
40+
41+
private static final String AUTH_ENCRYPTED_CONTENT_TYPE = "application/pkcs7-mime; name=\"smime.p7m\"; smime-type=authEnveloped-data";
42+
43+
final private AuthEnvelopedGenerator authFact;
44+
45+
static
46+
{
47+
AccessController.doPrivileged(new PrivilegedAction()
48+
{
49+
public Object run()
50+
{
51+
CommandMap commandMap = CommandMap.getDefaultCommandMap();
52+
53+
if (commandMap instanceof MailcapCommandMap)
54+
{
55+
CommandMap.setDefaultCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap));
56+
}
57+
58+
return null;
59+
}
60+
});
61+
}
62+
63+
/**
64+
* base constructor
65+
*/
66+
public SMIMEAuthEnvelopedGenerator()
67+
{
68+
authFact = new AuthEnvelopedGenerator();
69+
}
70+
71+
/**
72+
* add a recipientInfoGenerator.
73+
*/
74+
public void addRecipientInfoGenerator(
75+
RecipientInfoGenerator recipientInfoGen)
76+
throws IllegalArgumentException
77+
{
78+
authFact.addRecipientInfoGenerator(recipientInfoGen);
79+
}
80+
81+
/**
82+
* Use a BER Set to store the recipient information
83+
*/
84+
public void setBerEncodeRecipients(
85+
boolean berEncodeRecipientSet)
86+
{
87+
authFact.setBEREncodeRecipients(berEncodeRecipientSet);
88+
}
89+
90+
/**
91+
* return encrypted content type for enveloped data.
92+
*/
93+
protected String getEncryptedContentType() {
94+
return AUTH_ENCRYPTED_CONTENT_TYPE;
95+
}
96+
97+
/**
98+
* return content encryptor.
99+
*/
100+
protected SMIMEStreamingProcessor getContentEncryptor(
101+
MimeBodyPart content,
102+
OutputEncryptor encryptor)
103+
throws SMIMEException
104+
{
105+
if (encryptor instanceof OutputAEADEncryptor) {
106+
return new ContentEncryptor(content, (OutputAEADEncryptor)encryptor);
107+
}
108+
// this would happen if the encryption algorithm is not AEAD algorithm
109+
throw new SMIMEException("encryptor is not AEAD encryptor");
110+
}
111+
112+
private static class AuthEnvelopedGenerator
113+
extends CMSAuthEnvelopedDataStreamGenerator
114+
{
115+
private ASN1ObjectIdentifier dataType;
116+
private ASN1EncodableVector recipientInfos;
117+
118+
protected OutputStream open(
119+
ASN1ObjectIdentifier dataType,
120+
OutputStream out,
121+
ASN1EncodableVector recipientInfos,
122+
OutputAEADEncryptor encryptor)
123+
throws IOException
124+
{
125+
this.dataType = dataType;
126+
this.recipientInfos = recipientInfos;
127+
128+
return super.open(dataType, out, recipientInfos, encryptor);
129+
}
130+
131+
OutputStream regenerate(
132+
OutputStream out,
133+
OutputAEADEncryptor encryptor)
134+
throws IOException
135+
{
136+
return super.open(dataType, out, recipientInfos, encryptor);
137+
}
138+
}
139+
140+
private class ContentEncryptor
141+
implements SMIMEStreamingProcessor
142+
{
143+
private final MimeBodyPart _content;
144+
private OutputAEADEncryptor _encryptor;
145+
146+
private boolean _firstTime = true;
147+
148+
ContentEncryptor(
149+
MimeBodyPart content,
150+
OutputAEADEncryptor encryptor)
151+
{
152+
_content = content;
153+
_encryptor = encryptor;
154+
}
155+
156+
public void write(OutputStream out)
157+
throws IOException
158+
{
159+
OutputStream encrypted;
160+
161+
try
162+
{
163+
if (_firstTime)
164+
{
165+
encrypted = authFact.open(out, _encryptor);
166+
167+
_firstTime = false;
168+
}
169+
else
170+
{
171+
encrypted = authFact.regenerate(out, _encryptor);
172+
}
173+
174+
CommandMap commandMap = CommandMap.getDefaultCommandMap();
175+
176+
if (commandMap instanceof MailcapCommandMap)
177+
{
178+
_content.getDataHandler().setCommandMap(MailcapUtil.addCommands((MailcapCommandMap)commandMap));
179+
}
180+
181+
_content.writeTo(encrypted);
182+
183+
encrypted.close();
184+
}
185+
catch (MessagingException | CMSException e)
186+
{
187+
throw new WrappingIOException(e.toString(), e);
188+
}
189+
}
190+
}
191+
}
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
package org.bouncycastle.mail.smime;
2+
3+
import org.bouncycastle.cms.CMSAuthEnvelopedDataParser;
4+
import org.bouncycastle.cms.CMSException;
5+
6+
import javax.mail.MessagingException;
7+
import javax.mail.internet.MimeBodyPart;
8+
import javax.mail.internet.MimeMessage;
9+
import javax.mail.internet.MimePart;
10+
import java.io.IOException;
11+
12+
/**
13+
* Stream based containing class for an S/MIME pkcs7-mime encrypted MimePart using AEAD algorithm.
14+
*/
15+
public class SMIMEAuthEnvelopedParser
16+
extends CMSAuthEnvelopedDataParser
17+
{
18+
private final MimePart message;
19+
20+
public SMIMEAuthEnvelopedParser(
21+
MimeBodyPart message)
22+
throws IOException, MessagingException, CMSException
23+
{
24+
this(message, 0);
25+
}
26+
27+
public SMIMEAuthEnvelopedParser(
28+
MimeMessage message)
29+
throws IOException, MessagingException, CMSException
30+
{
31+
this(message, 0);
32+
}
33+
34+
/**
35+
* Create a parser from a MimeBodyPart using the passed in buffer size
36+
* for reading it.
37+
*
38+
* @param message body part to be parsed.
39+
* @param bufferSize bufferSoze to be used.
40+
*/
41+
public SMIMEAuthEnvelopedParser(
42+
MimeBodyPart message,
43+
int bufferSize)
44+
throws IOException, MessagingException, CMSException
45+
{
46+
super(SMIMEUtil.getInputStream(message, bufferSize));
47+
48+
this.message = message;
49+
}
50+
51+
/**
52+
* Create a parser from a MimeMessage using the passed in buffer size
53+
* for reading it.
54+
*
55+
* @param message message to be parsed.
56+
* @param bufferSize bufferSize to be used.
57+
*/
58+
public SMIMEAuthEnvelopedParser(
59+
MimeMessage message,
60+
int bufferSize)
61+
throws IOException, MessagingException, CMSException
62+
{
63+
super(SMIMEUtil.getInputStream(message, bufferSize));
64+
65+
this.message = message;
66+
}
67+
68+
public MimePart getEncryptedContent()
69+
{
70+
return message;
71+
}
72+
}

mail/src/main/java/org/bouncycastle/mail/smime/SMIMEEnvelopedGenerator.java

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,25 @@ public void setBerEncodeRecipients(
112112
fact.setBEREncodeRecipients(berEncodeRecipientSet);
113113
}
114114

115-
/**
115+
/**
116+
* return encrypted content type for enveloped data.
117+
*/
118+
protected String getEncryptedContentType() {
119+
return ENCRYPTED_CONTENT_TYPE;
120+
}
121+
122+
/**
123+
* return content encryptor.
124+
*/
125+
protected SMIMEStreamingProcessor getContentEncryptor(
126+
MimeBodyPart content,
127+
OutputEncryptor encryptor)
128+
throws SMIMEException
129+
{
130+
return new ContentEncryptor(content, encryptor);
131+
}
132+
133+
/**
116134
* if we get here we expect the Mime body part to be well defined.
117135
*/
118136
private MimeBodyPart make(
@@ -124,8 +142,8 @@ private MimeBodyPart make(
124142
{
125143
MimeBodyPart data = new MimeBodyPart();
126144

127-
data.setContent(new ContentEncryptor(content, encryptor), ENCRYPTED_CONTENT_TYPE);
128-
data.addHeader("Content-Type", ENCRYPTED_CONTENT_TYPE);
145+
data.setContent(getContentEncryptor(content, encryptor), getEncryptedContentType());
146+
data.addHeader("Content-Type", getEncryptedContentType());
129147
data.addHeader("Content-Disposition", "attachment; filename=\"smime.p7m\"");
130148
data.addHeader("Content-Description", "S/MIME Encrypted Message");
131149
data.addHeader("Content-Transfer-Encoding", encoding);
@@ -210,11 +228,7 @@ public void write(OutputStream out)
210228

211229
encrypted.close();
212230
}
213-
catch (MessagingException e)
214-
{
215-
throw new WrappingIOException(e.toString(), e);
216-
}
217-
catch (CMSException e)
231+
catch (MessagingException | CMSException e)
218232
{
219233
throw new WrappingIOException(e.toString(), e);
220234
}
@@ -249,7 +263,7 @@ OutputStream regenerate(
249263
}
250264
}
251265

252-
private static class WrappingIOException
266+
protected static class WrappingIOException
253267
extends IOException
254268
{
255269
private Throwable cause;

mail/src/test/java/org/bouncycastle/mail/smime/test/AllTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ public static Test suite()
4949

5050
suite.addTestSuite(NewSMIMESignedTest.class);
5151
suite.addTestSuite(SignedMailValidatorTest.class);
52+
suite.addTestSuite(NewSMIMEAuthEnvelopedTest.class);
5253
suite.addTestSuite(NewSMIMEEnvelopedTest.class);
5354
suite.addTestSuite(SMIMECompressedTest.class);
5455
suite.addTestSuite(SMIMEMiscTest.class);

0 commit comments

Comments
 (0)