Skip to content
This repository has been archived by the owner on Jan 6, 2022. It is now read-only.

markenwerk/java-utils-mail-dkim

 
 

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Be aware: This project is no longer maintained. Have a look at Simple Java Mail if you're interested in a successor project.


A DKIM library for Jakarta Mail

This is a simple to use library to use DKIM features in conjunction with Jakarta Mail.

Overview

This library allows you to

  • sign MIME Messages according to the DKIM standard,
  • check, whether the DNS resource record for a sending domain is prepared correctly for DKIM.

This library is hosted in the Maven Central Repository. You can use it with the following coordinates:

<dependency>
	<groupId>net.markenwerk</groupId>
	<artifactId>utils-mail-dkim</artifactId>
	<version>2.0.1</version>
</dependency>

Consult the usage description and Javadoc for further information.

Origin and state

The initial version of this library is based on a project called DKIM for JavaMail, which allows to sign MIME Messages according to the DKIM standard and fetch the corresponding DNS resource record. This library extended the DNS resource record check and integrated it in the signing procedure (this is enabled by default, but can be turned off). In addition to retrieving the corresponding DNS resource record for a signing domain and a selector, the check now tests the following, before signing a MIME message:

  • Check, whether the retrieved public key fits to the given private key.
  • Check, whether the retrieved DKIM granularity fits to the given DKIM identity.
  • Check, whether the retrieved DKIM version is DKIM1.
  • Check, whether the retrieved DKIM service type includes email.

Setup

In order to use DKIM, it is necessary to create a RSA key pair and publish the public key in an appropriate DNS entry.

A RSA private pair with a key size of 1024 bits can be generated as a PEM encoded PKCS8 file like this:

openssl genrsa -out dkim.pem 1024

While DKIM should be compatible with any reasonable key size, it might not be possible to publish arbitrary large public keys. See section 3.3.3. of the RFC for further information on key sizes.

Javas standard API only allows to import PKCS8 files in unencrypted PEM encoding. Therefore, it is either necessary to use a third party library like the Java version of The Legion of the Bouncy Castle or to convert the PEM encoded file into an unencrypted DER encoded file like this:

openssl pkcs8 -topk8 -nocrypt -in dkim.pem -outform der -out dkim.der

The corresponding public key can be obtained from the private key like this:

openssl rsa -in dkim.pem -pubout

This yields an output like this:

writing RSA key
-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCf4lvVllV2eoDqxartI0bUiJXD
v+TVhFoGcheKocQyLGrTi8BKamhoDt8yKiecpCm1rZ/nRyxSqIAJFMV3y/XslSVV
2Sc48efPtrdViGUcGYNCC/KrqYNgCF7vRO2oAQ7ePPBohwcR1hzavGeY/AVxpEeI
vixQNmunxkdaqHCLuQIDAQAB
-----END PUBLIC KEY-----

The content of the DNS resource record consists of a set of keys and values, where a typical DNS resource record has values for following keys:

  • v: The DKIM version, currently DKIM1.
  • g: The DKIM granularity, used to restrict the allowed sender identities, usualy *.
  • k: The key type, usualy rsa.
  • p: The Base64 encoded public key, usualy a RSA public key.
  • s: The allowed service types, usualy email or *.
  • t: Some flags used by DKIM validators.

See section 3.6.1. of the RFC for further information

To publish such a public key, i.e. for the domain example.com and the selector foo, it is necessary to create a DNS resource record with type TXT for the domain foo._domainkey.example.com with the following content:

v=DKIM1;g=*;k=rsa;p=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCf4lvV
llV2eoDqxartI0bUiJXDv+TVhFoGcheKocQyLGrTi8BKamhoDt8yKiecpCm1rZ/n
RyxSqIAJFMV3y/XslSVV2Sc48efPtrdViGUcGYNCC/KrqYNgCF7vRO2oAQ7ePPBo
hwcR1hzavGeY/AVxpEeIvixQNmunxkdaqHCLuQIDAQAB;s=email;t=s

Helpful tools

You can use http://dkimcore.org/tools/dkimrecordcheck.html to examine the DNS resource record for a given domain and a given selector and http://dkimvalidator.com/ to verify, that correct DKIM signatures are generated.

Usage

We will assume that you already know how to create a SMTP Session and how create and send a MIME Message with JavaMail, but here is a minimal example how one could send a simple message:

public void sendMail(Session session, String from, String to, String subject, String content) throws Exception {
	MimeMessage message = new MimeMessage(session);
	message.setFrom(new InternetAddress(from));
	message.setRecipient(RecipientType.TO, new InternetAddress(to));
	message.setSubject(subject);
	message.setContent(content, "text/plain; charset=utf-8");
	MimeMessage dkimSignedMessage = dkimSignMessage(message, from, "example.com", "foo");
	Transport.send(dkimSignedMessage);
}

To sign MimeMessage with DKIM, you have to configure a DkimSigner, which can be used multiple times, and create a new DkimMessage from the original MimeMessage and the DkimSigner.

private MimeMessage dkimSignMessage(MimeMessage message, String from, String signingDomain, String selector)  throws Exception {
	DkimSigner dkimSigner = new DkimSigner(signingDomain, selector, getDkimPrivateKeyFileForSender(from));
	dkimSigner.setIdentity(from);
	dkimSigner.setHeaderCanonicalization(Canonicalization.SIMPLE);
	dkimSigner.setBodyCanonicalization(Canonicalization.RELAXED);
	dkimSigner.setSigningAlgorithm(SigningAlgorithm.SHA256_WITH_RSA);
	dkimSigner.setLengthParam(true);
	dkimSigner.setCopyHeaderFields(false);
	return new DkimMessage(message, dkimSigner);
}

When the message is signed, a check is performed to check, whether the DNS resource record for the given domain and the given selector is prepared correctly for DKIM, i.e. if the given identity matches the configured granularity and if the given private key matches the configured public key. A DkimAcceptanceException is thrown otherwise. Please be aware, that this happens during Transport.send(dkimSignedMessage).

To disable this check, which is not recommended, call dkimSigner.setCheckDomainKey(false). Using the DomainKeyUtil, you can perform this check manually like this:

DomainKey domainKey = DomainKeyUtil.getDomainKey(signingDomain, selector);
domainKey.check(from, getDkimPrivateKeyFileForSender(from));

Languages

  • Java 100.0%