From 1b25fbb2879fb820c05b69a2c744b4464989494e Mon Sep 17 00:00:00 2001 From: bbottema Date: Tue, 1 Jun 2021 00:10:39 +0200 Subject: [PATCH] #320: added byDefault S/MIME signing on the Mailer level. Also, the smime properties are not set on Email anymore but on Mailer. S/MIME signing can be overridden on a per/Email basis. Consolidated all such properties (email validation criteria, S/MIME default signing, DKIM in the future as well) in new mailer level config object called EmailGovernance. --- .../api/email/EmailPopulatingBuilder.java | 8 +- .../org/simplejavamail/api/mailer/Mailer.java | 12 +- .../api/mailer/MailerGenericBuilder.java | 68 +++++++++- .../api/mailer/config/EmailGovernance.java | 36 +++++ .../internal/modules/SMIMEModule.java | 7 +- .../converter/EmailConverter.java | 22 ++- .../mimemessage/MimeMessageProducer.java | 9 +- .../MimeMessageProducerHelper.java | 8 +- .../internal/EmailPopulatingBuilderImpl.java | 12 -- .../simplejavamail/mailer/MailerHelper.java | 7 +- .../mailer/internal/EmailGovernanceImpl.java | 47 +++++++ .../mailer/internal/MailerException.java | 2 + .../internal/MailerGenericBuilderImpl.java | 128 +++++++++++++++++- .../mailer/internal/MailerImpl.java | 22 +-- .../internal/MailerRegularBuilderImpl.java | 4 +- .../mailer/internal/SendMailClosure.java | 12 +- .../simplejavamail/mailer/MailerLiveTest.java | 31 +++++ .../mailer/internal/MailerImplTest.java | 58 +++++--- .../internal/smimesupport/SMIMESupport.java | 13 +- .../resources/pkcs12/smime_keystore.pkcs12 | Bin 0 -> 4418 bytes 20 files changed, 422 insertions(+), 84 deletions(-) create mode 100644 modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/EmailGovernance.java create mode 100644 modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/EmailGovernanceImpl.java create mode 100644 modules/spring-module/src/test/resources/pkcs12/smime_keystore.pkcs12 diff --git a/modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java b/modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java index 36afe4bdd..685c03aa1 100644 --- a/modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java +++ b/modules/core-module/src/main/java/org/simplejavamail/api/email/EmailPopulatingBuilder.java @@ -1,12 +1,12 @@ package org.simplejavamail.api.email; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.internal.clisupport.model.Cli; import org.simplejavamail.api.internal.clisupport.model.CliBuilderApiType; import org.simplejavamail.api.mailer.config.Pkcs12Config; import javax.activation.DataSource; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.mail.Message; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; @@ -1127,10 +1127,12 @@ public interface EmailPopulatingBuilder { * Signs this email with an S/MIME signature, so the receiving client * can verify whether the email content was tampered with. *

- * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}. + * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}.
+ * Note: You can also configure your Mailer instance do sign all emails by default (also has better performance). * * @see S/MIME on Wikipedia * @see Primer on S/MIME + * @see org.simplejavamail.api.mailer.MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config) */ @Cli.ExcludeApi(reason = "delegated method contains CLI compatible arguments") EmailPopulatingBuilder signWithSmime(@NotNull Pkcs12Config pkcs12Config); diff --git a/modules/core-module/src/main/java/org/simplejavamail/api/mailer/Mailer.java b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/Mailer.java index e81ff2828..83729f0aa 100644 --- a/modules/core-module/src/main/java/org/simplejavamail/api/mailer/Mailer.java +++ b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/Mailer.java @@ -2,19 +2,19 @@ import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; import org.hazlewood.connor.bottema.emailaddress.EmailAddressValidator; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.MailException; import org.simplejavamail.api.email.Email; +import org.simplejavamail.api.mailer.config.EmailGovernance; import org.simplejavamail.api.mailer.config.OperationalConfig; import org.simplejavamail.api.mailer.config.ProxyConfig; import org.simplejavamail.api.mailer.config.ServerConfig; import org.simplejavamail.api.mailer.config.TransportStrategy; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.mail.Message; import javax.mail.Session; import javax.mail.Transport; -import java.util.EnumSet; import java.util.concurrent.Future; /** @@ -148,10 +148,8 @@ public interface Mailer { OperationalConfig getOperationalConfig(); /** - * @return The effective validation criteria used for email validation. Returns an empty set if no validation should be done. - * @see MailerGenericBuilder#withEmailAddressCriteria(EnumSet) - * @see EmailAddressCriteria + * @return The effective governance applied to each email (default S/MIME signing, email addresscriteria for validation etc.). */ @NotNull - EnumSet getEmailAddressCriteria(); + EmailGovernance getEmailGovernance(); } diff --git a/modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java index 84b4c3e0b..47da2ae11 100644 --- a/modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java +++ b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/MailerGenericBuilder.java @@ -1,14 +1,17 @@ package org.simplejavamail.api.mailer; import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.internal.clisupport.model.Cli; import org.simplejavamail.api.internal.clisupport.model.CliBuilderApiType; import org.simplejavamail.api.mailer.config.LoadBalancingStrategy; +import org.simplejavamail.api.mailer.config.Pkcs12Config; import org.simplejavamail.api.mailer.config.TransportStrategy; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.mail.Session; +import java.io.File; +import java.io.InputStream; import java.util.EnumSet; import java.util.List; import java.util.Map; @@ -226,6 +229,53 @@ public interface MailerGenericBuilder> { */ T withEmailAddressCriteria(@NotNull EnumSet emailAddressCriteria); + /** + * Signs this all emails by default with an S/MIME signature, so the receiving client + * can verify whether the email content was tampered with. + *

+ * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}. + * + * @see S/MIME on Wikipedia + * @see Primer on S/MIME + * @see org.simplejavamail.api.email.EmailPopulatingBuilder#signWithSmime(Pkcs12Config) + * @see #clearSignByDefaultWithSmime() + */ + @Cli.ExcludeApi(reason = "delegated method contains CLI compatible arguments") + T signByDefaultWithSmime(@NotNull Pkcs12Config pkcs12Config); + + /** + * Delegates to {@link #signByDefaultWithSmime(InputStream, String, String, String)}. + *

+ * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}. + * + * @param pkcs12StoreFile The key store file to use to find the indicated key + * @param storePassword The store's password + * @param keyAlias The name of the certificate in the key store to use + * @param keyPassword The password of the certificate + */ + T signByDefaultWithSmime(@NotNull File pkcs12StoreFile, @NotNull String storePassword, @NotNull String keyAlias, @NotNull String keyPassword); + + /** + * Delegates to {@link #signByDefaultWithSmime(byte[], String, String, String)}. + *

+ * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}. + */ + @Cli.ExcludeApi(reason = "Is duplicate API from CLI point of view") + T signByDefaultWithSmime(@NotNull InputStream pkcs12StoreStream, @NotNull String storePassword, @NotNull String keyAlias, @NotNull String keyPassword); + + /** + * Delegates to {@link #signByDefaultWithSmime(Pkcs12Config)}. + *

+ * Note: this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}. + * + * @param pkcs12StoreData The key store file to use to find the indicated key + * @param storePassword The store's password + * @param keyAlias The name of the certificate in the key store to use + * @param keyPassword The password of the certificate + */ + @Cli.ExcludeApi(reason = "Is duplicate API from CLI point of view") + T signByDefaultWithSmime(@NotNull byte[] pkcs12StoreData, @NotNull String storePassword, @NotNull String keyAlias, @NotNull String keyPassword); + /** * For advanced use cases. *

@@ -580,6 +630,13 @@ public interface MailerGenericBuilder> { */ T clearEmailAddressCriteria(); + /** + * Removes S/MIME signing, so emails won't be signed by default. + * + * @see #signByDefaultWithSmime(Pkcs12Config) + */ + T clearSignByDefaultWithSmime(); + /** * Removes all trusted hosts from the list. * @@ -651,6 +708,13 @@ public interface MailerGenericBuilder> { @Nullable EnumSet getEmailAddressCriteria(); + /** + * @see #signByDefaultWithSmime(Pkcs12Config) + * @see #signByDefaultWithSmime(InputStream, String, String, String) + */ + @Nullable + Pkcs12Config getPkcs12ConfigForSmimeSigning(); + /** * Returns the user set ExecutorService or else null as the default ExecutorService is not created until the {@link org.simplejavamail.api.mailer.config.OperationalConfig} is created for the * new {@link Mailer} instance. diff --git a/modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/EmailGovernance.java b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/EmailGovernance.java new file mode 100644 index 000000000..0f68041b7 --- /dev/null +++ b/modules/core-module/src/main/java/org/simplejavamail/api/mailer/config/EmailGovernance.java @@ -0,0 +1,36 @@ +package org.simplejavamail.api.mailer.config; + +import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.simplejavamail.api.email.EmailPopulatingBuilder; +import org.simplejavamail.api.mailer.MailerGenericBuilder; + +import java.io.InputStream; +import java.util.EnumSet; + +/** + * Governance for all emails being sent through the current {@link org.simplejavamail.api.mailer.Mailer} instance. + *

+ * In simpeler terms: this class represents actions taken or configuration used by default for each individual email sent through the current mailer. For example, you might want to S/MIME sign all + * emails by default. You can do it manually on each email of course, but then the keystore used for this not reused. + */ +public interface EmailGovernance { + + /** + * @return The effective validation criteria used for email validation. Returns an empty set if no validation should be done. + * @see MailerGenericBuilder#withEmailAddressCriteria(EnumSet) + * @see EmailAddressCriteria + */ + @NotNull + EnumSet getEmailAddressCriteria(); + + /** + * @see EmailPopulatingBuilder#signWithSmime(Pkcs12Config) + * @see EmailPopulatingBuilder#signWithSmime(InputStream, String, String, String) + * @see MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config) + * @see MailerGenericBuilder#signByDefaultWithSmime(InputStream, String, String, String) + */ + @Nullable + Pkcs12Config getPkcs12ConfigForSmimeSigning(); +} diff --git a/modules/core-module/src/main/java/org/simplejavamail/internal/modules/SMIMEModule.java b/modules/core-module/src/main/java/org/simplejavamail/internal/modules/SMIMEModule.java index 3f523d2bf..a299169ce 100644 --- a/modules/core-module/src/main/java/org/simplejavamail/internal/modules/SMIMEModule.java +++ b/modules/core-module/src/main/java/org/simplejavamail/internal/modules/SMIMEModule.java @@ -1,5 +1,7 @@ package org.simplejavamail.internal.modules; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.email.AttachmentResource; import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.OriginalSmimeDetails; @@ -9,8 +11,6 @@ import org.simplejavamail.api.internal.smimesupport.model.SmimeDetails; import org.simplejavamail.api.mailer.config.Pkcs12Config; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.mail.Session; import javax.mail.internet.MimeMessage; import javax.mail.internet.MimePart; @@ -70,7 +70,8 @@ public interface SMIMEModule { boolean verifyValidSignature(@NotNull MimeMessage mimeMessage, @NotNull OriginalSmimeDetails messageSmimeDetails); @NotNull - MimeMessage signAndOrEncryptEmail(@NotNull final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails); + MimeMessage signAndOrEncryptEmail(@NotNull final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails, + @Nullable final Pkcs12Config defaultSmimeSigningStore); @NotNull MimeMessage signMessage(@Nullable Session session, @NotNull MimeMessage message, @NotNull Pkcs12Config pkcs12Config); diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/EmailConverter.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/EmailConverter.java index 197055915..d2a78044c 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/EmailConverter.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/EmailConverter.java @@ -428,11 +428,29 @@ public static MimeMessage emailToMimeMessage(@NotNull final Email email) { } /** - * Refer to {@link MimeMessageProducerHelper#produceMimeMessage(Email, Session)}. + * Refer to {@link MimeMessageProducerHelper#produceMimeMessage(Email, Session, Pkcs12Config)}. + */ + public static MimeMessage emailToMimeMessage(@NotNull final Email email, @NotNull final Session session, @NotNull final Pkcs12Config defaultSmimeSigningStore) { + try { + return MimeMessageProducerHelper.produceMimeMessage( + checkNonEmptyArgument(email, "email"), + checkNonEmptyArgument(session, "session"), + checkNonEmptyArgument(defaultSmimeSigningStore, "defaultSmimeSigningStore")); + } catch (UnsupportedEncodingException | MessagingException e) { + // this should never happen, so we don't acknowledge this exception (and simply bubble up) + throw new IllegalStateException(e.getMessage(), e); + } + } + + /** + * Delegates to {@link MimeMessageProducerHelper#produceMimeMessage(Email, Session, Pkcs12Config)} with empty S/MIME signing store. */ public static MimeMessage emailToMimeMessage(@NotNull final Email email, @NotNull final Session session) { try { - return MimeMessageProducerHelper.produceMimeMessage(checkNonEmptyArgument(email, "email"), checkNonEmptyArgument(session, "session")); + return MimeMessageProducerHelper.produceMimeMessage( + checkNonEmptyArgument(email, "email"), + checkNonEmptyArgument(session, "session"), + null); } catch (UnsupportedEncodingException | MessagingException e) { // this should never happen, so we don't acknowledge this exception (and simply bubble up) throw new IllegalStateException(e.getMessage(), e); diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducer.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducer.java index 9fe2bd889..ff35684de 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducer.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducer.java @@ -1,10 +1,11 @@ package org.simplejavamail.converter.internal.mimemessage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.email.Email; +import org.simplejavamail.api.mailer.config.Pkcs12Config; import org.simplejavamail.internal.modules.ModuleLoader; -import org.jetbrains.annotations.NotNull; - import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeMessage; @@ -37,7 +38,7 @@ public abstract class MimeMessageProducer { */ abstract boolean compatibleWithEmail(@NotNull Email email); - final MimeMessage populateMimeMessage(@NotNull final Email email, @NotNull Session session) + final MimeMessage populateMimeMessage(@NotNull final Email email, @NotNull Session session, @Nullable final Pkcs12Config defaultSmimeSigningStore) throws MessagingException, UnsupportedEncodingException { checkArgumentNotEmpty(email, "email is missing"); checkArgumentNotEmpty(session, "session is needed, it cannot be attached later"); @@ -80,7 +81,7 @@ public String toString() { 3. DKIM signing */ if (ModuleLoader.smimeModuleAvailable()) { - message = ModuleLoader.loadSmimeModule().signAndOrEncryptEmail(session, message, email); + message = ModuleLoader.loadSmimeModule().signAndOrEncryptEmail(session, message, email, defaultSmimeSigningStore); } if (!valueNullOrEmpty(email.getDkimSigningDomain())) { diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducerHelper.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducerHelper.java index 136597568..3cd10a3f8 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducerHelper.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/converter/internal/mimemessage/MimeMessageProducerHelper.java @@ -1,8 +1,10 @@ package org.simplejavamail.converter.internal.mimemessage; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.email.Email; +import org.simplejavamail.api.mailer.config.Pkcs12Config; -import org.jetbrains.annotations.NotNull; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeMessage; @@ -34,10 +36,10 @@ public final class MimeMessageProducerHelper { private MimeMessageProducerHelper() { } - public static MimeMessage produceMimeMessage(@NotNull Email email, @NotNull Session session) throws UnsupportedEncodingException, MessagingException { + public static MimeMessage produceMimeMessage(@NotNull Email email, @NotNull Session session, @Nullable final Pkcs12Config defaultSmimeSigningStore) throws UnsupportedEncodingException, MessagingException { for (MimeMessageProducer mimeMessageProducer : mimeMessageProducers) { if (mimeMessageProducer.compatibleWithEmail(email)) { - return mimeMessageProducer.populateMimeMessage(email, session); + return mimeMessageProducer.populateMimeMessage(email, session, defaultSmimeSigningStore); } } throw new IllegalStateException("no compatible MimeMessageProducer found for email"); diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/email/internal/EmailPopulatingBuilderImpl.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/email/internal/EmailPopulatingBuilderImpl.java index 6a887c9ab..06123d4ca 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/email/internal/EmailPopulatingBuilderImpl.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/email/internal/EmailPopulatingBuilderImpl.java @@ -73,10 +73,6 @@ import static org.simplejavamail.config.ConfigLoader.Property.EMBEDDEDIMAGES_DYNAMICRESOLUTION_OUTSIDE_BASE_DIR; import static org.simplejavamail.config.ConfigLoader.Property.EMBEDDEDIMAGES_DYNAMICRESOLUTION_OUTSIDE_BASE_URL; import static org.simplejavamail.config.ConfigLoader.Property.SMIME_ENCRYPTION_CERTIFICATE; -import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEYSTORE; -import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEYSTORE_PASSWORD; -import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEY_ALIAS; -import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEY_PASSWORD; import static org.simplejavamail.config.ConfigLoader.getBooleanProperty; import static org.simplejavamail.config.ConfigLoader.getProperty; import static org.simplejavamail.config.ConfigLoader.getStringProperty; @@ -366,14 +362,6 @@ public class EmailPopulatingBuilderImpl implements InternalEmailPopulatingBuilde if (hasProperty(DEFAULT_SUBJECT)) { withSubject((String) getProperty(DEFAULT_SUBJECT)); } - if (hasProperty(SMIME_SIGNING_KEYSTORE)) { - signWithSmime(Pkcs12Config.builder() - .pkcs12Store(assumeNonNull(getStringProperty(SMIME_SIGNING_KEYSTORE))) - .storePassword(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEYSTORE_PASSWORD), "Keystore password property")) - .keyAlias(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEY_ALIAS), "Key alias property")) - .keyPassword(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEY_PASSWORD), "Key password property")) - .build()); - } if (hasProperty(SMIME_ENCRYPTION_CERTIFICATE)) { encryptWithSmime(assumeNonNull(getStringProperty(SMIME_ENCRYPTION_CERTIFICATE))); } diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/MailerHelper.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/MailerHelper.java index cc8065b6c..9acc4414b 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/MailerHelper.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/MailerHelper.java @@ -8,6 +8,7 @@ import org.simplejavamail.api.email.AttachmentResource; import org.simplejavamail.api.email.Email; import org.simplejavamail.api.email.Recipient; +import org.simplejavamail.api.mailer.config.Pkcs12Config; import org.simplejavamail.internal.modules.ModuleLoader; import org.slf4j.Logger; @@ -139,11 +140,11 @@ public static MimeMessage signMessageWithDKIM(@NotNull final MimeMessage message /** * Depending on the Email configuration, signs and then encrypts message (both steps optional), using the S/MIME module. * - * @see org.simplejavamail.internal.modules.SMIMEModule#signAndOrEncryptEmail(Session, MimeMessage, Email) + * @see org.simplejavamail.internal.modules.SMIMEModule#signAndOrEncryptEmail(Session, MimeMessage, Email, Pkcs12Config) */ @SuppressWarnings("unused") - public static MimeMessage signAndOrEncryptMessageWithSmime(@NotNull final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails) { + public static MimeMessage signAndOrEncryptMessageWithSmime(@NotNull final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails, @Nullable final Pkcs12Config defaultSmimeSigningStore) { return ModuleLoader.loadSmimeModule() - .signAndOrEncryptEmail(session, messageToProtect, emailContainingSmimeDetails); + .signAndOrEncryptEmail(session, messageToProtect, emailContainingSmimeDetails, defaultSmimeSigningStore); } } diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/EmailGovernanceImpl.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/EmailGovernanceImpl.java new file mode 100644 index 000000000..aa80223f6 --- /dev/null +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/EmailGovernanceImpl.java @@ -0,0 +1,47 @@ +package org.simplejavamail.mailer.internal; + +import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.simplejavamail.api.mailer.config.EmailGovernance; +import org.simplejavamail.api.mailer.config.Pkcs12Config; + +import java.util.EnumSet; + +/** + * @see EmailGovernance + */ +class EmailGovernanceImpl implements EmailGovernance { + @NotNull private final EnumSet emailAddressCriteria; + @Nullable private final Pkcs12Config pkcs12ConfigForSmimeSigning; + + EmailGovernanceImpl(@NotNull final EnumSet emailAddressCriteria, @Nullable final Pkcs12Config pkcs12ConfigForSmimeSigning) { + this.emailAddressCriteria = emailAddressCriteria; + this.pkcs12ConfigForSmimeSigning = pkcs12ConfigForSmimeSigning; + } + + @Override + public String toString() { + final StringBuilder sb = new StringBuilder("EmailGovernanceImpl{"); + sb.append("emailAddressCriteria=").append(emailAddressCriteria); + sb.append(", pkcs12ConfigForSmimeSigning=").append(pkcs12ConfigForSmimeSigning); + sb.append('}'); + return sb.toString(); + } + + /** + * @see EmailGovernance#getEmailAddressCriteria() + */ + @Override + public @NotNull EnumSet getEmailAddressCriteria() { + return emailAddressCriteria; + } + + /** + * @see EmailGovernance#getPkcs12ConfigForSmimeSigning() + */ + @Override + public @Nullable Pkcs12Config getPkcs12ConfigForSmimeSigning() { + return pkcs12ConfigForSmimeSigning; + } +} \ No newline at end of file diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerException.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerException.java index 815d6dc5c..aeee4e1c3 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerException.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerException.java @@ -8,6 +8,8 @@ @SuppressWarnings("serial") class MailerException extends MailException { + static final String ERROR_READING_SMIME_FROM_INPUTSTREAM = "Was unable to read S/MIME data from input stream"; + static final String ERROR_READING_FROM_FILE = "Error reading from file: %s"; static final String INVALID_PROXY_SLL_COMBINATION = "Proxy is not supported for SSL connections (this is a limitation by the underlying JavaMail framework)"; static final String GENERIC_ERROR = "Third party error"; static final String INVALID_ENCODING = "Encoding not accepted"; diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java index a4eb24739..d72cd5b9e 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerGenericBuilderImpl.java @@ -1,16 +1,23 @@ package org.simplejavamail.mailer.internal; +import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.mailer.CustomMailer; import org.simplejavamail.api.mailer.MailerGenericBuilder; +import org.simplejavamail.api.mailer.config.EmailGovernance; import org.simplejavamail.api.mailer.config.LoadBalancingStrategy; import org.simplejavamail.api.mailer.config.OperationalConfig; +import org.simplejavamail.api.mailer.config.Pkcs12Config; import org.simplejavamail.api.mailer.config.ProxyConfig; import org.simplejavamail.config.ConfigLoader.Property; import org.simplejavamail.internal.modules.ModuleLoader; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.EnumSet; @@ -21,10 +28,15 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; +import static java.lang.String.format; import static org.simplejavamail.config.ConfigLoader.Property.DEFAULT_CONNECTIONPOOL_CLUSTER_KEY; import static org.simplejavamail.config.ConfigLoader.Property.PROXY_HOST; import static org.simplejavamail.config.ConfigLoader.Property.PROXY_PASSWORD; import static org.simplejavamail.config.ConfigLoader.Property.PROXY_USERNAME; +import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEYSTORE; +import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEYSTORE_PASSWORD; +import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEY_ALIAS; +import static org.simplejavamail.config.ConfigLoader.Property.SMIME_SIGNING_KEY_PASSWORD; import static org.simplejavamail.config.ConfigLoader.getStringProperty; import static org.simplejavamail.config.ConfigLoader.hasProperty; import static org.simplejavamail.config.ConfigLoader.valueOrProperty; @@ -32,8 +44,12 @@ import static org.simplejavamail.config.ConfigLoader.valueOrPropertyAsInteger; import static org.simplejavamail.config.ConfigLoader.valueOrPropertyAsString; import static org.simplejavamail.internal.util.MiscUtil.checkArgumentNotEmpty; +import static org.simplejavamail.internal.util.MiscUtil.readInputStreamToBytes; import static org.simplejavamail.internal.util.MiscUtil.valueNullOrEmpty; import static org.simplejavamail.internal.util.Preconditions.assumeNonNull; +import static org.simplejavamail.internal.util.Preconditions.checkNonEmptyArgument; +import static org.simplejavamail.mailer.internal.MailerException.ERROR_READING_FROM_FILE; +import static org.simplejavamail.mailer.internal.MailerException.ERROR_READING_SMIME_FROM_INPUTSTREAM; /** * @see MailerGenericBuilder @@ -82,13 +98,19 @@ abstract class MailerGenericBuilderImpl> i */ @NotNull private Integer sessionTimeout; - + /** * @see MailerGenericBuilder#withEmailAddressCriteria(EnumSet) */ @NotNull private EnumSet emailAddressCriteria; + /** + * @see MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config) + */ + @Nullable + private Pkcs12Config pkcs12ConfigForSmimeSigning; + /** * @see MailerGenericBuilder#withExecutorService(ExecutorService) */ @@ -216,6 +238,15 @@ abstract class MailerGenericBuilderImpl> i } this.emailAddressCriteria = EmailAddressCriteria.RFC_COMPLIANT.clone(); + + if (hasProperty(SMIME_SIGNING_KEYSTORE)) { + signByDefaultWithSmime(Pkcs12Config.builder() + .pkcs12Store(assumeNonNull(getStringProperty(SMIME_SIGNING_KEYSTORE))) + .storePassword(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEYSTORE_PASSWORD), "Keystore password property")) + .keyAlias(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEY_ALIAS), "Key alias property")) + .keyPassword(checkNonEmptyArgument(getStringProperty(SMIME_SIGNING_KEY_PASSWORD), "Key password property")) + .build()); + } } /** @@ -241,6 +272,13 @@ private void validateProxy() { } } } + + /** + * For internal use. + */ + EmailGovernance buildEmailGovernance() { + return new EmailGovernanceImpl(assumeNonNull(getEmailAddressCriteria()), getPkcs12ConfigForSmimeSigning()); + } /** * For internal use. @@ -369,6 +407,70 @@ public T withEmailAddressCriteria(@NotNull final EnumSet e return (T) this; } + /** + * @param pkcs12StoreFile The file containing the keystore + * @param storePassword The password to get keys from the store + * @param keyAlias The key we need for signing + * @param keyPassword The password for the key + * + * @see MailerGenericBuilder#signByDefaultWithSmime(File, String, String, String) + */ + @Override + @SuppressFBWarnings(value = "OBL_UNSATISFIED_OBLIGATION", justification = "Input stream being created should not be closed here") + public T signByDefaultWithSmime(@NotNull final File pkcs12StoreFile, @NotNull final String storePassword, @NotNull final String keyAlias, @NotNull final String keyPassword) { + try { + return signByDefaultWithSmime(new FileInputStream(pkcs12StoreFile), storePassword, keyAlias, keyPassword); + } catch (IOException e) { + throw new MailerException(format(ERROR_READING_FROM_FILE, pkcs12StoreFile), e); + } + } + + /** + * @param pkcs12StoreStream The data (file) input stream containing the keystore + * @param storePassword The password to get keys from the store + * @param keyAlias The key we need for signing + * @param keyPassword The password for the key + * + * @see MailerGenericBuilder#signByDefaultWithSmime(InputStream, String, String, String) + */ + @Override + public T signByDefaultWithSmime(@NotNull final InputStream pkcs12StoreStream, @NotNull final String storePassword, @NotNull final String keyAlias, @NotNull final String keyPassword) { + final byte[] pkcs12StoreData; + try { + pkcs12StoreData = readInputStreamToBytes(pkcs12StoreStream); + } catch (IOException e) { + throw new MailerException(ERROR_READING_SMIME_FROM_INPUTSTREAM, e); + } + return signByDefaultWithSmime(pkcs12StoreData, storePassword, keyAlias, keyPassword); + } + + /** + * @param pkcs12StoreData The data (file) input stream containing the keystore + * @param storePassword The password to get keys from the store + * @param keyAlias The key we need for signing + * @param keyPassword The password for the key + * + * @see MailerGenericBuilder#signByDefaultWithSmime(InputStream, String, String, String) + */ + @Override + public T signByDefaultWithSmime(@NotNull final byte[] pkcs12StoreData, @NotNull final String storePassword, @NotNull final String keyAlias, @NotNull final String keyPassword) { + return signByDefaultWithSmime(Pkcs12Config.builder() + .pkcs12Store(pkcs12StoreData) + .storePassword(storePassword) + .keyAlias(keyAlias) + .keyPassword(keyPassword) + .build()); + } + + /** + * @see MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config) + */ + @Override + public T signByDefaultWithSmime(@NotNull final Pkcs12Config pkcs12Config) { + this.pkcs12ConfigForSmimeSigning = pkcs12Config; + return (T) this; + } + /** * @see MailerGenericBuilder#withExecutorService(ExecutorService) */ @@ -658,7 +760,7 @@ public T clearProxy() { return (T) withProxy(null, null, null, null) .withProxyBridgePort(DEFAULT_PROXY_BRIDGE_PORT); } - + /** * @see MailerGenericBuilder#clearEmailAddressCriteria() */ @@ -666,6 +768,15 @@ public T clearProxy() { public T clearEmailAddressCriteria() { return withEmailAddressCriteria(EnumSet.noneOf(EmailAddressCriteria.class)); } + + /** + * @see MailerGenericBuilder#clearSignByDefaultWithSmime() + */ + @Override + public T clearSignByDefaultWithSmime() { + this.pkcs12ConfigForSmimeSigning = null; + return (T) this; + } /** * @see MailerGenericBuilder#clearTrustedSSLHosts() @@ -763,6 +874,15 @@ public EnumSet getEmailAddressCriteria() { return emailAddressCriteria; } + /** + * @see MailerGenericBuilder#getPkcs12ConfigForSmimeSigning() + */ + @Override + @Nullable + public Pkcs12Config getPkcs12ConfigForSmimeSigning() { + return pkcs12ConfigForSmimeSigning; + } + /** * @see MailerGenericBuilder#getExecutorService() */ diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerImpl.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerImpl.java index be04bd7f7..9b5941f7d 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerImpl.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerImpl.java @@ -1,6 +1,5 @@ package org.simplejavamail.mailer.internal; -import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.simplejavamail.MailException; @@ -8,6 +7,7 @@ import org.simplejavamail.api.internal.authenticatedsockssupport.socks5server.AnonymousSocks5Server; import org.simplejavamail.api.mailer.AsyncResponse; import org.simplejavamail.api.mailer.Mailer; +import org.simplejavamail.api.mailer.config.EmailGovernance; import org.simplejavamail.api.mailer.config.OperationalConfig; import org.simplejavamail.api.mailer.config.ProxyConfig; import org.simplejavamail.api.mailer.config.ServerConfig; @@ -72,7 +72,7 @@ public class MailerImpl implements Mailer { * @see org.simplejavamail.api.mailer.MailerGenericBuilder#withEmailAddressCriteria(EnumSet) */ @NotNull - private final EnumSet emailAddressCriteria; + private final EmailGovernance emailGovernance; /** * @see org.simplejavamail.api.mailer.MailerRegularBuilder#withTransportStrategy(TransportStrategy) @@ -95,7 +95,7 @@ public class MailerImpl implements Mailer { MailerImpl(@NotNull final MailerFromSessionBuilderImpl fromSessionBuilder) { this(null, null, - fromSessionBuilder.getEmailAddressCriteria(), + fromSessionBuilder.buildEmailGovernance(), fromSessionBuilder.buildProxyConfig(), fromSessionBuilder.getSession(), fromSessionBuilder.buildOperationalConfig()); @@ -104,17 +104,17 @@ public class MailerImpl implements Mailer { MailerImpl(@NotNull final MailerRegularBuilderImpl regularBuilder) { this(regularBuilder.buildServerConfig(), regularBuilder.getTransportStrategy(), - regularBuilder.getEmailAddressCriteria(), + regularBuilder.buildEmailGovernance(), regularBuilder.buildProxyConfig(), null, regularBuilder.buildOperationalConfig()); } - MailerImpl(@Nullable ServerConfig serverConfig, @Nullable TransportStrategy transportStrategy, @NotNull EnumSet emailAddressCriteria, @NotNull ProxyConfig proxyConfig, + MailerImpl(@Nullable ServerConfig serverConfig, @Nullable TransportStrategy transportStrategy, @NotNull EmailGovernance emailGovernance, @NotNull ProxyConfig proxyConfig, @Nullable Session session, @NotNull OperationalConfig operationalConfig) { this.serverConfig = serverConfig; this.transportStrategy = transportStrategy; - this.emailAddressCriteria = emailAddressCriteria; + this.emailGovernance = emailGovernance; this.proxyConfig = proxyConfig; if (session == null) { session = createMailSession(checkNonEmptyArgument(serverConfig, "serverConfig"), checkNonEmptyArgument(transportStrategy, "transportStrategy")); @@ -323,7 +323,7 @@ public final void sendMail(final Email email) { @Nullable public final AsyncResponse sendMail(final Email email, @SuppressWarnings("SameParameterValue") final boolean async) { if (validate(email)) { - SendMailClosure sendMailClosure = new SendMailClosure(operationalConfig, session, email, proxyServer, async, operationalConfig.isTransportModeLoggingOnly(), + SendMailClosure sendMailClosure = new SendMailClosure(operationalConfig, emailGovernance, session, email, proxyServer, async, operationalConfig.isTransportModeLoggingOnly(), smtpConnectionCounter); if (!async) { @@ -346,7 +346,7 @@ public final AsyncResponse sendMail(final Email email, @SuppressWarnings("SamePa @SuppressWarnings({"SameReturnValue"}) public boolean validate(@NotNull final Email email) throws MailException { - return MailerHelper.validate(email, emailAddressCriteria); + return MailerHelper.validate(email, emailGovernance.getEmailAddressCriteria()); } /** @@ -407,11 +407,11 @@ public OperationalConfig getOperationalConfig() { } /** - * @see Mailer#getEmailAddressCriteria() + * @see Mailer#getEmailGovernance() */ @Override @NotNull - public EnumSet getEmailAddressCriteria() { - return emailAddressCriteria; + public EmailGovernance getEmailGovernance() { + return emailGovernance; } } \ No newline at end of file diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerRegularBuilderImpl.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerRegularBuilderImpl.java index 005bf0479..2eaa9b3d0 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerRegularBuilderImpl.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/MailerRegularBuilderImpl.java @@ -193,14 +193,14 @@ public MailerRegularBuilderImpl withCustomSSLFactoryInstance(@Nullable final SSL public Mailer buildMailer() { return new MailerImpl(this); } - + /** * For internal use. */ ServerConfig buildServerConfig() { vallidateServerConfig(); final int serverPort = SimpleOptional.ofNullable(port).orElse(transportStrategy.getDefaultServerPort()); - return new ServerConfigImpl(assumeNonNull(getHost()), serverPort, getUsername(), getPassword(), customSSLFactory, customSSLFactoryInstance); + return new ServerConfigImpl(assumeNonNull(getHost()), serverPort, username, password, customSSLFactory, customSSLFactoryInstance); } private void vallidateServerConfig() { diff --git a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SendMailClosure.java b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SendMailClosure.java index 53c67479a..6b1070fe1 100644 --- a/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SendMailClosure.java +++ b/modules/simple-java-mail/src/main/java/org/simplejavamail/mailer/internal/SendMailClosure.java @@ -1,14 +1,15 @@ package org.simplejavamail.mailer.internal; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.simplejavamail.api.email.Email; import org.simplejavamail.api.internal.authenticatedsockssupport.socks5server.AnonymousSocks5Server; +import org.simplejavamail.api.mailer.config.EmailGovernance; import org.simplejavamail.api.mailer.config.OperationalConfig; import org.simplejavamail.converter.internal.mimemessage.MimeMessageProducerHelper; import org.simplejavamail.mailer.internal.util.SessionLogger; import org.simplejavamail.mailer.internal.util.TransportRunner; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; import javax.mail.MessagingException; import javax.mail.Session; import javax.mail.internet.MimeMessage; @@ -26,15 +27,18 @@ class SendMailClosure extends AbstractProxyServerSyncingClosure { @NotNull private final OperationalConfig operationalConfig; + @NotNull private final EmailGovernance emailGovernance; @NotNull private final Session session; @NotNull private final Email email; private final boolean asyncForLoggingPurpose; private final boolean transportModeLoggingOnly; - SendMailClosure(@NotNull OperationalConfig operationalConfig, @NotNull Session session, @NotNull Email email, @Nullable AnonymousSocks5Server proxyServer, boolean asyncForLoggingPurpose, + SendMailClosure(@NotNull OperationalConfig operationalConfig, @NotNull EmailGovernance emailGovernance, @NotNull Session session, @NotNull Email email, @Nullable AnonymousSocks5Server proxyServer, + boolean asyncForLoggingPurpose, boolean transportModeLoggingOnly, @NotNull AtomicInteger smtpConnectionCounter) { super(smtpConnectionCounter, proxyServer); this.operationalConfig = operationalConfig; + this.emailGovernance = emailGovernance; this.session = session; this.email = email; this.asyncForLoggingPurpose = asyncForLoggingPurpose; @@ -47,7 +51,7 @@ public void executeClosure() { LOGGER.trace("sending email..."); try { // fill and send wrapped mime message parts - final MimeMessage message = MimeMessageProducerHelper.produceMimeMessage(email, session); + final MimeMessage message = MimeMessageProducerHelper.produceMimeMessage(email, session, emailGovernance.getPkcs12ConfigForSmimeSigning()); SessionLogger.logSession(session, asyncForLoggingPurpose, "mail"); message.saveChanges(); // some headers and id's will be set for this specific message diff --git a/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java b/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java index 6371a2cdd..7bdf4cbdb 100644 --- a/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java +++ b/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/MailerLiveTest.java @@ -131,6 +131,37 @@ public void createMailSession_OutlookMessageSmimeSignTest() .build()); } + @Test + public void createMailSession_OutlookMessageDefaultSmimeSignTest() + throws IOException, MessagingException, ExecutionException, InterruptedException { + // override the default from the @Before test + mailer = MailerBuilder + .withSMTPServer("localhost", SERVER_PORT) + .signByDefaultWithSmime(new File(RESOURCES_PKCS + "/smime_keystore.pkcs12"), "letmein", "smime_test_user_alias", "letmein") + .buildMailer(); + + EmailPopulatingBuilder builder = readOutlookMessage("test-messages/HTML mail with replyto and attachment and embedded image.msg"); + Email email = assertSendingEmail(builder, false, true, false, true, false); + + // verify that S/MIME was indeed only configured on the mailer instance + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning()).isNotNull(); + assertThat(builder.getPkcs12ConfigForSmimeSigning()).isNull(); + assertThat(email.getPkcs12ConfigForSmimeSigning()).isNull(); + + verifyReceivedOutlookEmail(email, true, false); + + EmailAssert.assertThat(email).wasNotMergedWithSmimeSignedMessage(); + + EmailAssert.assertThat(email).hasOriginalSmimeDetails(OriginalSmimeDetailsImpl.builder() + .smimeMode(SmimeMode.SIGNED) + .smimeMime("multipart/signed") + .smimeProtocol("application/pkcs7-signature") + .smimeMicalg("sha-256") + .smimeSignedBy("Benny Bottema") + .smimeSignatureValid(true) + .build()); + } + @Test public void testOutlookMessageWithNestedOutlookMessageAttachment() throws IOException { diff --git a/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/internal/MailerImplTest.java b/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/internal/MailerImplTest.java index 9c38a3f08..763128aae 100644 --- a/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/internal/MailerImplTest.java +++ b/modules/simple-java-mail/src/test/java/org/simplejavamail/mailer/internal/MailerImplTest.java @@ -1,12 +1,13 @@ package org.simplejavamail.mailer.internal; import org.hazlewood.connor.bottema.emailaddress.EmailAddressCriteria; +import org.jetbrains.annotations.NotNull; import org.junit.Before; import org.junit.Test; -import org.simplejavamail.api.mailer.config.OperationalConfig; +import org.simplejavamail.api.mailer.Mailer; +import org.simplejavamail.api.mailer.config.EmailGovernance; import org.simplejavamail.api.mailer.config.ProxyConfig; -import org.jetbrains.annotations.NotNull; import javax.mail.Session; import java.util.Arrays; import java.util.Collections; @@ -14,15 +15,13 @@ import java.util.Properties; import static java.util.EnumSet.noneOf; -import static java.util.UUID.randomUUID; -import static java.util.concurrent.Executors.newSingleThreadExecutor; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.simplejavamail.api.mailer.config.LoadBalancingStrategy.ROUND_ROBIN; import static org.simplejavamail.api.mailer.config.TransportStrategy.SMTP; import static org.simplejavamail.api.mailer.config.TransportStrategy.SMTPS; import static org.simplejavamail.api.mailer.config.TransportStrategy.SMTP_TLS; +import static org.simplejavamail.util.TestDataHelper.loadPkcs12KeyStore; import static testutil.EmailHelper.createDummyOperationalConfig; public class MailerImplTest { @@ -36,18 +35,13 @@ public void setup() { session = Session.getInstance(new Properties()); } - @NotNull - private ProxyConfig createEmptyProxyConfig() { - return new ProxyConfigImpl(null, null, null, null, null); - } - @Test public void trustAllHosts_PLAIN() { - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, true, false)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, true, false)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isEqualTo("*"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, false, true)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, false, true)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isNull(); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); @@ -57,11 +51,11 @@ public void trustAllHosts_PLAIN() { public void trustAllHosts_SMTPS() { ProxyConfig proxyBypassingMock = mock(ProxyConfig.class); when(proxyBypassingMock.requiresProxy()).thenReturn(false); - new MailerImpl(null, SMTPS, noneOf(EmailAddressCriteria.class), proxyBypassingMock, session, createDummyOperationalConfig(EMPTY_LIST, true, false)); + new MailerImpl(null, SMTPS, createDummyEmailGovernance(), proxyBypassingMock, session, createDummyOperationalConfig(EMPTY_LIST, true, false)); assertThat(session.getProperties().getProperty("mail.smtps.ssl.trust")).isEqualTo("*"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isEqualTo("false"); - new MailerImpl(null, SMTPS, noneOf(EmailAddressCriteria.class), proxyBypassingMock, session, createDummyOperationalConfig(EMPTY_LIST, false, true)); + new MailerImpl(null, SMTPS, createDummyEmailGovernance(), proxyBypassingMock, session, createDummyOperationalConfig(EMPTY_LIST, false, true)); assertThat(session.getProperties().getProperty("mail.smtps.ssl.trust")).isNull(); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isEqualTo("true"); @@ -69,11 +63,11 @@ public void trustAllHosts_SMTPS() { @Test public void trustAllHosts_SMTP_TLS() { - new MailerImpl(null, SMTP_TLS, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, true, false)); + new MailerImpl(null, SMTP_TLS, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, true, false)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isEqualTo("*"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("false"); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); - new MailerImpl(null, SMTP_TLS, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, false, true)); + new MailerImpl(null, SMTP_TLS, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, false, true)); assertThat(session.getProperties().getProperty("mail.smtps.ssl.trust")).isNull(); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isEqualTo("true"); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); @@ -81,26 +75,48 @@ public void trustAllHosts_SMTP_TLS() { @Test public void trustHosts() { - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList(), false, false)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList(), false, false)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isNull(); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a"), false, false)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a"), false, false)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isEqualTo("a"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a", "b"), false, false)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a", "b"), false, false)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isEqualTo("a b"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); - new MailerImpl(null, SMTP, noneOf(EmailAddressCriteria.class), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a", "b", "c"), false, true)); + new MailerImpl(null, SMTP, createDummyEmailGovernance(), createEmptyProxyConfig(), session, createDummyOperationalConfig(asList("a", "b", "c"), false, true)); assertThat(session.getProperties().getProperty("mail.smtp.ssl.trust")).isEqualTo("a b c"); assertThat(session.getProperties().getProperty("mail.smtp.ssl.checkserveridentity")).isNull(); assertThat(session.getProperties().getProperty("mail.smtps.ssl.checkserveridentity")).isNull(); } - + + @Test + public void testSignWithSmime_WithConfigObject() { + final EmailGovernanceImpl emailGovernance = new EmailGovernanceImpl(noneOf(EmailAddressCriteria.class), loadPkcs12KeyStore()); + final Mailer mailer = new MailerImpl(null, SMTP, emailGovernance, createEmptyProxyConfig(), session, createDummyOperationalConfig(EMPTY_LIST, true, false)); + + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning()).isNotNull(); + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning().getPkcs12StoreData()).isNotNull(); + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning().getStorePassword()).isEqualTo("letmein".toCharArray()); + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning().getKeyAlias()).isEqualTo("smime_test_user_alias"); + assertThat(mailer.getEmailGovernance().getPkcs12ConfigForSmimeSigning().getKeyPassword()).isEqualTo("letmein".toCharArray()); + } + @NotNull private List asList(String... args) { return Arrays.asList(args); } + + @NotNull + private ProxyConfig createEmptyProxyConfig() { + return new ProxyConfigImpl(null, null, null, null, null); + } + + @NotNull + private EmailGovernance createDummyEmailGovernance() { + return new EmailGovernanceImpl(noneOf(EmailAddressCriteria.class), null); + } } \ No newline at end of file diff --git a/modules/smime-module/src/main/java/org/simplejavamail/internal/smimesupport/SMIMESupport.java b/modules/smime-module/src/main/java/org/simplejavamail/internal/smimesupport/SMIMESupport.java index b41e6c15f..399690841 100644 --- a/modules/smime-module/src/main/java/org/simplejavamail/internal/smimesupport/SMIMESupport.java +++ b/modules/smime-module/src/main/java/org/simplejavamail/internal/smimesupport/SMIMESupport.java @@ -36,6 +36,7 @@ import org.simplejavamail.internal.smimesupport.builder.SmimeParseResultBuilder; import org.simplejavamail.internal.smimesupport.model.OriginalSmimeDetailsImpl; import org.simplejavamail.internal.smimesupport.model.SmimeDetailsImpl; +import org.simplejavamail.internal.util.SimpleOptional; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -351,6 +352,7 @@ public String getSignedByAddress(@NotNull AttachmentResource smimeAttachment) { throw new SmimeException(format(ERROR_EXTRACTING_SIGNEDBY_FROM_SMIME_SIGNED_ATTACHMENT, smimeAttachment), e); } } + /** * Delegates to {@link #determineSMIMESigned(MimePart)} and {@link #getSignedByAddress(SMIMESigned)}. * @@ -434,10 +436,15 @@ private static SignerInformationVerifier getVerifier(X509Certificate certificate @NotNull @Override - public MimeMessage signAndOrEncryptEmail(@Nullable final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails) { + public MimeMessage signAndOrEncryptEmail(@Nullable final Session session, @NotNull final MimeMessage messageToProtect, @NotNull final Email emailContainingSmimeDetails, + @Nullable final Pkcs12Config defaultSmimeSigningStore) { MimeMessage result = messageToProtect; - if (emailContainingSmimeDetails.getPkcs12ConfigForSmimeSigning() != null) { - result = signMessage(session, result, emailContainingSmimeDetails.getPkcs12ConfigForSmimeSigning()); + + final Pkcs12Config pkcs12Config = SimpleOptional + .ofNullable(emailContainingSmimeDetails.getPkcs12ConfigForSmimeSigning()) + .orMaybe(defaultSmimeSigningStore); + if (pkcs12Config != null) { + result = signMessage(session, result, pkcs12Config); } if (emailContainingSmimeDetails.getX509CertificateForSmimeEncryption() != null) { result = encryptMessage(session, result, emailContainingSmimeDetails.getX509CertificateForSmimeEncryption()); diff --git a/modules/spring-module/src/test/resources/pkcs12/smime_keystore.pkcs12 b/modules/spring-module/src/test/resources/pkcs12/smime_keystore.pkcs12 new file mode 100644 index 0000000000000000000000000000000000000000..621868f342fcdcd8ddd62a32a70fba050ef75fee GIT binary patch literal 4418 zcmY+GbyyROx5r0~4(SjnX$C_Yll|zTb1szn=q+eISpEg9na%Km;TfNYqUHO^!o|1B<1)!jGjo2gg#K zfMbD&|5`v;EO6sqTE)cy+?R!a9UL$VkmSEFkl_Hqyb-j&Knt&BZK+zRBjD2&;{Fj$3W-q0fOft#k!Q_dR zhUw{)Rb1j_Z-CKG!-?28DhPqu#M7M@9^s7pdI2Dz78<5{aVhJxpO>zsqdrQPorI2>sve zXfLB$BIj*QBA}(Wr~aJFkDcSe^g%49vWn?Z%B&O1Y6Xd?qIn!6mIj(=xY2RQ!?T`} zU3&2TFC~Dw<9oQ~8G&j$H0M%5&vXc%u5P_|Jvf?|mD`zDW{0_=Os+v*hHm^<<)0zS zqn1?xIfx+0nXe}%!vgH>e)`{dmfZgEbP3}>5xIw#T6_%IUtt|`qAFczkIE- z?VF&bwQ;`O%5I_SYt)aSEv@IP&{R=sV}&*B-%Z^nRq)Kc68Wk4veo4^hGQXpoqn80 z7w8pCU(d(}WoZ&@Iy!#kH6F^HCS~_aeE9E4 zDD0cNX;xXART)fK$>GQ58 zCQcV3`o;3@c4uYsCjtZ+Js5;qd_(Y?F96$UDqR1v5GHxULq48K#n6k*3fWY)@GR%7 zoVXco+SywU$N*7zMg;k=@jQm8SJ%gJIa>YcnrYgC7PFuEJ674%Yp!$+_G=kTah(`? z=NSgt|I7j_rq=_?>6GS3ijv4RyJMh6cQhomrTK_PV08l?Q~X8~M2%+Y^88s$P-shb zayY1JNbM@(ctr1u4z6yDQ82yXl^M^soMR?mj%sq-05z34XIwCT^R3R=xY?g8RUiPG zU!@>InU7}&K&1T7)l5ol*Eo~jaF?|UGL%H87)y-RZry5a3nCJY4)A^{a)Eywk|!i` zVgaDig4w*kZK&*mgkx+Qusp=ZS)V$qX?(k(Orj2{-nr`f!bE zBdguWgZi1U(W(>NKJ@F;Wx?u-K`L5<;yEIuQ8+?c^K~l-_y)b3@Xz-cWLX`g%`gCC z^3(0c-16~S$iBnIhnKlR+NswpHnxo+&feO@5|lm(&pz3`w4T1<=g=tmoEc$>jqJ=+ z{7Z%PzJu}zKx6>2$_5Z{ejeY53Cl+P#_THzcsp;6zU~U!uR{i`Mp4vNE+7B4$w4DY z4h7zmP4qYPPX)5Y{?7$7#(B*tz!37TB1d?Ly%h>*`R+LHaKNy$sU_8j+gFmr(}D5{ zp7MH>u@4eHy{#2Crc5=h(DJL!XF&=7jVqR_X)|}KDhAJkd3J?m8|(&?os0u-T&oSe zu7E1_R>>WE4rc-cixl$yu#4LVjrwlOYtm#>S+w}m>T8fOx*v+1{ywbO)KYD3lj@dE z6GY?Z!D0%`aXqBZJ$MtI!+i2b@^b}TSIg`uH?iFZ>y{25AEs8tFXF(e*?DJ$G;&&* z91`iiwlW}R-c&0hLyp9o4fIb!Pv(o`NXw>aX_T6Jnu@jzx!KanK)j#BBB-A(jW@lU z^OhRr#v_*P9rLjNl4X>{)go|YW*;iTGv2M$RNAg(_U!Qy+j45@xl9S*k2K2x#&>OP zwM|1#vVh}x8KD0cSPyu?PL%m~>2#F2wSi%)AELt;eFpK$v%H0~n_zkGQ7X5VV>F}ZNuh8C>p zT{M1f-R=)*Vi;<^dh##o?XpG$jpCq-@3_FFip&k>;O){k&L7mP<|L|ROAsz5rI!ff zupc-Wo6<@?T^z*>bqF3|@wkQO_&Dycws$R`=Vl?#$o5I&Y+G!XQ|6Oia*7=xPkLTq zZNI1fw{SUKY9e-xv);SPS(xVZMdg$BD^gAFi)x{AnUQOIbaF}vRu=g5g3&6z(`IBB z+8TJigD+@rOGew4nnauqS(pm;ZOe?yUoEZhGlqop}nvNjKz_G+P|A%X^SmFcxSYph* zymcQaAnE^5jsyqy-t@T_J^mm3@cs*a@xnfNj1!gO_wc*l)=Zehe9Ao1`TnM^B=zm- z(9yQ2o5gNEG^aYamZs#q)0U0_DpwtuNG;_Z&s#1kBN%a(aeOSTwp|*1>=}1ix%2l<6?saX-ljYWFv-#>P`3Y=< zTZ!1P1fEvs-HiJpL#q7pu5FuZ(I7*Ke{@Vf2k?5JSauBU^@Pw!jrEd8tx zczOQwl6K`YciXk2D0cl@ElRR@>U8KBu_fYluykd5m{UQZvec&=n7e;oi+JDwHDbaO=qOy%DEEDm zS-u!kdIyR2s2t$4%vH&*c>IWh=B8ns;Twoa9N!`X;Vdx@1Z77=d7sO ztSjnID7TfXpCANz%|k29toOpmgFz{)MMh3-^Sd^FhAc;2!bE5O35iR*NBI}M4_U+( zp0iwgYVR_3h09RbMPg7Ta-W*zB0yK|k6Tghn}wB-AJ)->s7c)EW%yT~pTR9M9rl$L z!jLHm=Hg@|o`+I0Q5ev3=gX#sYD>jCi()V5-#3PVpmw0xh73bUHdNR`(|t&otdlgl z*Gh@!v`iQHrbQ?RQC~+F; zDFRHSZcWQ05X*8zxoXA1vcWP_m(yRMqUpZwii42d?y($}i1sp@VS&=&-aGo5(=Y)x z=$^3RN5wcq&)j*`gHz}FGW$9l{QX)v=h7tvfq4X-E|b3`VcIWH(byfThg z_;q}W4{~8J6INL)~Q{a^CW>96P@><#J+#=Hy% z3dx{u^1kKma`Z)m7cDd>Qnil!(w{btR=~(zWKc=GEsUDo$dlobxLm)h`plJnnrDo7 z`qvMF@5koO6?X)$%BU?mqs7-&c&J||kS7uuoUeKhx^u>~()WZ^9FoN357psa6%ifS zt11ndNUZznJQxwoh42*jVRV}4TmG<6JiZ+;LgG^xRuh(|u_Fgqmh$zMyctEFzoUWM zVT!%|%14q9yHmogJ3wwDf2#EQWsmiUpJkj`Cu7Uc7~4jHdzn&u3q!^$+al ztOZH@Y}~x|#h%Gm>EBvFsD+3D-q{!Hu{rqN=X`@P^*EQq81s4ZQ!gVZ=VeXj-cZhu?shT)w z{9{!Hn2j{R7iWh4aC}zYo?m?gh2nnX0Iw6hN&LfP&sr2$PvdN=r>=7$w&kO}>&l>v zN1*X`q;Z{U>V*XL!`64y`;uU>OtYWXGh3dS^cw$HohD z=>iM|K?ho#QoP=|dkld;{ps$tPFA?wf`=8pI-!s5X5FW^^`4(^B4m?1kH?XTKvHZZ z$pNZ_jmdm#RU2OxUNN_$ZS~wqq{JmhhimF>q}yG=zCKYQeZi;rGBjgRY(e>{j9JnQ zN9d8yyrsiYaONiAk%-Ud!PH$)9b@1kfO?Was&MpISbiup_F8Jq{hx&d6@z`Kb;%z; zMyv8j=IB6)Z2rV0r%p)VdxG$BA(-$@IKD1u5E#L+gdltHAkEPr(~H+DaQu1lsY+VHo_;Z!?g6bqA=I@+yuPs%9-F+KBMW5aBzl(nQ?wge6)^rw^ zL|=QHmx}u*rW?OWkhU=CteMv&8{~?3C=FbmWt@@jbn}&!)#=*w(>7O!G;8JL!pEJ= z9)k~R?2rt1ENj(~uI^_Mu}u;58-9hng};Y^cP*|2r6$k?Y8`2%AuEve^QSXiGbPQX zaM0eI5q&$w!=5k+u0UF*jA#TN>LiZrEw3L3EM|e$M4^M_aq7xCf4N`b!g{F z_8m`cDZIae*kUM`55}A3v()rY!33nA+46m1q64MN(|Jed*j~OTNoQpy_Wa zwr6X(Xh@K3iTsI?{D(F{Q`sWu^Hp5I-isFotj&WX9P?a#4E|>51sqOexI33>C85fI zJDutQP=40~A?ss3CrCttvAa@t>@4mMQ22?afhYtWihakainn~GH$fe;%*qhs{Jk7S zfVV2bBRIhd8zanIc%UV_{9HhEDrdmM`~s_-d!{dDb?n;J$~WiUpSZGlw!B9l{pMH? z^M2MYQ}ajmqV$j;7U zrUbeo-w-YVR(}{RTvcq1bShNT9?bQ>&lxeDC_O$c)Ai!`BBCoPgelYyphldy7QdJa zmic#xR#fPb03ZP11Ms*H7l0ig@ID9JXFEX9eRTo&-**lG?|a_?5C8^&Il#m~0znEq pTxJ3QKJBIwgN*4*p-R)^D`p}WwGeRrw@!SbaV}P>HS-&h{{VFEKLG#$ literal 0 HcmV?d00001