Skip to content

Commit

Permalink
#320: added byDefault S/MIME signing on the Mailer level. Also, the s…
Browse files Browse the repository at this point in the history
…mime 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.
  • Loading branch information
bbottema committed May 31, 2021
1 parent e93123f commit 1b25fbb
Show file tree
Hide file tree
Showing 20 changed files with 422 additions and 84 deletions.
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -1127,10 +1127,12 @@ public interface EmailPopulatingBuilder {
* Signs this email with an <a href="https://tools.ietf.org/html/rfc5751">S/MIME</a> signature, so the receiving client
* can verify whether the email content was tampered with.
* <p>
* <strong>Note:</strong> this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}.
* <strong>Note:</strong> this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}.<br>
* <strong>Note:</strong> You can also configure your <code>Mailer</code> instance do sign all emails by default (also has better performance).
*
* @see <a href="https://en.wikipedia.org/wiki/S/MIME">S/MIME on Wikipedia</a>
* @see <a href="https://www.globalsign.com/en/blog/what-is-s-mime/">Primer on S/MIME</a>
* @see org.simplejavamail.api.mailer.MailerGenericBuilder#signByDefaultWithSmime(Pkcs12Config)
*/
@Cli.ExcludeApi(reason = "delegated method contains CLI compatible arguments")
EmailPopulatingBuilder signWithSmime(@NotNull Pkcs12Config pkcs12Config);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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<EmailAddressCriteria> getEmailAddressCriteria();
EmailGovernance getEmailGovernance();
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -226,6 +229,53 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
*/
T withEmailAddressCriteria(@NotNull EnumSet<EmailAddressCriteria> emailAddressCriteria);

/**
* Signs this <em>all emails by default</em> with an <a href="https://tools.ietf.org/html/rfc5751">S/MIME</a> signature, so the receiving client
* can verify whether the email content was tampered with.
* <p>
* <strong>Note:</strong> this only works in combination with the {@value org.simplejavamail.internal.modules.SMIMEModule#NAME}.
*
* @see <a href="https://en.wikipedia.org/wiki/S/MIME">S/MIME on Wikipedia</a>
* @see <a href="https://www.globalsign.com/en/blog/what-is-s-mime/">Primer on S/MIME</a>
* @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)}.
* <p>
* <strong>Note:</strong> 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)}.
* <p>
* <strong>Note:</strong> 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)}.
* <p>
* <strong>Note:</strong> 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);

/**
* <strong>For advanced use cases.</strong>
* <p>
Expand Down Expand Up @@ -580,6 +630,13 @@ public interface MailerGenericBuilder<T extends 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.
*
Expand Down Expand Up @@ -651,6 +708,13 @@ public interface MailerGenericBuilder<T extends MailerGenericBuilder<?>> {
@Nullable
EnumSet<EmailAddressCriteria> 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.
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
* <p>
* 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<EmailAddressCriteria> 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();
}
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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())) {
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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)));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
}
}
Loading

0 comments on commit 1b25fbb

Please sign in to comment.