First commit
This commit is contained in:
186
README.md
Normal file
186
README.md
Normal file
@@ -0,0 +1,186 @@
|
||||
# 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 :
|
||||
|
||||
1. `server.js` : L'API Node.js (Express + SQLite). C'est ici que vous allez travailler.
|
||||
2. `db_final.sqlite` : La base de données (générée automatiquement).
|
||||
3. `vanilla_client/` : Le client front-end (fourni, ne pas modifier).
|
||||
|
||||
Le schéma général de l'application est le suivant :
|
||||
|
||||
```mermaid
|
||||
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
|
||||
|
||||
1. Ouvrez le dossier dans votre terminal.
|
||||
2. Installez les dépendances :
|
||||
```bash
|
||||
npm install
|
||||
```
|
||||
3. Lancez le serveur :
|
||||
```bash
|
||||
nodemon server.js
|
||||
```
|
||||
4. 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 :
|
||||
|
||||
1. **Le secret du coffre :**
|
||||
|
||||
* Lors du `/register`, nous générons une `userVaultKey` alé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` ?
|
||||
|
||||
<!-- end list -->
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
|
||||
2. **Le JWT "Stateful" :**
|
||||
|
||||
* Lors du `/login`, le serveur récupère la `userVaultKey` de 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_KEY` avant de la mettre dans le JWT ?
|
||||
|
||||
<!-- end list -->
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
|
||||
3. **Sécurité du Token :**
|
||||
|
||||
* Regardez la méthode `res.cookie` dans `/login`. Pourquoi l'option `httpOnly: true` est-elle cruciale ici ? Le JavaScript côté client (ex: `script.js`) peut-il lire ce cookie ?
|
||||
|
||||
-----
|
||||
|
||||
## É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 `authenticateAndUnpackKey` a déjà fait le travail difficile. Il a déchiffré la clé de l'utilisateur et l'a placée dans `req.user.vaultKey`.
|
||||
* Utilisez la fonction utilitaire `encryptAES(texte, clé)` définie plus haut dans le fichier.
|
||||
* Regardez la structure de la table `entries` pour savoir quoi insérer (notamment `encrypted_blob`, `iv`, `auth_tag`).
|
||||
|
||||
*Voici le flux d'une requête authentifiée que vous devez implémenter :*
|
||||
|
||||
```mermaid
|
||||
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
|
||||
```
|
||||
|
||||
### 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 `WHERE` que l'entrée appartient bien à l'utilisateur connecté (`owner_id`).
|
||||
* Utilisez `decryptAES(...)` avec la `req.user.vaultKey` pour retrouver le texte clair.
|
||||
|
||||
### 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 `SELECT` classique.
|
||||
* **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).
|
||||
|
||||
-----
|
||||
|
||||
## É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).
|
||||
|
||||
1. L'attaquant trouve le fichier `.env` (ou le code) contenant la variable `SERVER_MASTER_KEY`.
|
||||
2. 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 ?)
|
||||
|
||||
-----
|
||||
Reference in New Issue
Block a user