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
- TransportStrategy.SMTP
- TransportStrategy.SMTPS
- TransportStrategy.SMTP_TLS
- TransportStrategy.OAUTH2
- Configure your own SSL connection factory
- Whitelisting hosts
- Verifying server identity
- Scanning for suspicious content
- Signing emails with DKIM
- Signing / encrypting emails with S/MIME
- Reading S/MIME signed / encrypted attachments
- S/MIME signed messages are merged by default
- Decrypting S/MIME attachments using certificate
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
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
- by a certificate authority in the system trust store; and
- 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
- by a certificate authority in the system trust store; and
- 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:
- Email Header Injection security
- StackExchange - What threats come from CRLF in email generation?
- OWASP - Testing for IMAP SMTP Injection
- CWE-93: Improper Neutralization of CRLF Sequences ('CRLF Injection')
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.
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