();
114 | try {
115 | X509Certificate certificate = getCertificate();
116 | if (null != certificate) {
117 | Principal principal = certificate.getSubjectDN();
118 | if (null != principal) {
119 | String name = principal.getName();
120 | StringTokenizer tokenizer = new StringTokenizer(name, ",");
121 | while (tokenizer.hasMoreTokens()) {
122 | String next = tokenizer.nextToken();
123 | if (next.startsWith("E="))
124 | addresses.add(next.substring(2));
125 | }
126 | }
127 | }
128 | } catch (Exception e) {
129 | }
130 | this.addresses = Collections.unmodifiableList(addresses);
131 | }
132 |
133 | }
134 |
--------------------------------------------------------------------------------
/src/main/java/net/markenwerk/utils/mail/smime/SmimeKeyStore.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 Torsten Krause, Markenwerk GmbH.
3 | *
4 | * This file is part of 'A S/MIME library for JavaMail', hereafter
5 | * called 'this library', identified by the following coordinates:
6 | *
7 | * groupID: net.markenwerk
8 | * artifactId: utils-mail-smime
9 | *
10 | * This library is free software; you can redistribute it and/or
11 | * modify it under the terms of the GNU Lesser General Public
12 | * License as published by the Free Software Foundation; either
13 | * version 3.0 of the License, or (at your option) any later version.
14 | *
15 | * This library is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 | * Lesser General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU Lesser General Public
21 | * License along with this library.
22 | *
23 | * See the LICENSE and NOTICE files in the root directory for further
24 | * information.
25 | */
26 | package net.markenwerk.utils.mail.smime;
27 |
28 | import java.io.InputStream;
29 | import java.security.KeyStore;
30 | import java.security.KeyStoreException;
31 | import java.security.PrivateKey;
32 | import java.security.cert.Certificate;
33 | import java.security.cert.X509Certificate;
34 | import java.util.Collections;
35 | import java.util.Enumeration;
36 | import java.util.HashSet;
37 | import java.util.Set;
38 |
39 | /**
40 | * A wrapper around a {@link KeyStore} that can be initialized with a PKCS12
41 | * keystore and is used to obtain {@link SmimeKey SmimeKeys}.
42 | *
43 | * @author Allen Petersen (akp at sourceforge dot net)
44 | * @author Torsten Krause (tk at markenwerk dot net)
45 | * @since 1.0.0
46 | */
47 | public class SmimeKeyStore {
48 |
49 | private KeyStore keyStore = null;
50 |
51 | /**
52 | * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from a
53 | * the given input stream.
54 | *
55 | *
56 | * The character array holding the password is overwritten with {@code 0s}
57 | * after it has been used.
58 | *
59 | * @param stream
60 | * The {@link InputStream} to read the PKCS12 keystore from.
61 | * @param password
62 | * The password to unlock the PKCS12 keystore with.
63 | */
64 | public SmimeKeyStore(InputStream stream, char[] password) {
65 | this(stream, password, true);
66 | }
67 |
68 | /**
69 | * Creates a new {@code SmimeKeyStore} by loading a PKCS12 keystore from a
70 | * the given input stream.
71 | *
72 | *
73 | * If {@code discardPassword} is set to {@code true}, the character array
74 | * holding the password is overwritten with {@code 0s} after it has been
75 | * used.
76 | *
77 | * @param stream
78 | * The {@link InputStream} to read the PKCS12 keystore from.
79 | * @param password
80 | * The password to unlock the PKCS12 keystore with.
81 | * @param discardPassword
82 | * Whether to overwrite the {@code char[]} holding the password
83 | * after it has been used.
84 | */
85 | public SmimeKeyStore(InputStream stream, char[] password, boolean discardPassword) {
86 | try {
87 | keyStore = KeyStore.getInstance("PKCS12", "BC");
88 | keyStore.load(stream, password);
89 | } catch (Exception e) {
90 | throw new SmimeException("Couldn't initialize SmimeKeyStore", e);
91 | } finally {
92 | if (discardPassword) {
93 | overwrite(password);
94 | }
95 | }
96 | }
97 |
98 | private void overwrite(char[] password) {
99 | if (null != password) {
100 | for (int i = 0, n = password.length; i < n; i++) {
101 | password[i] = 0;
102 | }
103 | }
104 | }
105 |
106 | /**
107 | * Returns the number of entries in the underlying PKCS12 keystore.
108 | *
109 | * @return The number of entries in the underlying {@link KeyStore}.
110 | *
111 | */
112 | public int size() {
113 | try {
114 | return keyStore.size();
115 | } catch (KeyStoreException e) {
116 | throw new SmimeException("Couldn't retrieve the number of entries from SmimeKeyStore", e);
117 | }
118 | }
119 |
120 | /**
121 | * Returns the S/MIME key associated with the given alias, using the given
122 | * password to recover it.
123 | *
124 | *
125 | * The character array holding the password is overwritten with {@code 0s}
126 | * after it has been used.
127 | *
128 | * @param alias
129 | * The alias.
130 | * @param password
131 | * The password to unlock the {@link PrivateKey} keystore with.
132 | *
133 | * @return The requested {@link SmimeKey}, or null if the given alias does
134 | * not exist or does not identify a private key entry.
135 | */
136 | public SmimeKey getPrivateKey(String alias, char[] password) {
137 | return getPrivateKey(alias, password, true);
138 | }
139 |
140 | /**
141 | * Returns the S/MIME key associated with the given alias, using the given
142 | * password to recover it.
143 | *
144 | *
145 | * If {@code discardPassword} is set to {@code true}, the character array
146 | * holding the password is overwritten with {@code 0s} after it has been
147 | * used.
148 | *
149 | * @param alias
150 | * The alias.
151 | * @param password
152 | * The password to unlock the {@link PrivateKey} keystore with.
153 | * @param discardPassword
154 | * Whether to overwrite the {@code char[]} holding the password
155 | * after it has been used.
156 | *
157 | * @return The requested {@link SmimeKey}, or null if the given alias does
158 | * not exist or does not identify a private key entry.
159 | */
160 | public SmimeKey getPrivateKey(String alias, char[] password, boolean discardPassword) {
161 | try {
162 | if (containsPrivateKeyAlias(alias)) {
163 | PrivateKey privateKey = (PrivateKey) keyStore.getKey(alias, password);
164 | Certificate[] certificateChain = keyStore.getCertificateChain(alias);
165 | return new SmimeKey(privateKey, copy(certificateChain));
166 | }
167 | return null;
168 | } catch (Exception e) {
169 | throw new SmimeException("Couldn't recover SmimeKey from SmimeKeyStore", e);
170 | } finally {
171 | if (discardPassword) {
172 | overwrite(password);
173 | }
174 | }
175 | }
176 |
177 | private X509Certificate[] copy(Certificate[] certificateChain) {
178 | X509Certificate[] x509certificateChain = new X509Certificate[certificateChain.length];
179 | for (int i = 0, n = certificateChain.length; i < n; i++) {
180 | x509certificateChain[i] = (X509Certificate) certificateChain[i];
181 | }
182 | return x509certificateChain;
183 | }
184 |
185 | /**
186 | * Returns a set containing all aliases listed in the PKCS12 keystore.
187 | *
188 | * @return A {@link Collections#unmodifiableSet(Set) unmodifiable set} of
189 | * aliases.
190 | */
191 | public Set getPrivateKeyAliases() {
192 | try {
193 | Enumeration aliases = keyStore.aliases();
194 | Set aliasSet = new HashSet();
195 | while (aliases.hasMoreElements()) {
196 | String alias = aliases.nextElement();
197 | if (keyStore.isKeyEntry(alias))
198 | aliasSet.add(alias);
199 | }
200 | return Collections.unmodifiableSet(aliasSet);
201 | } catch (Exception e) {
202 | throw new SmimeException("Couldn't recover aliases from SmimeKeyStore", e);
203 | }
204 | }
205 |
206 | /**
207 | * Checks if the given alias exists in the PKCS12 keystore.
208 | *
209 | * @param alias
210 | * The alias to look for.
211 | *
212 | * @return {@code true} if the alias exists, {@code false} otherwise.
213 | */
214 | public boolean containsPrivateKeyAlias(String alias) {
215 | try {
216 | return keyStore.isKeyEntry(alias);
217 | } catch (Exception e) {
218 | throw new SmimeException("Couldn't recover aliases from SmimeKeyStore", e);
219 | }
220 | }
221 |
222 | }
223 |
--------------------------------------------------------------------------------
/src/main/java/net/markenwerk/utils/mail/smime/SmimeState.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 Torsten Krause, Markenwerk GmbH.
3 | *
4 | * This file is part of 'A S/MIME library for JavaMail', hereafter
5 | * called 'this library', identified by the following coordinates:
6 | *
7 | * groupID: net.markenwerk
8 | * artifactId: utils-mail-smime
9 | *
10 | * This library is free software; you can redistribute it and/or
11 | * modify it under the terms of the GNU Lesser General Public
12 | * License as published by the Free Software Foundation; either
13 | * version 3.0 of the License, or (at your option) any later version.
14 | *
15 | * This library is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 | * Lesser General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU Lesser General Public
21 | * License along with this library.
22 | *
23 | * See the LICENSE and NOTICE files in the root directory for further
24 | * information.
25 | */
26 | package net.markenwerk.utils.mail.smime;
27 |
28 | import javax.mail.internet.MimeMultipart;
29 | import javax.mail.internet.MimePart;
30 |
31 | /**
32 | * The {@code SmimeState} of a {@link MimePart} or {@link MimeMultipart} is
33 | * derived from the corresponding content type and can be obtained with
34 | * {@link SmimeUtil#checkSignature(MimePart) checkSignature()};
35 | *
36 | * @author Torsten Krause (tk at markenwerk dot net)
37 | * @since 1.0.0
38 | */
39 | public enum SmimeState {
40 |
41 | /**
42 | * Indicates that the {@link MimePart} or {@link MimeMultipart} is S/MIME
43 | * encrypted.
44 | */
45 | ENCRYPTED,
46 |
47 | /**
48 | * Indicates that the {@link MimePart} or {@link MimeMultipart} is S/MIME
49 | * signed.
50 | */
51 | SIGNED,
52 |
53 | /**
54 | * Indicates that the {@link MimePart} or {@link MimeMultipart} is neither
55 | * S/MIME encrypted nor S/MIME signed.
56 | */
57 | NEITHER;
58 |
59 | }
60 |
--------------------------------------------------------------------------------
/src/main/java/net/markenwerk/utils/mail/smime/SmimeUtil.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 Torsten Krause, Markenwerk GmbH.
3 | *
4 | * This file is part of 'A S/MIME library for JavaMail', hereafter
5 | * called 'this library', identified by the following coordinates:
6 | *
7 | * groupID: net.markenwerk
8 | * artifactId: utils-mail-smime
9 | *
10 | * This library is free software; you can redistribute it and/or
11 | * modify it under the terms of the GNU Lesser General Public
12 | * License as published by the Free Software Foundation; either
13 | * version 3.0 of the License, or (at your option) any later version.
14 | *
15 | * This library is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 | * Lesser General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU Lesser General Public
21 | * License along with this library.
22 | *
23 | * See the LICENSE and NOTICE files in the root directory for further
24 | * information.
25 | */
26 | package net.markenwerk.utils.mail.smime;
27 |
28 | import java.io.IOException;
29 | import java.math.BigInteger;
30 | import java.security.GeneralSecurityException;
31 | import java.security.PrivateKey;
32 | import java.security.PublicKey;
33 | import java.security.Security;
34 | import java.security.cert.Certificate;
35 | import java.security.cert.CertificateEncodingException;
36 | import java.security.cert.CertificateException;
37 | import java.security.cert.X509Certificate;
38 | import java.util.ArrayList;
39 | import java.util.Arrays;
40 | import java.util.Enumeration;
41 | import java.util.Iterator;
42 | import java.util.List;
43 |
44 | import javax.activation.CommandMap;
45 | import javax.activation.MailcapCommandMap;
46 | import javax.mail.Header;
47 | import javax.mail.MessagingException;
48 | import javax.mail.Multipart;
49 | import javax.mail.Session;
50 | import javax.mail.internet.ContentType;
51 | import javax.mail.internet.MimeBodyPart;
52 | import javax.mail.internet.MimeMessage;
53 | import javax.mail.internet.MimeMultipart;
54 | import javax.mail.internet.MimePart;
55 |
56 | import org.bouncycastle.asn1.ASN1EncodableVector;
57 | import org.bouncycastle.asn1.cms.AttributeTable;
58 | import org.bouncycastle.asn1.cms.IssuerAndSerialNumber;
59 | import org.bouncycastle.asn1.smime.SMIMECapabilitiesAttribute;
60 | import org.bouncycastle.asn1.smime.SMIMECapability;
61 | import org.bouncycastle.asn1.smime.SMIMECapabilityVector;
62 | import org.bouncycastle.asn1.smime.SMIMEEncryptionKeyPreferenceAttribute;
63 | import org.bouncycastle.asn1.x500.X500Name;
64 | import org.bouncycastle.cert.X509CertificateHolder;
65 | import org.bouncycastle.cert.jcajce.JcaCertStore;
66 | import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
67 | import org.bouncycastle.cms.CMSAlgorithm;
68 | import org.bouncycastle.cms.CMSException;
69 | import org.bouncycastle.cms.RecipientInformation;
70 | import org.bouncycastle.cms.RecipientInformationStore;
71 | import org.bouncycastle.cms.SignerId;
72 | import org.bouncycastle.cms.SignerInfoGenerator;
73 | import org.bouncycastle.cms.SignerInformation;
74 | import org.bouncycastle.cms.SignerInformationVerifier;
75 | import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoGeneratorBuilder;
76 | import org.bouncycastle.cms.jcajce.JcaSimpleSignerInfoVerifierBuilder;
77 | import org.bouncycastle.cms.jcajce.JceCMSContentEncryptorBuilder;
78 | import org.bouncycastle.cms.jcajce.JceKeyTransEnvelopedRecipient;
79 | import org.bouncycastle.cms.jcajce.JceKeyTransRecipient;
80 | import org.bouncycastle.cms.jcajce.JceKeyTransRecipientId;
81 | import org.bouncycastle.cms.jcajce.JceKeyTransRecipientInfoGenerator;
82 | import org.bouncycastle.jce.provider.BouncyCastleProvider;
83 | import org.bouncycastle.mail.smime.SMIMEEnveloped;
84 | import org.bouncycastle.mail.smime.SMIMEEnvelopedGenerator;
85 | import org.bouncycastle.mail.smime.SMIMESigned;
86 | import org.bouncycastle.mail.smime.SMIMESignedGenerator;
87 | import org.bouncycastle.mail.smime.SMIMEUtil;
88 | import org.bouncycastle.operator.OperatorCreationException;
89 | import org.bouncycastle.operator.OutputEncryptor;
90 | import org.bouncycastle.util.Store;
91 |
92 | /**
93 | * Utilities for handling S/MIME specific operations on MIME messages from
94 | * JavaMail.
95 | *
96 | * @author Allen Petersen (akp at sourceforge dot net)
97 | * @author Torsten Krause (tk at markenwerk dot net)
98 | * @since 1.0.0
99 | */
100 | public final class SmimeUtil {
101 |
102 | static {
103 | if (null == Security.getProvider(BouncyCastleProvider.PROVIDER_NAME)) {
104 | Security.addProvider(new BouncyCastleProvider());
105 | updateMailcapCommandMap();
106 | }
107 | }
108 |
109 | private SmimeUtil() {
110 | }
111 |
112 | private static void updateMailcapCommandMap() {
113 | MailcapCommandMap map = (MailcapCommandMap) CommandMap.getDefaultCommandMap();
114 | map.addMailcap("application/pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_signature");
115 | map.addMailcap("application/pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.pkcs7_mime");
116 | map.addMailcap("application/x-pkcs7-signature;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_signature");
117 | map.addMailcap("application/x-pkcs7-mime;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.x_pkcs7_mime");
118 | map.addMailcap("multipart/signed;;x-java-content-handler=org.bouncycastle.mail.smime.handlers.multipart_signed");
119 | CommandMap.setDefaultCommandMap(map);
120 | }
121 |
122 | /**
123 | * Encrypts a MIME message and yields a new S/MIME encrypted MIME message.
124 | *
125 | * @param session
126 | * The {@link Session} that is used in conjunction with the
127 | * original {@link MimeMessage}.
128 | * @param mimeMessage
129 | * The original {@link MimeMessage} to be encrypted.
130 | * @param certificate
131 | * The {@link X509Certificate} used to obtain the
132 | * {@link PublicKey} to encrypt the original message with.
133 | * @return The new S/MIME encrypted {@link MimeMessage}.
134 | */
135 | public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate) {
136 | try {
137 | MimeMessage encryptedMimeMessage = new MimeMessage(session);
138 | copyHeaders(mimeMessage, encryptedMimeMessage);
139 |
140 | SMIMEEnvelopedGenerator generator = prepareGenerator(certificate);
141 | OutputEncryptor encryptor = prepareEncryptor();
142 |
143 | MimeBodyPart encryptedMimeBodyPart = generator.generate(mimeMessage, encryptor);
144 | copyContent(encryptedMimeBodyPart, encryptedMimeMessage);
145 | copyHeaders(encryptedMimeBodyPart, encryptedMimeMessage);
146 | encryptedMimeMessage.saveChanges();
147 | return encryptedMimeMessage;
148 | } catch (Exception e) {
149 | throw handledException(e);
150 | }
151 | }
152 |
153 | /**
154 | * Encrypts a MIME body part and yields a new S/MIME encrypted MIME body
155 | * part.
156 | *
157 | * @param mimeBodyPart
158 | * The original {@link MimeBodyPart} to be encrypted.
159 | * @param certificate
160 | * The {@link X509Certificate} used to obtain the
161 | * {@link PublicKey} to encrypt the original body part with.
162 | * @return The new S/MIME encrypted {@link MimeBodyPart}.
163 | */
164 | public static MimeBodyPart encrypt(MimeBodyPart mimeBodyPart, X509Certificate certificate) {
165 | try {
166 | SMIMEEnvelopedGenerator generator = prepareGenerator(certificate);
167 | OutputEncryptor encryptor = prepareEncryptor();
168 |
169 | MimeBodyPart encryptedMimeBodyPart = generator.generate(mimeBodyPart, encryptor);
170 | return encryptedMimeBodyPart;
171 |
172 | } catch (Exception e) {
173 | throw handledException(e);
174 | }
175 | }
176 |
177 | private static void copyHeaders(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException {
178 | @SuppressWarnings("unchecked")
179 | Enumeration headers = fromBodyPart.getAllHeaders();
180 | copyHeaders(headers, toMessage);
181 | }
182 |
183 | private static void copyHeaders(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException {
184 | @SuppressWarnings("unchecked")
185 | Enumeration headers = fromMessage.getAllHeaders();
186 | copyHeaders(headers, toMessage);
187 | }
188 |
189 | private static void copyHeaders(Enumeration headers, MimeMessage toMessage) throws MessagingException {
190 | while (headers.hasMoreElements()) {
191 | Header header = headers.nextElement();
192 | toMessage.setHeader(header.getName(), header.getValue());
193 | }
194 | }
195 |
196 | private static SMIMEEnvelopedGenerator prepareGenerator(X509Certificate certificate)
197 | throws CertificateEncodingException {
198 | JceKeyTransRecipientInfoGenerator infoGenerator = new JceKeyTransRecipientInfoGenerator(certificate);
199 | infoGenerator.setProvider(BouncyCastleProvider.PROVIDER_NAME);
200 | SMIMEEnvelopedGenerator generator = new SMIMEEnvelopedGenerator();
201 | generator.addRecipientInfoGenerator(infoGenerator);
202 | return generator;
203 | }
204 |
205 | private static OutputEncryptor prepareEncryptor() throws CMSException {
206 | return new JceCMSContentEncryptorBuilder(CMSAlgorithm.DES_EDE3_CBC).setProvider(
207 | BouncyCastleProvider.PROVIDER_NAME).build();
208 | }
209 |
210 | /**
211 | * Decrypts a S/MIME encrypted MIME message and yields a new MIME message.
212 | *
213 | * @param session
214 | * The {@link Session} that is used in conjunction with the
215 | * encrypted {@link MimeMessage}.
216 | * @param mimeMessage
217 | * The encrypted {@link MimeMessage} to be decrypted.
218 | * @param smimeKey
219 | * The {@link SmimeKey} used to obtain the {@link PrivateKey} to
220 | * decrypt the encrypted message with.
221 | * @return The new S/MIME decrypted {@link MimeMessage}.
222 | */
223 | public static MimeMessage decrypt(Session session, MimeMessage mimeMessage, SmimeKey smimeKey) {
224 | try {
225 | byte[] content = decryptContent(new SMIMEEnveloped(mimeMessage), smimeKey);
226 | MimeBodyPart mimeBodyPart = SMIMEUtil.toMimeBodyPart(content);
227 |
228 | MimeMessage decryptedMessage = new MimeMessage(session);
229 | copyHeaderLines(mimeMessage, decryptedMessage);
230 | copyContent(mimeBodyPart, decryptedMessage);
231 | decryptedMessage.setHeader("Content-Type", mimeBodyPart.getContentType());
232 | return decryptedMessage;
233 |
234 | } catch (Exception e) {
235 | throw handledException(e);
236 | }
237 | }
238 |
239 | /**
240 | * Decrypts a S/MIME encrypted MIME body part and yields a new MIME body
241 | * part.
242 | *
243 | * @param mimeBodyPart
244 | * The encrypted {@link MimeBodyPart} to be decrypted.
245 | * @param smimeKey
246 | * The {@link SmimeKey} used to obtain the {@link PrivateKey} to
247 | * decrypt the encrypted body part with.
248 | * @return The new S/MIME decrypted {@link MimeBodyPart}.
249 | */
250 | public static MimeBodyPart decrypt(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) {
251 | try {
252 | return SMIMEUtil.toMimeBodyPart(decryptContent(new SMIMEEnveloped(mimeBodyPart), smimeKey));
253 | } catch (Exception e) {
254 | throw handledException(e);
255 | }
256 |
257 | }
258 |
259 | /**
260 | * Decrypts a S/MIME encrypted MIME multipart and yields a new MIME body
261 | * part.
262 | *
263 | * @param mimeMultipart
264 | * The encrypted {@link MimeMultipart} to be decrypted.
265 | * @param smimeKey
266 | * The {@link SmimeKey} used to obtain the {@link PrivateKey} to
267 | * decrypt the encrypted multipart with.
268 | * @return The new S/MIME decrypted {@link MimeBodyPart}.
269 | */
270 | public static MimeBodyPart decrypt(MimeMultipart mimeMultipart, SmimeKey smimeKey) {
271 | try {
272 | MimeBodyPart mimeBodyPart = new MimeBodyPart();
273 | mimeBodyPart.setContent(mimeMultipart);
274 | mimeBodyPart.setHeader("Content-Type", mimeMultipart.getContentType());
275 | return decrypt(mimeBodyPart, smimeKey);
276 | } catch (Exception e) {
277 | throw handledException(e);
278 | }
279 | }
280 |
281 | private static byte[] decryptContent(SMIMEEnveloped smimeEnveloped, SmimeKey smimeKey) throws MessagingException,
282 | CMSException {
283 | X509Certificate certificate = smimeKey.getCertificate();
284 | PrivateKey privateKey = smimeKey.getPrivateKey();
285 |
286 | RecipientInformationStore recipients = smimeEnveloped.getRecipientInfos();
287 | RecipientInformation recipient = recipients.get(new JceKeyTransRecipientId(certificate));
288 |
289 | if (null == recipient) {
290 | throw new MessagingException("no recipient");
291 | }
292 |
293 | JceKeyTransRecipient transportRecipient = new JceKeyTransEnvelopedRecipient(privateKey);
294 | transportRecipient.setProvider(BouncyCastleProvider.PROVIDER_NAME);
295 | return recipient.getContent(transportRecipient);
296 | }
297 |
298 | private static void copyHeaderLines(MimeMessage fromMessage, MimeMessage toMessage) throws MessagingException {
299 | @SuppressWarnings("unchecked")
300 | Enumeration headerLines = fromMessage.getAllHeaderLines();
301 | while (headerLines.hasMoreElements()) {
302 | String nextElement = headerLines.nextElement();
303 | toMessage.addHeaderLine(nextElement);
304 | }
305 | }
306 |
307 | private static void copyContent(MimeBodyPart fromBodyPart, MimeMessage toMessage) throws MessagingException,
308 | IOException {
309 | toMessage.setContent(fromBodyPart.getContent(), fromBodyPart.getContentType());
310 | }
311 |
312 | /**
313 | * Signs a MIME body part and yields a new S/MIME signed MIME body part.
314 | *
315 | * @param mimeBodyPart
316 | * The original {@link MimeBodyPart} to be signed.
317 | * @param smimeKey
318 | * The {@link SmimeKey} used to obtain the {@link PrivateKey} to
319 | * sign the original body part with.
320 | * @return The new S/MIME signed {@link MimeBodyPart}.
321 | */
322 | public static MimeBodyPart sign(MimeBodyPart mimeBodyPart, SmimeKey smimeKey) {
323 | try {
324 | SMIMESignedGenerator generator = getGenerator(smimeKey);
325 | MimeMultipart signedMimeMultipart = generator.generate(MimeUtil.canonicalize(mimeBodyPart));
326 | MimeBodyPart signedMimeBodyPart = new MimeBodyPart();
327 | signedMimeBodyPart.setContent(signedMimeMultipart);
328 | return signedMimeBodyPart;
329 |
330 | } catch (Exception e) {
331 | throw handledException(e);
332 | }
333 |
334 | }
335 |
336 | private static SMIMESignedGenerator getGenerator(SmimeKey smimeKey) throws CertificateEncodingException,
337 | OperatorCreationException {
338 | SMIMESignedGenerator generator = new SMIMESignedGenerator();
339 | generator.addCertificates(getCertificateStore(smimeKey));
340 | generator.addSignerInfoGenerator(getInfoGenerator(smimeKey));
341 | return generator;
342 | }
343 |
344 | private static SignerInfoGenerator getInfoGenerator(SmimeKey smimeKey) throws OperatorCreationException,
345 | CertificateEncodingException {
346 | JcaSimpleSignerInfoGeneratorBuilder builder = new JcaSimpleSignerInfoGeneratorBuilder();
347 | builder.setSignedAttributeGenerator(new AttributeTable(getSignedAttributes(smimeKey)));
348 | builder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
349 |
350 | PrivateKey privateKey = smimeKey.getPrivateKey();
351 | X509Certificate certificate = smimeKey.getCertificate();
352 | SignerInfoGenerator infoGenerator = builder.build("SHA256withRSA", privateKey, certificate);
353 | return infoGenerator;
354 | }
355 |
356 | private static ASN1EncodableVector getSignedAttributes(SmimeKey smimeKey) {
357 | ASN1EncodableVector signedAttributes = new ASN1EncodableVector();
358 | IssuerAndSerialNumber issuerAndSerialNumber = getIssuerAndSerialNumber(smimeKey);
359 | signedAttributes.add(new SMIMEEncryptionKeyPreferenceAttribute(issuerAndSerialNumber));
360 | signedAttributes.add(new SMIMECapabilitiesAttribute(getCapabilityVector()));
361 | return signedAttributes;
362 | }
363 |
364 | private static SMIMECapabilityVector getCapabilityVector() {
365 | SMIMECapabilityVector capabilityVector = new SMIMECapabilityVector();
366 | capabilityVector.addCapability(SMIMECapability.dES_EDE3_CBC);
367 | capabilityVector.addCapability(SMIMECapability.rC2_CBC, 128);
368 | capabilityVector.addCapability(SMIMECapability.dES_CBC);
369 | return capabilityVector;
370 | }
371 |
372 | private static IssuerAndSerialNumber getIssuerAndSerialNumber(SmimeKey smimeKey) {
373 | X509Certificate certificate = smimeKey.getCertificate();
374 | BigInteger serialNumber = certificate.getSerialNumber();
375 | X500Name issuerName = new X500Name(certificate.getIssuerDN().getName());
376 | IssuerAndSerialNumber issuerAndSerialNumber = new IssuerAndSerialNumber(issuerName, serialNumber);
377 | return issuerAndSerialNumber;
378 | }
379 |
380 | private static JcaCertStore getCertificateStore(SmimeKey smimeKey) throws CertificateEncodingException {
381 | Certificate[] certificateChain = smimeKey.getCertificateChain();
382 | X509Certificate certificate = smimeKey.getCertificate();
383 |
384 | List certificateList = null;
385 | if (certificateChain != null && certificateChain.length > 0) {
386 | certificateList = Arrays.asList(certificateChain);
387 | } else {
388 | certificateList = new ArrayList();
389 | certificateList.add(certificate);
390 | }
391 | return new JcaCertStore(certificateList);
392 | }
393 |
394 | /**
395 | * Signs a MIME message and yields a new S/MIME signed MIME message.
396 | *
397 | * @param session
398 | * The {@link Session} that is used in conjunction with the
399 | * original {@link MimeMessage}.
400 | * @param mimeMessage
401 | * The original {@link MimeMessage} to be signed.
402 | * @param smimeKey
403 | * The {@link SmimeKey} used to obtain the {@link PrivateKey} to
404 | * sign the original message with.
405 | * @return The new S/MIME signed {@link MimeMessage}.
406 | */
407 | public static MimeMessage sign(Session session, MimeMessage mimeMessage, SmimeKey smimeKey) {
408 | try {
409 | MimeMessage signedMessage = new MimeMessage(session);
410 | copyHeaderLines(mimeMessage, signedMessage);
411 | copyContent(sign(extractMimeBodyPart(mimeMessage), smimeKey), signedMessage);
412 | return signedMessage;
413 | } catch (Exception e) {
414 | throw handledException(e);
415 | }
416 | }
417 |
418 | private static MimeBodyPart extractMimeBodyPart(MimeMessage mimeMessage) throws IOException, MessagingException {
419 | Object content = mimeMessage.getContent();
420 | UpdatableMimeBodyPart updateableMimeBodyPart = new UpdatableMimeBodyPart();
421 | if (content instanceof Multipart) {
422 | updateableMimeBodyPart.setContent((Multipart) content);
423 | } else {
424 | updateableMimeBodyPart.setContent(content, mimeMessage.getDataHandler().getContentType());
425 | }
426 | updateableMimeBodyPart.updateHeaders();
427 | return updateableMimeBodyPart;
428 | }
429 |
430 | /**
431 | * Checks the signature on a S/MIME signed MIME multipart.
432 | *
433 | * @param mimeMultipart
434 | * The {@link MimeMultipart} to be checked.
435 | * @return {@code true} if the multipart is correctly signed, {@code false}
436 | * otherwise.
437 | */
438 | public static boolean checkSignature(MimeMultipart mimeMultipart) {
439 | try {
440 | return checkSignature(new SMIMESigned(mimeMultipart));
441 | } catch (Exception e) {
442 | throw handledException(e);
443 | }
444 | }
445 |
446 | /**
447 | * Checks the signature on a S/MIME signed MIME part (i.e. MIME message).
448 | *
449 | * @param mimePart
450 | * The {@link MimePart} to be checked.
451 | * @return {@code true} if the part is correctly signed, {@code false}
452 | * otherwise.
453 | */
454 | public static boolean checkSignature(MimePart mimePart) {
455 | try {
456 | if (mimePart.isMimeType("multipart/signed")) {
457 | return checkSignature(new SMIMESigned((MimeMultipart) mimePart.getContent()));
458 | } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) {
459 | return checkSignature(new SMIMESigned(mimePart));
460 | } else {
461 | throw new SmimeException("Message not signed");
462 | }
463 | } catch (Exception e) {
464 | throw handledException(e);
465 | }
466 | }
467 |
468 | /**
469 | * Checks a SMIMESigned to make sure that the signature matches.
470 | */
471 | private static boolean checkSignature(SMIMESigned smimeSigned) throws MessagingException, IOException,
472 | GeneralSecurityException {
473 | try {
474 | boolean returnValue = true;
475 |
476 | @SuppressWarnings("rawtypes")
477 | Store certificates = smimeSigned.getCertificates();
478 | Iterator signerInformations = smimeSigned.getSignerInfos().getSigners().iterator();
479 |
480 | while (returnValue && signerInformations.hasNext()) {
481 | SignerInformation signerInformation = signerInformations.next();
482 | X509Certificate certificate = getCertificate(certificates, signerInformation.getSID());
483 | SignerInformationVerifier verifier = getVerifier(certificate);
484 | if (!signerInformation.verify(verifier)) {
485 | returnValue = false;
486 | }
487 | }
488 | return returnValue;
489 |
490 | } catch (Exception e) {
491 | throw handledException(e);
492 | }
493 | }
494 |
495 | private static X509Certificate getCertificate(@SuppressWarnings("rawtypes") Store certificates, SignerId signerId)
496 | throws CertificateException {
497 | @SuppressWarnings({ "unchecked" })
498 | X509CertificateHolder certificateHolder = (X509CertificateHolder) certificates.getMatches(signerId).iterator()
499 | .next();
500 | JcaX509CertificateConverter certificateConverter = new JcaX509CertificateConverter();
501 | certificateConverter.setProvider(BouncyCastleProvider.PROVIDER_NAME);
502 | return certificateConverter.getCertificate(certificateHolder);
503 | }
504 |
505 | private static SignerInformationVerifier getVerifier(X509Certificate certificate) throws OperatorCreationException {
506 | JcaSimpleSignerInfoVerifierBuilder builder = new JcaSimpleSignerInfoVerifierBuilder();
507 | builder.setProvider(BouncyCastleProvider.PROVIDER_NAME);
508 | return builder.build(certificate);
509 | }
510 |
511 | /**
512 | * Returns the signed MIME body part of a S/MIME signed MIME multipart.
513 | *
514 | * @param mimeMultipart
515 | * The {@link MimeMultipart} to be stripped off.
516 | * @return The signed {@link MimeBodyPart} contained in the
517 | * {@link MimeMultipart}.
518 | */
519 | public static MimeBodyPart getSignedContent(MimeMultipart mimeMultipart) {
520 | try {
521 | return new SMIMESigned(mimeMultipart).getContent();
522 | } catch (Exception e) {
523 | throw handledException(e);
524 | }
525 | }
526 |
527 | /**
528 | * Returns the signed MIME body part of a S/MIME signed MIME part (i.e. MIME
529 | * message).
530 | *
531 | * @param mimePart
532 | * The {@link MimePart} to be stripped off.
533 | * @return The signed {@link MimeBodyPart} contained in the {@link MimePart}
534 | * .
535 | */
536 | public static MimeBodyPart getSignedContent(MimePart mimePart) {
537 | try {
538 | if (mimePart.isMimeType("multipart/signed")) {
539 | return new SMIMESigned((MimeMultipart) mimePart.getContent()).getContent();
540 | } else if (mimePart.isMimeType("application/pkcs7-mime") || mimePart.isMimeType("application/x-pkcs7-mime")) {
541 | return new SMIMESigned(mimePart).getContent();
542 | } else {
543 | throw new SmimeException("Message not signed");
544 | }
545 | } catch (Exception e) {
546 | throw handledException(e);
547 | }
548 | }
549 |
550 | /**
551 | * Returns the S/MIME state of a MIME multipart.
552 | *
553 | * @param mimeMultipart
554 | * The {@link MimeMultipart} to be checked.
555 | * @return the {@link SmimeState} of the {@link MimeMultipart}.
556 | */
557 | public static SmimeState getStatus(MimeMultipart mimeMultipart) {
558 | try {
559 | return getStatus(new ContentType(mimeMultipart.getContentType()));
560 | } catch (Exception e) {
561 | throw handledException(e);
562 | }
563 | }
564 |
565 | /**
566 | * Returns the S/MIME state of a MIME part (i.e. MIME message).
567 | *
568 | * @param mimePart
569 | * The {@link MimePart} to be checked.
570 | * @return the {@link SmimeState} of the {@link MimePart}.
571 | */
572 | public static SmimeState getStatus(MimePart mimePart) {
573 | try {
574 | return getStatus(new ContentType(mimePart.getContentType()));
575 | } catch (Exception e) {
576 | throw handledException(e);
577 | }
578 | }
579 |
580 | private static SmimeState getStatus(ContentType contentType) {
581 | try {
582 | if (isSmimeSignatureContentType(contentType)) {
583 | return SmimeState.SIGNED;
584 | } else if (isSmimeEncryptionContenttype(contentType)) {
585 | return SmimeState.ENCRYPTED;
586 | } else {
587 | return SmimeState.NEITHER;
588 | }
589 | } catch (Exception e) {
590 | return SmimeState.NEITHER;
591 | }
592 | }
593 |
594 | private static boolean isSmimeEncryptionContenttype(ContentType contentType) {
595 | String baseContentType = contentType.getBaseType();
596 | return baseContentType.equalsIgnoreCase("application/pkcs7-mime")
597 | || baseContentType.equalsIgnoreCase("application/x-pkcs7-mime");
598 | }
599 |
600 | private static boolean isSmimeSignatureContentType(ContentType contentType) {
601 | String baseContentType = contentType.getBaseType();
602 | return baseContentType.equalsIgnoreCase("multipart/signed")
603 | && isSmimeSignatureProtocoll(contentType.getParameter("protocol"));
604 | }
605 |
606 | private static boolean isSmimeSignatureProtocoll(String protocol) {
607 | return protocol.equalsIgnoreCase("application/pkcs7-signature")
608 | || protocol.equalsIgnoreCase("application/x-pkcs7-signature");
609 | }
610 |
611 | private static SmimeException handledException(Exception e) {
612 | if (e instanceof SmimeException) {
613 | return (SmimeException) e;
614 | }
615 | return new SmimeException(e.getMessage(), e);
616 | }
617 |
618 | }
619 |
--------------------------------------------------------------------------------
/src/main/java/net/markenwerk/utils/mail/smime/UpdatableMimeBodyPart.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (c) 2015 Torsten Krause, Markenwerk GmbH.
3 | *
4 | * This file is part of 'A S/MIME library for JavaMail', hereafter
5 | * called 'this library', identified by the following coordinates:
6 | *
7 | * groupID: net.markenwerk
8 | * artifactId: utils-mail-smime
9 | *
10 | * This library is free software; you can redistribute it and/or
11 | * modify it under the terms of the GNU Lesser General Public
12 | * License as published by the Free Software Foundation; either
13 | * version 3.0 of the License, or (at your option) any later version.
14 | *
15 | * This library is distributed in the hope that it will be useful,
16 | * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
18 | * Lesser General Public License for more details.
19 | *
20 | * You should have received a copy of the GNU Lesser General Public
21 | * License along with this library.
22 | *
23 | * See the LICENSE and NOTICE files in the root directory for further
24 | * information.
25 | */
26 | package net.markenwerk.utils.mail.smime;
27 |
28 | import java.io.InputStream;
29 |
30 | import javax.mail.MessagingException;
31 | import javax.mail.internet.MimeBodyPart;
32 |
33 | /**
34 | * A {@link MimeBodyPart} that exposes the method {@code updateHeaders()} with
35 | * {@code public} visibility.
36 | *
37 | * @author Allen Petersen (akp at sourceforge dot net)
38 | * @author Torsten Krause (tk at markenwerk dot net)
39 | * @since 1.0.0
40 | */
41 | class UpdatableMimeBodyPart extends MimeBodyPart {
42 |
43 | /**
44 | * Create a new {@code UpdatableMimeBodyPart}.
45 | */
46 | public UpdatableMimeBodyPart() {
47 | super();
48 | }
49 |
50 | /**
51 | * Create a new {@code UpdatableMimeBodyPart} by reading and parsing the
52 | * data from the specified input stream.
53 | *
54 | * @param in
55 | * The {@link InputStream} to be read.
56 | * @throws MessagingException
57 | * If the {@code MimeBodyPart} couldn't be read.
58 | */
59 | public UpdatableMimeBodyPart(InputStream in) throws MessagingException {
60 | super(in);
61 | }
62 |
63 | /**
64 | * Calls updateHeaders().
65 | */
66 | public void updateHeaders() throws MessagingException {
67 | super.updateHeaders();
68 | }
69 |
70 | }
71 |
--------------------------------------------------------------------------------