Files
ENI-JSAdvanced_14/README.md
2025-12-18 15:28:26 +01:00

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 :

  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 :

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 :
    npm install
    
  3. Lancez le serveur :
    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 ?
    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 ?
        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 :

    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 ?)