Chiffrer un message avec OpenPGP.js

Pour ma page de contact, je souhaitais faciliter l'envoi de messages chiffrés avec ma clé publique PGP. Dans l'idéal, une simple case à cocher permettrait de chiffrer le message avant l'envoi au serveur, puis de là dans ma boite mail. C'est pourquoi je me suis penché sur plusieurs implémentations d'OpenPGP en Javascript.

La première, Hanewinkel, était assez légère une fois minifiée (30 ko). Néanmoins, le chiffrement de messages contenant des caractères accentués posait problème. J'aurais pu m'en contenter, mais c'est toujours bon d'arriver à lire des messages directement. Si toutefois ça vous intéresse, voici un petit snippet à intégrer :

lang-javascript
function encryptPGP(key, message){
	var pu = new getPublicKey(key);
 	if(pu.vers == -1) 
 		return;

	var keytyp = 0;      // 0=RSA, 1=Elgamal
	var keyid = pu.keyid;
	var pubkey = pu.pkey.replace(/\n/g,'');
	return doEncrypt(keyid, keytyp, pubkey, message);
}

$("#contact form").on("submit", function() {

	// Comme on va peut-être avoir besoin de
	// chiffrer le message, on n'utilise pas
	// le .serialize() habituel d'Ajax, mais
	// un tableau.
	var donnees = $(this).serializeArray();

	// Si la checkbox est cochée
	if($("#contact-crypted")[0].checked) {
		// On récupère la clé publique affichée
		// dans la page
		var key = $("#pgp-key").html();
		// On récupère le message
		var message = $("#contact-message").val();
		// On le chiffre
		message = encryptPGP(key, message);
		// Et on affiche le message chiffré
		$("#contact-message").val(message);

		// Enfin, on remplace le message par
		// son équivalent chiffré dans le tableau
		for(var i = 0; i < donnees.length; i++) {
			if(donnees[i].name == "contact-message")
				donnees[i].value = message;
		}
	}

	// On transforme notre tableau en une suite
	// de paramètres dont Ajax va se charger
	donnees = jQuery.param(donnees);

	// Et on envoie tout ça par Ajax pour
	// éviter un rechargement de la page
	$.ajax({
		url: $(this).attr("action"),
		type: $(this).attr("method"), 
		data: donnees,
		success: function(html) { 
			alert(html);
		},
		error: function(html) {
			alert(html);
		}
	});

    return false;
});

Je suis donc allé voir du côté d'openpgpjs, une solution plus lourde (250 ko), mais qui cette fois fonctionne avec les caractères accentués. Le problème principal qu'on a ici, c'est que `openpgp` est une *promise*. C'est cool pour un tas de choses, mais là on aimerait bien attendre la fin du chiffrement avant d'envoyer notre message au serveur. Oh, et au cas où vous vous le demanderiez, openpgp.js embarque un *polyfill* pour assurer la compatibilité avec les navigateurs qui ne tiennent pas leurs promesses…

Donc je passerai sur mes envies de meurtre lorsque j'ai compris que je devrais refaire tout le code et vous le présente tel quel :

lang-javascript
$("#contact form").on("submit", function() {

	// Si la checkbox est cochée
	if($("#contact-crypted")[0].checked) {
		
		var contact = $("#contact form");
	
		// Comme on va peut-être avoir besoin de
		// chiffrer le message, on n'utilise pas
		// le .serialize() habituel d'Ajax, mais
		// un tableau.
		var donnees = $(contact).serializeArray();
	
		// On récupère le message
		var message = $("#contact-message").val();

		// On récupère la clé publique affichée
		// dans la page et on la prépare
		var pubkey = openpgp.key.readArmored($("#pgp-key").html());

		// Enfin, on lance le chiffrement
		openpgp.encryptMessage(pubkey.keys, message).then(function(pgpMessage){
			
			// On récupère le message chiffré
			message = pgpMessage;
			
			// Et on l'affiche
			$("#contact-message").val(message);

			// Enfin, on remplace le message par
			// son équivalent chiffré dans le tableau
			for(var i = 0; i < donnees.length; i++) {
				if(donnees[i].name == "contact-message")
				donnees[i].value = message;
			}

			// On transforme notre tableau en une suite
			// de paramètres dont Ajax va se charger
			donnees = jQuery.param(donnees);

			// Et on envoie tout ça par Ajax pour
			// éviter un rechargement de la page
			$.ajax({
				url: $(contact).attr("action"),
				type: $(contact).attr("method"), 
				data: donnees,
				success: function(html) { 
					alert(html);
				},
				error: function(html) {
					alert(html);
				}
			});
		}).catch(function(error){
			alert(error);
		});
	}
	// Et là la méthode classique,
	// sans chiffrement
	else {
		$.ajax({
			url: $(this).attr("action"),
			type: $(this).attr("method"), 
			data: $(this).serialize(),
			success: function(html) { 
				alert(html);
			},
			error: function(html) {
				alert(html);
			}
		});
	}	

	return false;
});

Note sur la sécurité : ne déployez pas cette solution autrement que pour un petit formulaire de contact. Il s'agit plus d'un *proof of concept*. J'entends par là qu'elle se base sur ce qu'on appelle dans le milieu host based security, autrement dit aucune véritable sécurité. Il suffit à quelqu'un (que ce soit l'hébergeur, le FAI, le VPN, le navigateur, etc.) de changer la clé publique, le fichier `openpgp.js` ou le code javascript pour qu'il soit capable de lire le message. Plus d'informations sur cet article.

----

Permaliens :