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 CLRF 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_OATH2

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

§

TransportStrategy.SMTP

This transport strategy falls back to plaintext when a mail server does not indicate support for STARTTLS. Additionally, even if a TLS session is negotiated, server certificates are not validated in any way.

MailerBuilder.withTransportStrategy(TransportStrategy.SMTP);

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, 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.withTransportStrategy(TransportStrategy.SMTP);
// or as property:
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.OAUTH2

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

MailerBuilder
  .withSMTPServer("host", port, "username", yourOAUTH2Token)
  .withTransportStrategy(TransportStrategy.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, nested datasource name and description
  • every embedded image name, nested datasource name and description
  • from recipient name and address
  • 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 names and addresses, 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 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 on the Mailer instead.


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 SmimeEncryptConfig and SmimeSignConfig 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(SmimeSignConfig.builder
		.pkcs12Config(myKeyInfo)
		.signatureAlgorithm("SHA256withRSA") // optional
		.build())
    .buildEmail();

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

mailer.sendMail(emailToBeEncrypted);
Sign all emails by default (this works for encryption as well):
currentMailerBuilder
    (...)
    .withEmailDefaults(EmailBuilder.startingBlank()
		.signWithSmime(myPkcs12Config)
		.buildEmail())
    .buildMailer();
Or with properties:
# defaults on Mailer level:
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
# encryption can only be applied to individual Email instances
# but this can be default behaviour (not recommended as users would need to share private keys)
simplejavamail.smime.encryption.certificate=x509CertificateInStandardPEM.crt
simplejavamail.smime.encryption.key_encapsulation_algorithm=RSA
simplejavamail.smime.encryption.cipher_algorithm=DES_EDE3_CBC
§

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
§

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().smimeMode(); // SIGNED_ENCRYPTED
        Email signedOrEncrypted = signedAndEncrypted.getSmimeSignedEmail();

        signedOrEncrypted.getOriginalSmimeDetails().smimeMode(); // 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