Skip to content

Commit 84c908a

Browse files
committed
#5: Support fixing MessageId
1 parent 2a6c554 commit 84c908a

File tree

4 files changed

+103
-32
lines changed

4 files changed

+103
-32
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package org.simplejavamail.utils.mail.smime;
2+
3+
import jakarta.mail.MessagingException;
4+
import jakarta.mail.Session;
5+
import jakarta.mail.internet.MimeMessage;
6+
import org.jetbrains.annotations.Nullable;
7+
8+
import static java.lang.String.format;
9+
10+
public class SmimeMessageIdFixingMimeMessage extends MimeMessage {
11+
@Nullable
12+
private final String messageId;
13+
14+
public SmimeMessageIdFixingMimeMessage(Session session, @Nullable String messageId) {
15+
super(session);
16+
this.messageId = messageId;
17+
}
18+
19+
@Override
20+
protected void updateMessageID() throws MessagingException {
21+
if (messageId == null || messageId.length() == 0) {
22+
super.updateMessageID();
23+
} else {
24+
setHeader("Message-ID", messageId);
25+
}
26+
}
27+
28+
@Override
29+
public String toString() {
30+
try {
31+
return format("SmimeMimeMessage<id:%s, subject:%s>", super.getMessageID(), super.getSubject());
32+
} catch (MessagingException e) {
33+
throw new IllegalStateException("should not reach here");
34+
}
35+
}
36+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
package org.simplejavamail.utils.mail.smime;
2+
3+
import com.sun.mail.smtp.SMTPMessage;
4+
import jakarta.mail.MessagingException;
5+
import jakarta.mail.Session;
6+
import jakarta.mail.internet.MimeMessage;
7+
import org.jetbrains.annotations.Nullable;
8+
9+
import static java.lang.String.format;
10+
11+
public class SmimeMessageIdFixingSMTPMessage extends SMTPMessage {
12+
@Nullable
13+
private final String messageId;
14+
15+
public SmimeMessageIdFixingSMTPMessage(Session session, @Nullable String messageId) {
16+
super(session);
17+
this.messageId = messageId;
18+
}
19+
20+
@Override
21+
protected void updateMessageID() throws MessagingException {
22+
if (messageId == null || messageId.length() == 0) {
23+
super.updateMessageID();
24+
} else {
25+
setHeader("Message-ID", messageId);
26+
}
27+
}
28+
29+
@Override
30+
public String toString() {
31+
try {
32+
return format("SmimeSMTPMessage<id:%s, subject:%s>", super.getMessageID(), super.getSubject());
33+
} catch (MessagingException e) {
34+
throw new IllegalStateException("should not reach here");
35+
}
36+
}
37+
}

Diff for: src/main/java/org/simplejavamail/utils/mail/smime/SmimeUtil.java

+21-23
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import org.bouncycastle.operator.OutputEncryptor;
3434
import org.bouncycastle.operator.jcajce.JcaAlgorithmParametersConverter;
3535
import org.bouncycastle.util.Store;
36+
import org.jetbrains.annotations.Nullable;
3637

3738
import javax.crypto.spec.OAEPParameterSpec;
3839
import javax.crypto.spec.PSource;
@@ -87,15 +88,14 @@ private static void updateMailcapCommandMap() {
8788
/**
8889
* Encrypts a MIME message and yields a new S/MIME encrypted MIME message.
8990
*
90-
* @param session The {@link Session} that is used in conjunction with the
91-
* original {@link MimeMessage}.
91+
* @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}.
92+
* @param messageId Optional MessageID that should be preserved on the encrypted MimeMessage result.
9293
* @param mimeMessage The original {@link MimeMessage} to be encrypted.
93-
* @param certificate The {@link X509Certificate} used to obtain the
94-
* {@link PublicKey} to encrypt the original message with.
94+
* @param certificate The {@link X509Certificate} used to obtain the {@link PublicKey} to encrypt the original message with.
9595
* @return The new S/MIME encrypted {@link MimeMessage}.
9696
*/
97-
public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate) {
98-
return encrypt(session, mimeMessage, certificate, DEFAULT_KEY_ENCAPSULATION_ALGORITHM, DEFAULT_CIPHER);
97+
public static MimeMessage encrypt(Session session, @Nullable String messageId, MimeMessage mimeMessage, X509Certificate certificate) {
98+
return encrypt(session, mimeMessage, messageId, certificate, DEFAULT_KEY_ENCAPSULATION_ALGORITHM, DEFAULT_CIPHER);
9999
}
100100

101101
/**
@@ -104,16 +104,17 @@ public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509
104104
* @param session The {@link Session} that is used in conjunction with the
105105
* original {@link MimeMessage}.
106106
* @param mimeMessage The original {@link MimeMessage} to be encrypted.
107+
* @param messageId Optional MessageID that should be preserved on the encrypted MimeMessage result.
107108
* @param certificate The {@link X509Certificate} used to obtain the
108109
* {@link PublicKey} to encrypt the original message with.
109110
* @param keyEncapsulationAlgorithm Algorithm used to encapsulate the symmetric encryption key.
110111
* Currently, RSA RSA-OAEP with various SHA digest lengths are supported.
111112
* @param cmsAlgorithm Encryption algorithm for symmetric content encryption.
112113
* @return The new S/MIME encrypted {@link MimeMessage}.
113114
*/
114-
public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, X509Certificate certificate, KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, ASN1ObjectIdentifier cmsAlgorithm) {
115+
public static MimeMessage encrypt(Session session, MimeMessage mimeMessage, @Nullable String messageId, X509Certificate certificate, KeyEncapsulationAlgorithm keyEncapsulationAlgorithm, ASN1ObjectIdentifier cmsAlgorithm) {
115116
try {
116-
MimeMessage encryptedMimeMessage = new MimeMessage(session);
117+
MimeMessage encryptedMimeMessage = new SmimeMessageIdFixingMimeMessage(session, messageId);
117118
copyHeaders(mimeMessage, encryptedMimeMessage);
118119

119120
SMIMEEnvelopedGenerator generator = prepareGenerator(certificate, keyEncapsulationAlgorithm);
@@ -408,34 +409,31 @@ private static JcaCertStore getCertificateStore(SmimeKey smimeKey) throws Certif
408409
/**
409410
* Signs a MIME message and yields a new S/MIME signed MIME message.
410411
*
411-
* @param session The {@link Session} that is used in conjunction with the
412-
* original {@link MimeMessage}.
412+
* @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}.
413+
* @param messageId Optional MessageID that should be preserved on the signed MimeMessage.
413414
* @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed.
414-
* @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to
415-
* sign the original message with.
415+
* @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to sign the original message with.
416416
* @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}.
417417
*/
418-
public static <T extends MimeMessage> T sign(Session session, T mimeMessage, SmimeKey smimeKey) {
419-
return sign(session, mimeMessage, smimeKey, DEFAULT_SIGNATURE_ALGORITHM_NAME);
418+
public static <T extends MimeMessage> T sign(Session session, @Nullable String messageId, T mimeMessage, SmimeKey smimeKey) {
419+
return sign(session, messageId, mimeMessage, smimeKey, DEFAULT_SIGNATURE_ALGORITHM_NAME);
420420
}
421421

422422
/**
423423
* Signs a MIME message and yields a new S/MIME signed MIME message.
424424
*
425-
* @param session The {@link Session} that is used in conjunction with the
426-
* original {@link MimeMessage}.
425+
* @param session The {@link Session} that is used in conjunction with the original {@link MimeMessage}.
426+
* @param messageId Optional MessageID that should be preserved on the signed MimeMessage.
427427
* @param mimeMessage The original {@link MimeMessage} or {@link SMTPMessage} to be signed.
428-
* @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to
429-
* sign the original message with.
430-
* @param algorithmName The name of the signature algorithm to use. Must be an algorithm
431-
* supported by the Bouncy Castle security provider.
428+
* @param smimeKey The {@link SmimeKey} used to obtain the {@link PrivateKey} to sign the original message with.
429+
* @param algorithmName The name of the signature algorithm to use. Must be an algorithm supported by the Bouncy Castle security provider.
432430
* @return The new S/MIME signed {@link MimeMessage} or {@link SMTPMessage}.
433431
*/
434-
public static <T extends MimeMessage> T sign(Session session, T mimeMessage, SmimeKey smimeKey, String algorithmName) {
432+
public static <T extends MimeMessage> T sign(Session session, @Nullable String messageId, T mimeMessage, SmimeKey smimeKey, String algorithmName) {
435433
//noinspection unchecked
436434
return (mimeMessage instanceof SMTPMessage)
437-
? sign(mimeMessage, (T) new SMTPMessage(session), smimeKey, algorithmName)
438-
: sign(mimeMessage, (T) new MimeMessage(session), smimeKey, algorithmName);
435+
? sign(mimeMessage, (T) new SmimeMessageIdFixingSMTPMessage(session, messageId), smimeKey, algorithmName)
436+
: sign(mimeMessage, (T) new SmimeMessageIdFixingMimeMessage(session, messageId), smimeKey, algorithmName);
439437
}
440438

441439
private static <T extends MimeMessage> T sign(T mimeMessage, T signedMessage, SmimeKey smimeKey, String algorithmName) {

Diff for: src/test/java/org/simplejavamail/utils/mail/smime/SmimeUtilTest.java

+9-9
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ private MimeMessage createTestMessage(String from, String to) throws MessagingEx
5454
public void SuccessfullySignAndValidate() throws MessagingException, IOException {
5555
MimeMessage testMessage = createTestMessage("[email protected]", "[email protected]");
5656
SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray());
57-
MimeMessage signedMessage = SmimeUtil.sign(this.mailSession, testMessage, alicesKey);
57+
MimeMessage signedMessage = SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey);
5858
MimeMultipart multipartContent = (MimeMultipart) signedMessage.getContent();
5959
assertThat(SmimeUtil.getStatus(multipartContent)).isEqualTo(SmimeState.SIGNED);
6060
assertThat(SmimeUtil.checkSignature(multipartContent)).isTrue();
@@ -65,8 +65,8 @@ public void SuccessfullyEnvelopeAndDecryptDefault() throws MessagingException {
6565
MimeMessage testMessage = createTestMessage("[email protected]", "[email protected]");
6666
SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray());
6767
X509Certificate alicesCert = alicesKey.getCertificate();
68-
MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession,
69-
SmimeUtil.sign(this.mailSession, testMessage, alicesKey),
68+
MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession, null,
69+
SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey),
7070
alicesCert);
7171
assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED);
7272
MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey);
@@ -79,8 +79,8 @@ public void SuccessfullyEnvelopeAndDecrypt() throws MessagingException {
7979
SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray());
8080
X509Certificate alicesCert = alicesKey.getCertificate();
8181
MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession,
82-
SmimeUtil.sign(this.mailSession, testMessage, alicesKey, SignatureAlgorithmRsaPss),
83-
alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA256, CMSAlgorithm.AES256_CBC);
82+
SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey, SignatureAlgorithmRsaPss),
83+
null, alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA256, CMSAlgorithm.AES256_CBC);
8484
assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED);
8585
MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey);
8686
assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue();
@@ -93,8 +93,8 @@ public void AliceToBoEnvelopeAndDecrypt() throws MessagingException {
9393
SmimeKey bobsKey = this.bobsKeyStore.getPrivateKey("bob", "bob".toCharArray());
9494
X509Certificate bobsCert = bobsKey.getCertificate();
9595
MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession,
96-
SmimeUtil.sign(this.mailSession, testMessage, alicesKey, SignatureAlgorithmRsaPss),
97-
bobsCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA512, CMSAlgorithm.AES256_GCM);
96+
SmimeUtil.sign(this.mailSession, null, testMessage, alicesKey, SignatureAlgorithmRsaPss),
97+
null, bobsCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA512, CMSAlgorithm.AES256_GCM);
9898
assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED);
9999
MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, bobsKey);
100100
assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue();
@@ -107,8 +107,8 @@ public void BobToAliceEnvelopeAndDecrypt() throws MessagingException {
107107
SmimeKey alicesKey = this.alicesKeyStore.getPrivateKey("alice", "alice".toCharArray());
108108
X509Certificate alicesCert = alicesKey.getCertificate();
109109
MimeMessage encryptedMessage = SmimeUtil.encrypt(this.mailSession,
110-
SmimeUtil.sign(this.mailSession, testMessage, bobsKey, SignatureAlgorithmRsaPss),
111-
alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA384, CMSAlgorithm.AES192_CCM);
110+
SmimeUtil.sign(this.mailSession, null, testMessage, bobsKey, SignatureAlgorithmRsaPss),
111+
null, alicesCert, KeyEncapsulationAlgorithm.RSA_OAEP_SHA384, CMSAlgorithm.AES192_CCM);
112112
assertThat(SmimeUtil.getStatus((encryptedMessage))).isEqualTo(SmimeState.ENCRYPTED);
113113
MimeMessage decryptedMessage = SmimeUtil.decrypt(this.mailSession, encryptedMessage, alicesKey);
114114
assertThat(SmimeUtil.checkSignature(decryptedMessage)).isTrue();

0 commit comments

Comments
 (0)