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 :
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 :
$("#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.