Vérification de mail signé avec Javamail crypto

Icon from Nuvola icon theme for KDE 3.x.
Image via Wikipedia

L’utilisation de javamail crypto doit permettre au développeur de ne pas trop rentrer dans la technique de la norme S/MIME. En effet avec des points d’entrée clairs comme crypter, signer, décrypter et vérifier on fait rapidement confiance. A tord.

Il y a pourtant des signes qui ne trompent pas comme la date de dernière mise à jour du projet: juin 2006, et les quelques TODO dans le code. On se raccroche néanmoins à un espoir devant la peur du code à écrire sans l’aide de cette bibliothèque. De plus, quand il s’agit de crypter, signer et décrypter, Javamail Crypto donne satisfaction. Le seul point inquiétant est la vérification de la signature S/MIME qui nécessite de faire une petite gymnastique pour fournir à l’API la clé publique pour vérifier la signature…

En regardant le post Réception de mail crypté S/MIME, on voit qu’une fois le certificat publique obtenu, il suffit de la passer à la classe utilitaire en même temps que le mail dont on veut vérifier la signature. La confiance est là et on oublie de tester ce qui se passe quand on signe avec un certificat différent de celui utilisé pour la vérification. La réponse est rien.

Quelle fut ma déception quand un mail signé par un tiers fut validé par javamail crypto combien même je lui passe un certificat différents comme référence! En investiguant la méthode appelée et grace à l’open source, la crédulité dont on a pu faire prouve tire en nous un le gloussement d’un rire nerveux. La clé passée en paramètre n’est jamais utilisée !!!!

En fait javamail se contente de vérifier que la signature correspond bien au contenu signé. Cette vérification de la non altération du message est cependant insuffisante quand il s’agit de données sensible. Sans aller jusqu’à s’assurer de la non répudiation en vérifiant les listes de révocation, on s’attend à ce que Javamail crypto s’assure au moins que la signature provient bien d’un émetteur donné. Sinon à quoi ça sert de passer la clé publique ?

Finalement il vaut mieux s’adresser à Dieu qu’à ses saints. En utilisant directement Bouncycastle dont javamail crypto est une abstraction, on s’en sort. Avec plus de code mais il faut bien ça. Le lecteur trouvera e lui même le détail de la solution en se référant à la classe org.bouncycastle.mail.smime.examples.ValidateSignedMail présente dans les sources.

Réception de mail crypté S/MIME avec Javamail-crypto

Crypto stub
Image via Wikipedia

Dans le billet précédent je montrais comment envoyer un mail S/MIME, ce billet traite de l’opération inverse. Comment réceptionner un mail crypté et signé en S/MIME ?

L’idée est de reprendre la méthode de lecture du certificat publique du signataire pour vérifier la signature du mail mais il faut auparavant décrypter le mail reçu. En effet l’expéditeur signe le message puis le crypte donc on doit décrypter puis vérifier la signature une fois que le mail est devenu lisible.
Voici le pseudo-code

MimeMessage m = retrieveMessageFromMailBox();
m = decryptMessage(m);
checkSignature(m);

La lecture du message dans la boite mail ne pose pas de grande difficulté mais le résultat est un mail avec comme content-type la valeur application/pkcs7-mime. Malheureusement le code utilitaire SMIMEEncryptionUtils de javamail-crypto (qui décrypte un message contient un oubli qui l’empêche de gérer les mails cryptés contenant des PJ comme c’est le cas dans un mail signé. En effet le résultat du décryptage doit être un mail avec le content-type valant multipart/signed ou application/pkcs7-mime ou bien application/x-pkcs7-mime, sinon le mail n’est pas détecté comme un mail signé. Or le méthode qui décrypter decryptMessage.decryptMessage()oublie malheureusement d’invoquer saveChange()sur le message. Aussi le le content-type n’est pas mis à jour. So do it yourself !

Ensuite il suffit d’appeler la méthode utilitaire de vérification de signature SMIMEEncryptionUtils.checkSignature() pour finir la partie réception d’un S/MIME. Il est a noté que le mail signé par SMIMEEncryptionUtils.signeMessage() est de type multipart/signed ce qui signifie que le mail est constitué de 2 partie :

  1. le contenu message original avant signature
  2. la signature portant sur le message original précédent

Il faut en tenir compte pour extraire les informations du message original. C’est-à-dire que le contenu du mail décrypté que l’on obtient par MimeMessage.getContent() pourra être itéré mais on ne s’intéressera qu’à sa première partie. Cette première partie obtenue par Multipart.getBodyPart() sera soit considéré comme  le contenu du corps du message original avant signature si son content-type est text/plain mais si le message original avait déjà des pièces-jointes le content-type sera multipart/mixed et il faudra alors itérer sur la première partie. La forme du message original dépend du client mail utilisé pour créer le message original avant signature et cryptage.

Encrytage de mail avec JavaMail-Crypto et BouncyCastle

Icon from Nuvola icon theme for KDE 3.x.
Image via Wikipedia

Javamail-crypto est une API peu récente puisque le jar est estampillé juin 2006. Cepedant l’usage de cette API simplifie grandemant la vie du programmeur puisque pourvu que le message en clair, la session mail, ainsi que la clé soient fournis, il suffit de faire

EncryptionUtils smimeUtils = null;
String type = EncryptionManager.SMIME;
smimeUtils = EncryptionManager.getEncryptionUtils(type);
smimeSignedMsg = smimeUtils.encryptMessage(session, mmessage,
smimeKey);

Lorsque l’on doit envoyer un mail crypté à la norme S/MIME, on utilise la clé publique du destinataire. On ne peut pas utiliser Javamail-crypto et Bouncycastle comme on le ferait dans le cas de la signature comme dans l’exemple suivant:

char[] smimePw = new String("mot de passe")
.toCharArray();
EncryptionKeyManager smimeKeyMgr = smimeUtils.createKeyManager();
InputStream privKeyIS = null:
smimeKeyMgr.loadPrivateKeystore(privKeyIS, smimePw);
smimeKey = smimeKeyMgr.getPrivateKey("cle privee signature",
smimePw);

En effet bien que l’on puisse charger les clé publiques, l’API s’attend à lire en entrée un fichier PKCS12. Or ce fichier doit aussi contenir la clé privée ce que le destinataire du mail ne va jamais nous fournir. Il se bornera à nous fournir un certificat contenant sa clé publique.

Heureusement que l’API de cryptage prend une clé java.security.Key , il nous suffit de créer un tel objet. Pour cela il ne suffit par de créer un objet implémentant l’interface Key car le code de SMIMEEncryptionUtils.encryptMessage() fait un transtypage:

BouncySMIMEEncryptionKey bKey = (BouncySMIMEEncryptionKey) key;

Nous devons donc créer de toute pièce une instance de BouncySMIMEEncryptionKey.Cela est assez facile car en regardant le code on voit qu’il nous suffit, pour encryptage de mail, de fixer le certificat dans un objet BouncySMIMEEncryptionKey.

Voici le code complet pour lire la clé publique dans un fichier PEM et encrypter un mail:

EncryptionUtils smimeUtils = null;
BouncySMIMEEncryptionKey smimeKey = null;
String type = EncryptionManager.SMIME;
smimeUtils = EncryptionManager.getEncryptionUtils(type);
PEMReader reader = new PEMReader(new InputStreamReader(inputStremoOnThePEM));
X509CertificateObject cert = (X509CertificateObject) reader.readObject();
smimeKey = new BouncySMIMEEncryptionKey();
smimeKey.setCertificate(cert);
MimeMessage smimeSignedMsg = smimeUtils.encryptMessage(session, mmessage,smimeKey);