Fork me on GitHub
Simple Java Mail
Simple API, Complex Emails

Simple Java Mail Security settings

Simple Java Mail provides an easy API to access all underlying Jakarta Mail security related settings as well as a few extra options, including CRLF injection scanning, DKIM and S/MIME support.

§

Authentication methods with transport strategies

Although Simple Java Mail started out as a library to help produce RFC-anatomically correct emails, one of its primary drivers now is to simplify configuration, using transport strategies.

There are four strategies:

MailerBuilder
  .withSMTPServer("host", port, "username", passwordOrOAUTH2Token)
  .withTransportStrategy(TransportStrategy.SMTP)
  .withTransportStrategy(TransportStrategy.SMTPS)
  .withTransportStrategy(TransportStrategy.SMTP_TLS)
  .withTransportStrategy(TransportStrategy.SMTP_OAUTH2)

Or with property default:

simplejavamail.transportstrategy=SMTP
# or: SMTPS, SMTP_TLS, SMTP_OAUTH2

Let's quickly review them one-by-one.

§

TransportStrategy.SMTP

MailerBuilder.withTransportStrategy(TransportStrategy.SMTP);

The SMTP legacy strategy is the oldest and simplest strategy, which works with simple username and password. This transport strategy attempts a TLS upgrade, but falls back to plaintext when a mail server does not indicate support for STARTTLS.

Since 8.6.0, with the switch from the deprecated Jakarta Mail to its successor Angus Mail, whenever a TLS session is negotiated, server certificates are also validated for SMTP. To revert back to pre-8.6.0 (legacy) behaviour, you can turn this off with mailerBuilder.verifyingServerIdentity(false). However, this is only recommended for testing purposes as you leave yourself open to Man In The Middle (MITM) attacks, even if you think you fully control your (private) network.

Furthermore, this transport strategy only offers protection against passive network eavesdroppers when the mail server indicates support for STARTTLS. Active network attackers can trivially bypass the encryption 1) by tampering with the STARTTLS indicator, 2) by presenting a self-signed certificate (if you set verifyingServerIdentity to false), 3) by presenting a certificate issued by an untrusted certificate authority; or 4) by presenting a certificate that was issued by a valid certificate authority to a domain other than the mail server's.

For proper mail transport encryption, use TransportStrategy.SMTPS or TransportStrategy.SMTP_TLS.

To disable opportunistic TLS and revert back to the legacy SMTP_PLAIN behavior prior to 5.0.0 (not recommended), you can turn it off programmatically or by setting the property simplejavamail.opportunistic.tls.


TransportStrategy.SMTP.setOpportunisticTLS(false);

MailerBuilder
	  .verifyingServerIdentity(false)
	  .withTransportStrategy(TransportStrategy.SMTP)

// or as property:
simplejavamail.defaults.verifyserveridentity=false
simplejavamail.opportunistic.tls=false
§

TransportStrategy.SMTPS

SMTP entirely encapsulated by TLS. Commonly known as SMTPS.

MailerBuilder.withTransportStrategy(TransportStrategy.SMTPS);

Strict validation of server certificates is enabled. Server certificates must be issued

  1. by a certificate authority in the system trust store; and
  2. to a subject matching the identity of the remote SMTP server.
§

TransportStrategy.SMTP_TLS

Plaintext SMTP with a mandatory, authenticated STARTTLS upgrade.

MailerBuilder.withTransportStrategy(TransportStrategy.SMTP_TLS);

Strict validation of server certificates is enabled. Server certificates must be issued

  1. by a certificate authority in the system trust store; and
  2. to a subject matching the identity of the remote SMTP server.

To quote FastMail on the differences between SSL and TLS:

SSL and TLS both provide a way to encrypt a communication channel between two computers (e.g. your computer and our server). TLS is the successor to SSL and the terms SSL and TLS are used interchangeably unless you're referring to a specific version of the protocol.

The ordering of protocols in terms of oldest to newest is: SSL v2, SSL v3, TLS v1.0, TLS v1.1, TLS v1.2, TLS v1.3 (currently proposed).

§

TransportStrategy.SMTP_OAUTH2

OAUTH2 authentication is easy, just use the SMTP_OAUTH2 TransportStrategy and provide the OAUTH2 token as the server password.

MailerBuilder
  .withSMTPServer("host", port, "username", yourOAUTH2Token)
  .withTransportStrategy(TransportStrategy.SMTP_OAUTH2);
§

Configure your own SSL connection factory

Furthermore, you can take complete control of SSL connections by providing your own SSL connection factory:

MailerBuilder
	.withCustomSSLFactoryClass(theClassName) // or:
	.withCustomSSLFactoryInstance(theInstance) // takes precedence
	.buildMailer();
Or with property default:
simplejavamail.custom.sslfactory.class=you.project.YourSSLSocketFactory
§

Whitelisting hosts

Simple Java Mail by default trusts all hosts for SSL connections, but you can also selectively whitelist hosts.

Note that this is not the same as server identity verification, which is enabled through verifyingServerIdentity(boolean). It would be prudent to have at least one of these features turned on, lest you be vulnerable to man-in-the-middle attacks.

MailerBuilder
	// disable trust all hosts for SSL connections
	.trustingAllHosts(false);
	// or white list hosts for SSL connections (identity key validation notwithstanding)
	.trustingSSLHosts("a", "b", "c", ...);
Or with property default:
simplejavamail.defaults.trustallhosts=false
# following property is ignored when trustallhosts is true:
simplejavamail.defaults.trustedhosts=192.168.1.122;mymailserver.com;ix55432y
§

Verifying server identity

Simple Java Mail also enables server identity verification for SSL connections by default (also see RFC 2595, 2.4. Server Identity Check). This is a security feature that verifies the server identity by checking the server's certificate against the host name used by the client to start the connection.

Note that this is not the same as trustingAllHosts(boolean) or trustingSSLHosts(String...). Again, it would be prudent to have at least one of these features turned on, lest you be vulnerable to man-in-the-middle attacks.

MailerBuilder
	.verifyingServerIdentity(false);
Or with property default:
simplejavamail.defaults.verifyserveridentity=true
§

Scanning for suspicious content

Finally, Simple Java Mail by default tests most fields and headers for suspicious content, which could indicate a CRLF injection attack. This is a unique feature of Simple Java Mail.

The values being scanned are:

  • subject
  • every header name and value
  • every attachment name, content ID, nested datasource name and description
  • every embedded image name, content ID, nested datasource name and description
  • from recipient name and address
  • every replyTo recipient name and address, if provided
  • bounceTo recipient name and address, if provided
  • every TO/CC/BCC recipient name and address
  • disposition-notification-to recipient name and address, if provided
  • return-receipt-to recipient name and address, if provided

Here's some more info on this topic:

This behaviour can only be turned off by turning off all client validations, which also includes checking for email completeness and email-address validations. The scans will still be performed, but issues found will only be logged as warnings.

MailerBuilder
	.disablingAllClientValidation(true);
§

Signing emails with DKIM

Simple Java Mail also supports signing with DKIM domain keys. DKIM is an optional feature and if you want to use it, you need to include the dkim-module.

currentEmailBuilder.signWithDomainKey(
	DkimConfig.builder()
		.dkimPrivateKeyData(byte[] / File / InputStream)
		.dkimSigningDomain("your_domain.org")
		.dkimSelector("your_selector")
		.useLengthParam(true) // default is false
		.excludedHeadersFromDkimDefaultSigningList("From", "Subject") // default is none
		.headerCanonicalization(Canonicalization.SIMPLE) // default is RELAXED
		.bodyCanonicalization(Canonicalization.SIMPLE) // default is RELAXED
		.signingAlgorithm("SHA256_WITH_ED25519") // default is SHA256_WITH_RSA
		.build()
);
Or configure DKIM once for every email sent through a mailer:
currentMailerBuilder
    .withDefaultDkimSigning(
        DkimConfig.builder()
            .dkimPrivateKeyData(byte[] / File / InputStream)
            .dkimSigningDomain("your_domain.org")
            .dkimSelector("your_selector")
            .build())
    .buildMailer();
Or with properties:
# defaults on Mailer level:
simplejavamail.dkim.signing.private_key_file_or_data=my_dkim_key.der # or key as base64
simplejavamail.dkim.signing.selector=dkim1
simplejavamail.dkim.signing.signing_domain=your-domain.com
simplejavamail.dkim.signing.use_length_param=true
simplejavamail.dkim.signing.excluded_headers_from_default_signing_list=From
simplejavamail.dkim.signing.header_canonicalization=SIMPLE
simplejavamail.dkim.signing.body_canonicalization=SIMPLE
simplejavamail.dkim.signing.algorithm=SHA256_WITH_ED25519

You can also use the helper method to sign a message yourself, but beware that the signing is only triggered when the MimeMessage streamed to a transport (or file):

MailerHelper.signMessageWithDKIM(mimeMessageToSign, emailContainingSigningDetails);

Excluding headers

By default, DKIM signs a fixed list of possible headers. If you need more control of which headers are signed, you can provide a list of headers that should be skipped from this list.

	DkimConfig.builder()
		(..)
		.excludedHeadersFromDkimDefaultSigningList("Message-ID", "Date", "Return-Path", "Bounces-To")
		.build()
§

Signing / encrypting emails with S/MIME

Simple Java Mail supports signing and encrypting with S/MIME. S/MIME is an optional feature and if you want to use it, you need to include the smime-module.


You can sign, encrypt or both sign and encrypt an email. In the latter case the email will first be signed and then encrypted, as per advice of the underlying library. All signing/encrypting is performed when the email is being sent.


You can sign individual emails or sign all emails by configuring S/MIME defaults on the Mailer or through properties.

For encryption, Simple Java Mail can use one email-level encryption certificate, or different certificates per recipient. When any TO/CC/BCC recipient carries a certificate, that recipient certificate is used first. Recipients without a certificate fall back to the email-level or mailer-level SmimeEncryptionConfig, if one is configured.

For a mailing list, department or other governed group, use RecipientsBuilder before adding the flat recipient list to the email. withDefaultSmimeCertificate(...) fills only missing certificates, withFixedSmimeCertificate(...) replaces every recipient certificate in that group, and clearingSmimeCertificates() removes certificate state from reused recipients so the email-level or mailer-level fallback certificate can apply.


For maximum flexibility, you can configure all algorithms and certificates specific to S/MIME signing and encryption. This includes choosing the key encapsulation algorithm and cipher algorithm for encryption, and the signature algorithm for signing. For a list of available algorithms, see the SmimeEncryptionConfig and SmimeSigningConfig classes.

Signing an email:
Pkcs12Config myKeyInfo = Pkcs12Config.builder()
    .pkcs12Store("my_smime_keystore.pkcs12")
    .storePassword("my_store_password")
    .keyAlias("my_key_alias")
    .keyPassword("my_key_password")
    .build();

Email emailToBeSigned = currentEmailBuilder
    .(..)
    .signWithSmime(SmimeSigningConfig.builder()
		.pkcs12Config(myKeyInfo)
		.signatureAlgorithm("SHA256withRSA") // optional
		.build())
    .buildEmail();

mailer.sendMail(emailToBeSigned);
Encrypting an email:
Email emailToBeEncrypted = currentEmailBuilder
    .(..)
    .encryptWithSmime(SmimeEncryptionConfig.builder()
		.x509Certificate("x509CertificateInStandardPEM.crt")
		.keyEncapsulationAlgorithm("RSA_OAEP_SHA384") // optional
		.cipherAlgorithm("AES256_CBC") // optional
		.build())
    .buildEmail();

mailer.sendMail(emailToBeEncrypted);
Encrypting with different recipient certificates:
SmimeEncryptionConfig algorithmsAndFallback = SmimeEncryptionConfig.builder()
    .x509Certificate(fallbackCertificate)
    .keyEncapsulationAlgorithm("RSA_OAEP_SHA384")
    .cipherAlgorithm("AES256_CBC")
    .build();

Recipient alice = new RecipientBuilder()
    .withAddress("alice@example.com")
    .withType(Message.RecipientType.TO)
    .withSmimeCertificate(aliceCertificate)
    .build();

Recipient bob = new RecipientBuilder()
    .withAddress("bob@example.com")
    .withType(Message.RecipientType.TO)
    .withSmimeCertificate(bobCertificate)
    .build();

Email emailToBeEncrypted = currentEmailBuilder
    .withRecipients(alice, bob)
    .encryptWithSmime(algorithmsAndFallback)
    .buildEmail();
Encrypting a group with exactly one certificate:
Collection<Recipient> finance = new RecipientsBuilder()
    .withFixedSmimeCertificate(financeCertificate)
    .withRecipientsWithDefaultName("Finance", Message.RecipientType.TO,
            "alice@example.com", "bob@example.com")
    .buildRecipients();

Email emailToBeEncrypted = currentEmailBuilder
    .withRecipients(finance)
    .buildEmail();
Defaulting only missing certificates in a group:
Collection<Recipient> finance = new RecipientsBuilder()
    .withDefaultSmimeCertificate(financeFallbackCertificate)
    .withRecipients(previousFinanceRecipients)
    .buildRecipients();
// recipients that already had their own certificates keep them;
// the others receive financeFallbackCertificate
Clearing reused recipient certificates so the email-level certificate applies:
Collection<Recipient> reusableRecipients = new RecipientsBuilder()
    .clearingSmimeCertificates()
    .withRecipients(previousRecipients)
    .buildRecipients();

Email emailToBeEncrypted = currentEmailBuilder
    .withRecipients(reusableRecipients)
    .encryptWithSmime(emailLevelSmimeEncryptConfig)
    .buildEmail();
Sign all emails by default from Java:
currentMailerBuilder
    (...)
    .withEmailDefaults(EmailBuilder.startingBlank()
		.signWithSmime(myPkcs12Config)
		.buildEmail())
    .buildMailer();
Property-only S/MIME defaults:
# Sign every email sent through a property-configured mailer.
simplejavamail.smime.signing.keystore=my_smime_keystore.pkcs12
simplejavamail.smime.signing.keystore_password=keystore_password
simplejavamail.smime.signing.key_alias=key_alias
simplejavamail.smime.signing.key_password=key_password
simplejavamail.smime.signing.algorithm=SHA256withRSA

# Encrypt with one fallback certificate and the selected algorithms.
# Recipient-level certificates configured in Java still take precedence.
simplejavamail.smime.encryption.certificate=shared-recipient-certificate.pem
simplejavamail.smime.encryption.key_encapsulation_algorithm=RSA
simplejavamail.smime.encryption.cipher=DES_EDE3_CBC

# Optional when the application is fully property-driven.
simplejavamail.defaults.to.name=Finance
simplejavamail.defaults.to.address=alice@example.com,bob@example.com

Properties can configure the signing identity and a single encryption fallback. They cannot express different S/MIME certificates per recipient; use RecipientBuilder or RecipientsBuilder for per-recipient or group certificate policy.

§

Reading S/MIME signed / encrypted attachments

Simple Java Mail can automatically handle S/MIME signed messages or attachments and has some useful extras such as providing you with metadata.

S/MIME is an optional feature and if you want to use it, you need to include the smime-module.

Email mergedEmail = EmailConverter.outlookMsgToEmail("yourSMIMESignedMessage.msg"); // or
Email mergedEmail = EmailConverter.emlToEmail("yourSMIMESignedMessage.eml");

// all attachments as-is:
mergedEmail.getAttachments(); // smime.p7m, my-doc.docx
// all attachments with the encrypted ones replaced:
mergedEmail.getDecryptedAttachments(); // signed-email.eml, my-doc.docx

// if the message itself was signed (rather than a independently signed attachment):
OriginalSmimeDetails details = mergedEmail.getOriginalSmimeDetails();
details.getSmimeMode(); // SIGNED
details.getSmimeMime(); // application/pkcs7-mime or multipart/signed
details.getSmimeType(); // signed-data, enveloped-data
details.getSmimeName(); // smime.p7m or smime.p7s
details.getSmimeMicalg(); // ie. sha-512
details.getSmimeSignedBy(); // email or name used
details.getSmimeSignatureValid(); // true, false or null when not applicable

When S/MIME signature validation fails, Simple Java Mail keeps the parsed signed content available and exposes the invalid status through getOriginalSmimeDetails().getSmimeSignatureValid(). Unsupported encrypted S/MIME payloads in Outlook messages are treated leniently so the surrounding message can still be converted when possible.

§

S/MIME signed messages are merged by default

As an S/MIME signed message is actually nested as an attachment, the default behavior is to merge the S/MIME signed content into the root message. This only happens if there was exactly one S/MIME signed attachment and the decrypted version is of type "message/rfc822".

This default behavior can be deactivated. For your convenience, the decrypted message is available as a separate Email instance:

Email nonMergedEmail = EmailBuilder
                .copying(mergedEmail)
                .clearSMIMESignedAttachmentMergingBehavior()
                .buildEmail();

// or by configuring the intermediary builder:
emailBuilder = EmailConverter.outlookMsgToEmailBuilder(msgFile); // or
emailBuilder = EmailConverter.emlToEmailBuilder(emlFile);
Email nonMergedEmail = emailBuilder
                .notMergingSingleSMIMESignedAttachment()
                .buildEmail();
You always have access to the nested decrypted message:
mergedEmail.getSmimeSignedEmail();
nonMergedEmail.getSmimeSignedEmail();

If a message is both signed and encrypted, getSmimeSignedEmail() will itself have a nested getOriginalSmimeDetails().


        signedAndEncrypted.getOriginalSmimeDetails().getSmimeMode(); // SIGNED_ENCRYPTED
        Email signedOrEncrypted = signedAndEncrypted.getSmimeSignedEmail();

        signedOrEncrypted.getOriginalSmimeDetails().getSmimeMode(); // SIGNED or ENCRYPTED

        // whether it is SIGNED or ENCRYPTED depends on the order in which the original
        // email client handled this S/MIME scenario
§

Decrypting S/MIME attachments using certificate

Every conversion method optionally accepts a Pkcs12Config instance, which contains details about your key store and certificate. With that, you can decrypt an S/MIME encrypted mail.

Pkcs12Config yourPkcs12Config = Pkcs12Config.builder()
      .pkcs12Store("smime_keystore.pkcs12") // path, File or InputStream
      .storePassword("letmein")
      .keyAlias("smime_test_user_alias")
      .keyPassword("letmein")
      .build();

EmailConverter.outlookMsgToEmail("yourSMIMEEncryptedMessage.msg", yourPkcs12Config); // or
EmailConverter.emlToEmail("yourSMIMEEncryptedMessage.eml", yourPkcs12Config);

mergedEmail.getOriginalSmimeDetails().getSmimeMode(); // ENCRYPTED or SIGNED_ENCRYPTED