Skip to main content

Comment mettre en œuvre l’épinglage SSL/TLS dans Node.js

Écrit par
Headshot of Nwani Victory

Nwani Victory

blog feature multithreading

29 août 2023

0 minutes de lecture

Face aux attaques de l’intercepteur (MITM) menées par des acteurs malveillants, disposer d’un certificat SSL/TLS ne suffit plus à justifier la confiance accordée à une connexion entrante. Les développeurs adoptent donc de plus en plus l’épinglage SSL/TLS, également appelé épinglage de certificat ou de clé publique, comme mesure supplémentaire pour prouver l’authenticité et l’intégrité d’une connexion.

Dans une application Node.js, l’épinglage SSL/TLS ajoute une couche de sécurité en empêchant les attaquants d’intercepter et de modifier les communications entre le client et le serveur.

Cet article présente l’épinglage de certificats, ses avantages et ses cas d’utilisation dans les applications Node.js.

Quand et pourquoi utiliser l’épinglage SSL/TLS

L’épinglage SSL/TLS est un mécanisme de sécurité qui contribue à protéger contre les attaques MITM et d’autres attaques liées aux certificats. Il garantit qu’un client, comme une application Node.js, se connecte uniquement à un serveur doté d’un certificat numérique préalablement vérifié. Cette technique consiste à stocker et à utiliser des certificats ou des clés publiques spécifiques dans les applications hôtes, puis à les comparer à la clé publique ou au certificat du serveur. Nous pouvons intégrer les certificats ou les clés publiques directement dans nos applications, au moyen de variables d’environnement, ou les stocker à l’extérieur dans un service de coffre-fort de clés pour plus de flexibilité.

L’épinglage de certificats réduit les risques d’attaques MITM en considérant comme malveillantes et en interrompant toutes les requêtes dont le certificat ne correspond pas au certificat épinglé, même si elles utilisent HTTPS. Il contribue également à prévenir d’autres vulnérabilités liées aux certificats, comme leur usurpation ou leur falsification, ainsi que la compromission des autorités de certification (CA).

Selon l’enquête Stack Overflow Developer Survey, 47,12 % des développeurs utilisent Node.js, ce qui en fait une technologie web standard pour les applications côté serveur. Les modules intégrés à Node.js, comme HTTPS, ajoutent une couche de protection en validant les certificats épinglés. Une application Node.js utilisant l’épinglage de certificats compare le certificat présenté lors de la négociation TLS au certificat ou aux clés publiques prédéfinis. En cas de divergence, elle interrompt la requête au niveau de la couche transport.

Les applications financières sur appareils mobiles constituent un cas d’utilisation de l’épinglage de certificats. Une attaque MITM sur un tel appareil peut avoir des conséquences désastreuses pour son propriétaire, car celui-ci peut transmettre des informations personnelles sensibles, comme des coordonnées bancaires ou des informations de contact. Même si les versions récentes des systèmes d’exploitation mobiles, comme Android Pie, disposent de fonctionnalités de sécurité qui n’autorisent les applications à établir que des connexions sécurisées, des acteurs malveillants pourraient lancer des attaques par hameçonnage en générant des certificats autosignés pour accéder à l’appareil. L’épinglage de certificats garantit que l’application n’autorise pas une connexion frauduleuse, même si l’appareil fait confiance au certificat autosigné.

Préparer la mise en œuvre de l’épinglage SSL/TLS

Lorsqu’un client envoie une requête réseau à un serveur, la couche transport lance une négociation sécurisée à l’aide du protocole SSL/TLS. Le serveur présente son certificat numérique, qui contient la clé publique utilisée pour le chiffrement et l’authentification. Au cours de la négociation, l’application récupère la clé publique du serveur à partir du certificat présenté.

L’application compare la clé publique récupérée à une copie préconfigurée ou intégrée au code. Si elles correspondent, la connexion est considérée comme sécurisée et les communications peuvent se poursuivre. Dans le cas contraire, l’application peut interrompre la connexion ou prendre les mesures appropriées conformément à sa politique de sécurité, par exemple déclencher une alerte ou refuser d’envoyer des données sensibles.

Les CA jouent un rôle essentiel dans le processus d’épinglage des certificats en fournissant les certificats SSL/TLS nécessaires à l’établissement d’une connexion sécurisée. La CA signe le certificat, que le navigateur valide pour les applications web ou qu’une chaîne de confiance du système d’exploitation valide. L’épinglage de certificats constitue une étape supplémentaire pour confirmer que le certificat est fiable.

TLS est un module intégré à Node.js qui peut extraire d’un hôte un objet certificat contenant un champ de clé publique. Le code ci-dessous montre comment utiliser le module TLS pour envoyer une requête à un hôte et récupérer le certificat sous forme d’objet :

const tls = require("tls");
const host = "example.com";
const port = 443;

const socket = tls.connect(port, host, () => { 
const certificate = socket.getPeerCertificate();
 const publicKey = certificate.pubkey;

 console.log("Public Key:", publicKey);
 console.log("Certificate:", certificate);
});

socket.on("error", (error) => {
 console.error("Connection error:", error);
});

Dans ce code, la méthode getPeerCertificate extrait le certificat. Nous pouvons également utiliser la méthode getPeerX509Certificate pour récupérer le certificat du pair sous la forme d’un objet x509Certificate.

Mettre en œuvre l’épinglage SSL/TLS dans Node.js

Pour utiliser l’épinglage SSL/TLS dans une application Node.js, nous devons spécifier la clé ou le certificat dans la configuration de la requête. Avec HTTPS, les propriétés ca, cert et key des options https.request contribuent à l’épinglage SSL/TLS. Lors de l’épinglage d’un certificat, la propriété `ca` doit spécifier un tableau contenant les certificats encodés au format Privacy-Enhanced Mail (PEM) ou les certificats CA approuvés. La propriété key doit spécifier un tableau contenant les clés privées associées à un certificat de serveur approuvé.

Snyk Code est un outil de test de sécurité statique des applications (SAST) conçu pour les développeurs. Il détecte en temps réel les vulnérabilités de sécurité dans une base de code, pendant le développement. Il est disponible via des commandes d’interface de ligne de commande (CLI), des kits de développement logiciel (SDK) pour les pipelines d’intégration et de déploiement continus (CI/CD), des interfaces web, des extensions pour les environnements de développement intégrés (IDE) et des éditeurs de code. Dans un projet Node.js, Snyk Code peut détecter l’absence de prise en charge des certificats SSL/TLS et nous en avertir.

Pour détecter l’absence de prise en charge SSL/TLS, utilisez la commande code test ci-dessous pour analyser la base de code du projet depuis votre terminal. La commande snyk ignore --file-path=<file_path> permet d’exclure de l’analyse Snyk les fichiers ou répertoires que nous ne voulons pas inclure, comme le répertoire node_modules contenant nos packages Node.js :

snyk code test

Bien que l’épinglage SSL/TLS offre des avantages en matière de sécurité, une mise en œuvre défaillante, sans gestion des erreurs, peut aussi avoir des conséquences désastreuses. Nos mises en œuvre de l’épinglage doivent prévoir une solution de repli pour gérer les cas limites, par exemple lorsqu’un certificat épinglé est révoqué ou a expiré. La gestion des erreurs empêche l’application entière de planter lorsqu’elle ne parvient pas à confirmer un certificat ou une clé publique. Des audits peuvent nous aider à consigner ces situations.

Pour commencer, créez un répertoire nommé snyk-tls, puis utilisez :

npm init -y

Le code ci-dessous illustre l’épinglage d’une empreinte de clé publique dans une application Node.js. Celle-ci correspond à l’empreinte de la clé publique du certificat pour une requête GET créée à l’aide du module natif https de Node.js. Si les empreintes de clé publique ne correspondent pas, le code interrompt la requête. Créez un fichier index.js et ajoutez le code suivant pour essayer :

const https = require("https");

const PINNED_CERT_FINGERPRINTS = [
    "44:0C:58:51:4C:73:7C:67:DA:A2:72:29:81:68:CD:FC:51:B5:79:65:66:F0:55:FA:55:C4:45:30:BB:DD:09:82",
];

const options = {
    hostname: "google.com",
    port: 443,
    path: "/",
    method: "GET",
    headers: {
        "User-Agent": "Node.js/https"
    }
};

const req = https.request(options, (res) => {
    res.on("data", d => {
        console.log("Request succeeded and received a response after certificate pinning validation")
    });
});

req.on("error", console.error);

req.on("socket", (socket) => {
    socket.on("secureConnect", () => {
        // Uses the sha256 fingerprint hash of the certificate instead of the certificate itself or
        // the public key info. The one downside to this is that if/when the certificate
        // changes the fingerprint will too and the PINNED_CERT_FINGERPRINTS will need to be updated.
        // Reference: https://owasp.org/www-community/controls/Certificate_and_Public_Key_Pinning
        const fingerprint256 = socket.getPeerCertificate().fingerprint256;

        if (!PINNED_CERT_FINGERPRINTS.includes(fingerprint256)) {
            req.emit("error", new Error("The certificate fingerprint received does not match any pinned certificate fingerprints"));
            return req.destroy();
        } else {
            console.log("All OK. Server matched one of our pinned certificate fingerprints");
        }
    });
});

req.end();

Dans le code ci-dessus, le tableau PINNED_CERT_FINGERPRINTS permet de vérifier si l’empreinte du certificat public figure parmi les empreintes autorisées. Si ce n’est pas le cas, la méthode destroy interrompt la requête.

En production, nous devons stocker la clé publique ou le certificat dans des variables d’environnement ou dans un coffre-fort de clés distinct, afin d’éviter que la clé publique ne soit exposée lors de l’envoi du code vers un dépôt.

Tester et gérer l’épinglage SSL/TLS

Pour tester l’efficacité d’une mise en œuvre de l’épinglage, une excellente méthode consiste à simuler une attaque MITM. Des outils comme Mitmproxy ou Wireshack permettent de créer un environnement de test pour surveiller, intercepter et relayer les requêtes réseau destinées à un hôte de test.

Pour tester l’efficacité de notre épinglage SSL/TLS, nous devons désactiver les certificats épinglés et lancer une attaque MITM via un proxy afin d’enregistrer le comportement de référence de l’application sans épinglage. Nous répétons ensuite le test avec l’épinglage activé pour vérifier si l’application peut détecter le proxy et interrompre la connexion.

Comparer le hachage de la clé du certificat est une autre méthode pour tester l’épinglage SSL/TLS. Le hachage d’un certificat lui est propre : une fonction de hachage cryptographique transforme des données de taille arbitraire en valeurs de taille fixe qui représentent le certificat. C’est donc une méthode adaptée à la validation d’un certificat. À l’aide du module Node.js crypto, nous pouvons générer un hachage à partir du certificat de l’hôte et le comparer au hachage attendu de la clé publique.

Une troisième façon de tester notre épinglage SSL/TLS consiste à utiliser des outils d’analyse réseau tels que Nmap pour effectuer une analyse de ports SSL. Nmap fournit la commande ssl-enum-chipers pour analyser un port SSL sur une cible spécifiée.

Il est essentiel de gérer et de mettre à jour les certificats ou les clés publiques épinglés pour préserver la confiance, la sécurité et l’intégrité des canaux de communication. En les tenant à jour, nous pouvons atténuer les risques liés à la compromission, à l’expiration et à la révocation des certificats, tout en garantissant des connexions sécurisées et authentifiées.

Ouvrez le dossier snyk-tls dans le terminal et exécutez la commande ci-dessous.

echo -n | openssl s_client -connect google.com:443 -servername google.com | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > cert.pem

Cette commande génère un fichier cert.pem contenant le certificat public de google.com.

Ensuite, créez un fichier main.js dans le dossier du projet. Collez-y le code ci-dessous.

// import the required modules
const tls = require('node:tls');
const https = require('node:https');
const { exec } = require('child_process');

// setting up the variables
const certPath = 'cert.pem';
const command = `openssl x509 -noout -in ${certPath} -fingerprint -sha256`;

// generate SHA256 fingerprint of the certificate
const generate_SHA256_fingerprint = () => {
 return new Promise((resolve, reject) => {
   exec(command, (error, stdout, stderr) => {
     if (error) {
       reject(new Error('Error executing OpenSSL command: ' + error));
       return;
     }

     if (stderr) {
       reject(new Error('OpenSSL command returned an error: ' + stderr));
       return;
     }

     resolve(stdout.split('=')[1].trim());
   });
 });
};

// verify the certificate
const verifyCertificate = async () => {
 try {
   const cert256 = await generate_SHA256_fingerprint();
   console.log('Pinned Certificate Fingerprint:', cert256);

   const options = {
     hostname: 'google.com',
     port: 443,
     path: '/',
     method: 'GET',
     checkServerIdentity: function (host, cert) {
       // Make sure the certificate is issued to the host we are connected to
       const err = tls.checkServerIdentity(host, cert);
       if (err) {
         return err;
       }

       // Pin the exact certificate
       if (cert.fingerprint256 !== cert256) {
         const msg = 'Certificate verification error: ' +
           `The certificate of '${cert.subject.CN}' ` +
           'does not match our pinned fingerprint';
         return new Error(msg);
       }
     },
   };

   options.agent = new https.Agent(options);
   const req = https.request(options, (res) => {
     console.log('All OK. Server matched our pinned cert');
   });

   req.on('error', (e) => {
     console.error(e.message);
   });

   req.end();
 } catch (error) {
   console.error('Error generating SHA256 fingerprint:', error);
 }
};

verifyCertificate();

Le code ci-dessus importe les modules nécessaires et définit deux variables : certPath et command. certPath définit le chemin d’accès au fichier de certificat. command contient la commande shell qui génère l’empreinte SHA-256 du certificat.

La fonction generate_SHA256_fingerprint() utilise la fonction exec pour exécuter la commande openssl qui génère l’empreinte SHA-256 du certificat. Cette fonction renvoie une promesse qui se résout avec l’empreinte.

La fonction verifyCertificate() définit un objet options pour configurer la requête HTTPS. Cet objet spécifie le hostname, le port, le path et la method. Il définit également une fonction checkServerIdentity() pour vérifier le certificat du serveur. Cette fonction garantit que le certificat appartient à l’hôte auquel nous nous connectons et vérifie que l’empreinte correspond à l’empreinte épinglée.

Pour tester ce script, exécutez la commande node main.js dans votre terminal. Vous devriez obtenir le résultat suivant.

All OK. Server matched our pinned cert

Pour mettre à jour un certificat ou une clé épinglés, nous ouvrons le fichier API et exécutons la commande openssl qui génère le fichier cert.pem. Si nous utilisons un coffre-fort externe pour stocker les certificats ou les clés publiques épinglés, nous pouvons modifier les valeurs dans ce service sans changer le code de notre application. Voici les situations qui nécessitent la mise à jour de nos certificats épinglés :

  • Expiration de certificats ou de clés publiques

  • Fuites de données

  • Révocation par l’émetteur

  • Politiques de conformité des utilisateurs imposant le renouvellement du certificat après une durée déterminée

Conclusion

Cet article nous a appris à mettre en œuvre l’épinglage SSL/TLS dans des applications Node.js. L’épinglage SSL/TLS ajoute une couche de sécurité en vérifiant la clé publique ou le certificat du serveur à l’aide d’une copie stockée localement. Il contribue à protéger nos applications contre les attaques MITM et d’autres vulnérabilités liées aux certificats.

Node.js intègre des modules comme HTTPS qui prennent en charge l’épinglage de certificats. Nous pouvons appliquer l’épinglage SSL/TLS en comparant la clé publique pendant la négociation TLS et en interrompant la connexion en cas de divergence. Une bonne gestion des erreurs est également essentielle pour éviter de perdre l’accès dans les cas limites où un certificat ou une clé publique expire ou est révoqué par un émetteur. Tester l’épinglage SSL/TLS dans nos applications en simulant des attaques, en comparant les hachages de clés et en mettant à jour les certificats ou les clés constitue une bonne pratique pour en garantir l’efficacité. Il est également recommandé de stocker les clés publiques de manière sécurisée.

En tirant parti de l’épinglage SSL/TLS, nous pouvons renforcer la sécurité globale de nos applications Node.js et garantir une expérience utilisateur fiable et sûre.

Lancez-vous dans les compétitions Capture The Flag

Apprenez à résoudre des défis Capture The Flag en regardant à la demande notre atelier virtuel d’initiation.

Lire la suite

blog feature snyk container custom base recommendations
Blog

Découvrez Evo Agentic AppSec : une sécurité au rythme du développement de l’IA

Le développement piloté par l’IA dépasse les workflows de sécurité traditionnels. L’AppSec agentique de Snyk s’appuie sur des agents de sécurité pour détecter, corriger et vérifier les vulnérabilités en continu, tandis que les équipes définissent les exigences.

blog feature ai violet
Blog

VulnBump : les agents IA peuvent-ils corriger les dépendances vulnérables sans casser le build ?

VulnBump évalue des agents IA sur 307 mises à niveau de dépendances réelles. Une correction n’est comptabilisée que si la vulnérabilité disparaît et que la compilation et les tests restent au vert.

Blog

Les modèles de pointe ont trouvé les vulnérabilités. Seul l’attaquant a trouvé les chaînes d’exploitation.

L’analyse statique a détecté les failles, mais seuls des tests d’attaque en conditions réelles ont prouvé comment elles pouvaient être enchaînées pour provoquer des compromissions. Comparaison d’Evo COS, de Claude Security et de Claude Code Security.