A S/MIME library for JavaMail
This is a simple to use library to use S/MIME features in conjunction with JavaMail.
Overview
This library allows you to
- sign MIME Messages according to the S/MIME standard,
- encrypt MIME Messages according to the S/MIME standard,
- check, whether a MIME Message is encrypted or signed according to the S/MIME standard,
- check, whether the signature of a MIME message that is signed according to the S/MIME standard is valid or
- decrypt a MIME message that is encrypted according to the S/MIME standard.
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-smime</artifactId>
<version>1.0.8</version>
</dependency>
Consult the usage description and Javadoc for further information.
Origin and state
The initial version of this library is based on the S/MIME specific parts of a project called JavaMail-Crypto API which relies on the Java version of The Legion of the Bouncy Castle as a provider of all sorts of cryptography witchcraft.
The JavaMail-Crypto API itself seems to be heavily influenced by the example code from the Bouncy Castle project.
We've decided to provide this library as an alternative for the S/MIME specific functionality of the JavaMail-Crypto API since the original project appears to be unmaintained since June of 2006 and is in fact incompatible with more current versions of Bouncy Castle (Some methods were deprecated for years and are now completely removed).
It is currently not our intention to provide a corresponding modernization of the PGP specific functionality.
The project page of the JavaMail-Crypto API states that it is currently in an alpha state. However, we never had any major issues while we were using it in a production environment. Fixes to minor issues have been incorporated in this library.
We used the original - and now this - library mainly to sign and encrypt system generated messages. None of the major mail clients that actually have S/MIME support (Thunderbird, Mail for Mac & iOS, etc.) had any problems to decrypt or check the signatures of these messages.
This library has roughly the same range of functionality regarding S/MIME as the original library. This may not be every aspect of the current RFC, but we're trying to improve this library when necessary. A further goal of this library is to provide an API that is as simple as possible to use.
Setup
An application that wants to encrypt a S/MIME message has to provide a S/MIME certificate in form of a standard X509Certificate
.
This library imposes no obstacles on how the certificate object is obtained.
An application that wants to sign or decrypt a S/MIME message has to provide a standard PrivateKey
and the X509Certificate
chain for the S/MIME certificate, but the preferred way is to provide these as a PKCS12 keystore.
Some CAs provide S/MIME certificates free of charge (i.E. COMODO). In most cases, a private key is created by the browser that is used to apply for the certificate, i.e. by using the keygen tag, and after validation, i.e. opening a confirmation link send via email, the corresponding certificate is installed into the browsers certificate store. The private key and the certificate, including the certificate chain, can then be exported as a PKCS12 keystore. The alias given to the private key inside the keystore is usually neither guessable nor very sensible, but you can use the keytool to find it out.
keytool -list -storetype pkcs12 -keystore smime.p12
For a PKCS12 keystore called smime.p12
this yields a output like
Keystore type: PKCS12
Keystore provider: SunJSSE
Your keystore contains 1 entry
's comodo ca limited id, Oct 8, 2015, PrivateKeyEntry,
Certificate fingerprint (SHA1): F1:A9:99:CC:35:CA:3E:C7:D3:01:EC:95:14:D7:C0:32:1C:AF:50:CF
where 's comodo ca limited id
is the given alias. It can be changed like this:
keytool -changealias -alias "'s comodo ca limited id" -destalias "alias" -storetype pkcs12 -keystore smime.p12
The public certificate can be exported into a PEM encoded file like this:
keytool -export -alias "alias" -storetype pkcs12 -keystore smime.p12 -rfc -file certificate.pem
Usage
First things first: The BouncyCastleProvider
has to be added as a JCE provider somewhere in your application before this library can be used:
Security.addProvider(new BouncyCastleProvider());
Importing the S/MIME certificate ...
Depending on what you want to do with this library you must import the private key and certificate chain as a SmimeKey
or import the public certificate as a X509certificate
.
... to sing or decrypt a message
While SmimeKey
has a public constructor it is recommended to use a SmimeKeyStore
, which is a thin wrapper around a PKCS12 keystore and can be created and used like this:
SmimeKeyStore smimeKeyStore = new SmimeKeyStore(pkcs12Stream, storePass);
SmimeKey smimeKey = smimeKeyStore.getPrivateKey("alias", keyPass);
To create a SmimeKeyStore
you have to provide an InputStream
that yields the PKCS12 keystore (most likely a FileInputStream
) and the store password as a char[]
. By default, the char[]
will be overwritten after is has been used. This behaviour can be turned off with the optional third parameter.
To obtain a SmimeKey
from the SmimeKeyStore
you have to provide the alias of the private key entry and the password to decrypt the private key. Again, as a char[]
that will be overwritten by default.
... to encrypt a message
There are many ways to import a X509certificate
. One possibility is the following where you have to provide an InputStream
that yields the PEM encoded certificate:
CertificateFactory factory = CertificateFactory.getInstance("X.509", "BC");
X509Certificate certificate = (X509Certificate) factory.generateCertificate(pemStream);
Using the library as a sender ...
While is is in theory possible to sign and encrypt a message in any order and even multiple times, this is not recommended in reality because even the common mail clients don't support such usages of S/MIME very well.
So far, we haven't encountered any problems with the following usages:
- Signing only
- Encrypting only
- Fist signing, than encrypting
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 encryptedSignedMessage = encryptMessage(session, signMessage(session, message, from), to);
Transport.send(encryptedSignedMessage);
}
.. to sign a message
Just use the SmimeUtil
with the MimeMessage
to be signed and the SmimeKey
to sign it with:
private MimeMessage signMessage(Session session, MimeMessage message, String from) throws Exception {
SmimeKey smimeKey = getSmimeKeyForSender(from);
return SmimeUtil.sign(session, message, smimeKey);
}
.. to encrypt a message
Just use the SmimeUtil
with the MimeMessage
to be encrypted and the X509certificate
to encrypt it with:
private MimeMessage encryptMessage(Session session, MimeMessage message, String to) throws Exception {
X509Certificate certificate = getCertificateForRecipient(to);
return SmimeUtil.encrypt(session, message, certificate);
}
Using the library as a receiver
We will assume that you already know how to create a POP or IMAP Session
and how receive a MIME Message with JavaMail, but here is a minimal example how one could read messages:
Store store = session.getStore();
store.connect(host, port, user, password);
Folder inbox = store.getFolder("Inbox");
inbox.open(Folder.READ_ONLY);
for (int i = 1, n = inbox.getMessageCount(); i <= n; i++) {
MimeMessage mimeMessage = (MimeMessage) inbox.getMessage(i);
}
You can then use the SmimeUtil
check the messages content type and find out if it has a SmimeState
of ENCRYPTED
, SIGNED
or NEITHER
like this:
SmimeState smimeState = SmimeUtil.getStatus(mimePart);
If the messages S/MIME state is ENCRYPTED
, you can use the SmimeUtil
with the encrypted MimeMessage
and the SmimeKey
to decrypt like this:
MimeMessage decryptedMessage = SmimeUtil.decrypt(session, mimeMessage, getSmimeKey());
If the messages S/MIME state is SIGNED
(the contains a MIME multipart with exactly two body parts: the signed content and the signature), you can use the SmimeUtil
to check whether the signature is valid for the signed content and retrieve the signed content like this:
boolean validSignature = SmimeUtil.checkSignature(mimePart)
MimeBodyPart signedContent = SmimeUtil.getSignedContent(mimePart);
If the messages S/MIME state is NEITHER
it just means that the message is neither S/MIME encrypted nor S/MIME signed. It may be encrypted or signed by some other means.