7.8 KiB
TP gestionnaire de mots de passe sécurisé
Objectifs
- Comprendre l'authentification moderne (JWT stocké en Cookie HttpOnly vs LocalStorage).
- Manipuler des primitives cryptographiques (AES-256-GCM, Argon2).
- Implémenter les opérations CRUD sécurisées où le serveur chiffre/déchiffre les données.
- Analyse critique : identifier les limites de sécurité d'une architecture "Server-Side Encryption".
Structure du projet
Le projet est constitué de :
server.js: L'API Node.js (Express + SQLite). C'est ici que vous allez travailler.db_final.sqlite: La base de données (générée automatiquement).vanilla_client/: Le client front-end (fourni, ne pas modifier).
Le schéma général de l'application est le suivant :
flowchart LR
Client["Browser (client vanilla_client/)"] -- "Requêtes http (api/...)" --> Server("Serveur (server.js)")
Server -- "Cookie HttpOnly (JWT)" --> Client
Server -- "Requêtes sql (lecture/écriture)" --> DB[("Base de données (db_final.sqlite)")]
Comment lancer le projet
- Ouvrez le dossier dans votre terminal.
- Installez les dépendances :
npm install - Lancez le serveur :
nodemon server.js - Pour le client : clic droit sur
vanilla_client/index.html> "Run".
Étape 1 : analyse de l'existant (lecture de code)
Avant de coder, analysez le fichier server.js, spécifiquement les parties Configuration, Middleware et les routes /register et /login.
Répondez (pour vous-mêmes ou sur papier) aux questions suivantes pour comprendre le flux :
-
Le secret du coffre :
- Lors du
/register, nous générons uneuserVaultKeyaléatoire. C'est la clé qui chiffrera tous les mots de passe de l'utilisateur. - Cette clé n'est jamais stockée en clair dans la BDD. Comment est-elle protégée avant d'être insérée dans la table
users?
sequenceDiagram participant Client participant Serveur participant BDD Client->>Serveur: POST /register (login, password) activate Serveur Serveur->>Serveur: Hash password "ex: Argon2(password)" Serveur->>Serveur: Génère userVaultKey "clé aléatoire" Serveur->>Serveur: Chiffre vaultKey "AES(vaultKey, SERVER_MASTER_KEY)" Serveur->>BDD: INSERT users (login, hash, encryptedVaultKey) activate BDD BDD-->>Serveur: Utilisateur créé deactivate BDD Serveur-->>Client: Réponse 201 "utilisateur créé" deactivate Serveur - Lors du
-
Le JWT "Stateful" :
- Lors du
/login, le serveur récupère lauserVaultKeyde l'utilisateur. - Où le serveur stocke-t-il cette clé pour que l'utilisateur puisse s'en servir lors des requêtes suivantes (ex: ajouter un mot de passe) ?
- Pourquoi chiffrons-nous cette clé avec
SERVER_MASTER_KEYavant de la mettre dans le JWT ?
sequenceDiagram participant Client participant Serveur participant BDD Client->>Serveur: "POST /login (login, password)" activate Serveur Serveur->>BDD: "SELECT * FROM users WHERE login = ?" activate BDD BDD-->>Serveur: "Données utilisateur (hash, encryptedVaultKey)" deactivate BDD Serveur->>Serveur: "Vérifie hash (Argon2(password) vs hash)" alt "Mot de passe correct" Serveur->>Serveur: "Déchiffre vaultKey (AES(encryptedVaultKey, MASTER_KEY))" Serveur->>Serveur: "Crée JWT (payload: { userId, decryptedVaultKey })" Serveur-->>Client: "Réponse 200 OK (Set-Cookie: token=JWT HttpOnly)" else "Mot de passe incorrect" Serveur-->>Client: "Réponse 401 (non autorisé)" end deactivate Serveur - Lors du
-
Sécurité du Token :
- Regardez la méthode
res.cookiedans/login. Pourquoi l'optionhttpOnly: trueest-elle cruciale ici ? Le JavaScript côté client (ex:script.js) peut-il lire ce cookie ?
- Regardez la méthode
Étape 2 : implémentation (à vous de jouer)
Le serveur démarre, vous pouvez vous inscrire et vous connecter. Cependant, les fonctionnalités liées aux mots de passe renvoient des erreurs ou sont vides.
Vous devez compléter les 3 routes manquantes dans server.js (cherchez les commentaires TODO).
Mission A : sauvegarder une entrée (POST /entries)
Objectif : chiffrer le mot de passe reçu avant de l'insérer en base.
-
Aide :
- Le middleware
authenticateAndUnpackKeya déjà fait le travail difficile. Il a déchiffré la clé de l'utilisateur et l'a placée dansreq.user.vaultKey. - Utilisez la fonction utilitaire
encryptAES(texte, clé)définie plus haut dans le fichier. - Regardez la structure de la table
entriespour savoir quoi insérer (notammentencrypted_blob,iv,auth_tag).
Voici le flux d'une requête authentifiée que vous devez implémenter :
sequenceDiagram participant Client participant Serveur participant BDD Client->>Serveur: POST /entries (payload: { title, ... }, cookie: token) activate Serveur Note over Serveur: Middleware "authenticateAndUnpackKey" Serveur->>Serveur: Vérifie le JWT (reçu du cookie) Serveur->>Serveur: Extrait payload "{ userId, decryptedVaultKey }" Serveur->>Serveur: Stocke "decryptedVaultKey" dans "req.user.vaultKey" Note over Serveur: Exécution de la route (Mission A) Serveur->>Serveur: Chiffre le mot de passe "AES(data, req.user.vaultKey)" Serveur->>BDD: INSERT entries (owner_id, encrypted_blob, iv, tag) activate BDD BDD-->>Serveur: Entrée sauvegardée deactivate BDD Serveur-->>Client: Réponse 201 "créé" deactivate Serveur - Le middleware
Mission B : déchiffrer un mot de passe (GET /entries/:id/password)
Objectif : Renvoyer le mot de passe en clair à l'utilisateur lorsqu'il clique sur l'icône "Oeil".
- Aide :
- Récupérez l'entrée en BDD via son
id. - Sécurité : Assurez-vous dans la clause
WHEREque l'entrée appartient bien à l'utilisateur connecté (owner_id). - Utilisez
decryptAES(...)avec lareq.user.vaultKeypour retrouver le texte clair.
- Récupérez l'entrée en BDD via son
Mission C : lister les entrées (GET /entries)
Objectif : Renvoyer la liste des sites pour le tableau de bord. Aucun mot de passe ne doit être inclus.
- Aide :
- Faites un
SELECTclassique. - Performance & Sécurité : Ne renvoyez JAMAIS le champ
encrypted_blob(le mot de passe chiffré) ou les IV/Tags dans cette liste. On ne veut que les métadonnées (id,title,url,username_field) pour construire l'interface. Le déchiffrement se fera uniquement à la demande (Mission B).
- Faites un
Étape 3 : audit de sécurité (réflexion)
Une fois votre code fonctionnel, prenez du recul. Dans cette architecture, le chiffrement se fait Côté Serveur.
Scénario catastrophe : Imaginez qu'un attaquant (ou un employé malveillant) obtienne un accès complet à la machine qui héberge le serveur (accès aux fichiers et à la mémoire vive).
- L'attaquant trouve le fichier
.env(ou le code) contenant la variableSERVER_MASTER_KEY. - L'attaquant fait un "Dump" de la base de données
db_final.sqlite.
Questions :
- L'attaquant peut-il déchiffrer les mots de passe stockés dans la table
entries? Si oui, expliquez comment il procèderait étape par étape. - Architecture "Zero Knowledge" : comment aurions-nous dû architecturer l'application pour que, même avec un accès total au serveur, l'attaquant ne puisse jamais lire les mots de passe des utilisateurs ? (Indice : Où devrait se faire le chiffrement AES ?)