jquery

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.

Un lien « Retour en haut » dynamique

Les petits boutons « Retour en haut », on les aime tous. Généralement, il s'agit d'une simple div en position: fixed. En voici un qui n'apparait qu'arrivé à un certain pourcentage de la page, et qui s'arrêtre gracieusement juste au-dessus du footer.

Le code HTML est on ne peut plus simple :


<div class="relative">
	<div class="toTop">
		<a href="#top" title="Retour en haut">
			Retour en haut
		</a>
	</div>
</div>
<footer id="footer">
…
</footer>

Le code CSS :


.relative {
	position:relative;
}

.toTop {
	display:none;
	position:fixed;
	right:20px;
	bottom:20px;
	z-index:1000;
}

Voilà, nous avons un lien, mais il n'apparait pas pour le moment. On ajoute un peu de jQuery :


function toTop(element,footer){
	var scroll = $(window).scrollTop();
	var maxScroll = $(window).height() * 0.4;

	if(scroll > maxScroll)
		$(element).show();
	else
		$(element).hide();


	if($(element).offset().top + $(element).height() >= $(footer).offset().top - 10)
		$(element).css('position', 'absolute');
	if($(document).scrollTop() + window.innerHeight < $(footer).offset().top)
		$(element).css('position', 'fixed');
}	

$(window).scroll(function() {
	toTop('.toTop',"#footer");
});

$(window).resize(function() {
	toTop('.toTop',"#footer");
});

Scrolling doux vers une ancre

Un petit bout de jQuery intéressant pour animer le scrolling vers une ancre (ce que les anglophones appellent smooth scroll) :


$('a[href*=#]').on('click', function(event){     
	event.preventDefault();
	$('html,body').animate({scrollTop:$(this.hash).offset().top}, 700);
});	
	

Effet parallax avec jQuery

Un gros bout de code en jQuery pour faire un effet parallax sur un élément donné. Je le colle là sans trop d'explications, j'ai beaucoup trop de choses à faire pour tout commenter.


/*
* Adds a parallax effect to a background image
* (position:fixed;no-repeat;background-size:42%;)
*
* element : the element with a background image
* percent : percent of the background-size (0.42)
* height : height of the background image
* width : width of the background image
* factor : factor of speed (the lower the faster)
* reference : the element bottom-placed to get the total
*             height of the page
*
* author : Guillaume Litaudon  <guillaume.litaudon@gmail.com>
*/

function parallax(element,percent,height,width,factor,reference){
	var winWidth = $(window).width();
	var winHeight = $(window).height();

	var sizePercent = percent == 1 ? height : ((winWidth*percent)/width)*height;

	var maxScroll = -(sizePercent - winHeight);
	var speed = factor*($(reference).offset().top / winHeight);
	var yPos = -($(window).scrollTop() / speed);
	yPos = yPos >= 0 ? 0 : yPos;
	yPos = yPos >= maxScroll ? yPos : maxScroll;
	var coords = '0 '+ yPos + 'px';

	$(element).css({ backgroundPosition: coords });
}


function goGoParallaxEffect(){
	/* La taille dépend de si l'on est en-dessous
	* de pictureWidth ou non
	*/
	var pictureHeight = 1080;
	var pictureWidth = 388;

	if($(window).width() > pictureWidth)
		parallax('#page',0.42,pictureWidth,pictureHeight,5,footer);
	else
		parallax('#page',1,pictureWidth,pictureHeight,5,footer);
}

$(window).scroll(function() {
	goGoParallaxEffect();
});

$(window).resize(function() {
	goGoParallaxEffect();
});	
Haut de page