Aller au contenu

PD-99 — Plan d'implémentation


Navigation User Story | Document | | | -------- | --- | | [Expression de Besoin](index.md) | | | **Plan d'implémentation** | *(ce document)* | | [Critères d'acceptation](PD-99-acceptability.md) | | | [Retour d'expérience](PD-99-rex.md) | | [Retour a mobile-ios](../PD-195-epic.md) - [Index User Story](index.md)

1. Découpage en composants

1.1 Composants UI (Frontend iOS)

Composant Responsabilité
LoginScreen.tsx Écran principal de connexion (email + mot de passe)
BiometricLoginPrompt.tsx Composant de connexion facilitée par biométrie
LoginForm.tsx Formulaire avec validation temps réel
PasswordDisclaimer.tsx Rappel explicite : mot de passe irrécupérable
AuthErrorMessage.tsx Affichage erreurs neutres et non-discriminantes

1.2 Services (Frontend iOS)

Service Responsabilité
src/services/srp.ts Client SRP-6a (génération A, calcul M1, vérification M2)
src/services/api.ts Appels endpoints /auth/login/challenge et /auth/login/verify
src/services/keyDerivation.ts Dérivation K_auth via Argon2id
src/services/storage.ts Stockage sécurisé tokens (expo-secure-store)
src/services/biometric.ts Gestion Face ID / Touch ID

1.3 State Management

Store/Hook Responsabilité
useAuth.ts Hook unifié : état authentification, méthodes login/logout
useAuthStore.ts Zustand store : Master Key en mémoire
useBiometric.ts Hook biométrie : disponibilité, enrollment, vérification

1.4 Composants Backend (existants)

Composant Responsabilité
auth.controller.ts Endpoints /auth/login/challenge, /auth/login/verify
srp.service.ts Validation SRP serveur, calcul B, vérification M1
srp-session-store.service.ts Sessions SRP éphémères (Redis, TTL 5 min)

2. Flux techniques

2.1 Flux nominal : Connexion email + mot de passe

┌─────────────┐                    ┌─────────────┐                    ┌─────────────┐
│   Client    │                    │   Backend   │                    │    Redis    │
└──────┬──────┘                    └──────┬──────┘                    └──────┬──────┘
       │                                  │                                  │
       │  1. GET /auth/srp-params         │                                  │
       │─────────────────────────────────>│                                  │
       │  {N, g, version}                 │                                  │
       │<─────────────────────────────────│                                  │
       │                                  │                                  │
       │  2. Génère a (256 bits)          │                                  │
       │     A = g^a mod N                │                                  │
       │                                  │                                  │
       │  3. POST /auth/login/challenge   │                                  │
       │     {email, A}                   │                                  │
       │─────────────────────────────────>│                                  │
       │                                  │  4. Lookup user (email)          │
       │                                  │  5. Génère b, calcule B          │
       │                                  │  6. Store session ──────────────>│
       │  {salt, B}                       │                                  │
       │<─────────────────────────────────│                                  │
       │                                  │                                  │
       │  7. K_auth = Argon2id(pwd, salt) │                                  │
       │     x = SHA3-256(salt || K_auth) │                                  │
       │     u = SHA3-256(PAD(A)||PAD(B)) │                                  │
       │     S = (B - k*g^x)^(a+u*x) mod N│                                  │
       │     K = SHA3-256(S)              │                                  │
       │     M1 = SHA3-256(A || B || K)   │                                  │
       │                                  │                                  │
       │  8. POST /auth/login/verify      │                                  │
       │     {email, M1}                  │                                  │
       │─────────────────────────────────>│  9. Get session <────────────────│
       │                                  │ 10. Calcule S, K, M1_expected    │
       │                                  │ 11. Vérifie M1 === M1_expected   │
       │                                  │ 12. Génère JWT + M2              │
       │                                  │ 13. Delete session ─────────────>│
       │  {accessToken, M2}               │                                  │
       │<─────────────────────────────────│                                  │
       │                                  │                                  │
       │ 14. M2_expected = SHA3-256(...)  │                                  │
       │ 15. Vérifie M2 === M2_expected   │                                  │
       │ 16. Store JWT (SecureStore)      │                                  │
       │ 17. Unlock Master Key local      │                                  │
       │                                  │                                  │

2.2 Flux connexion facilitée (biométrie)

┌─────────────┐                    ┌─────────────┐
│   Client    │                    │  Keychain   │
└──────┬──────┘                    └──────┬──────┘
       │                                  │
       │  1. Prompt Face ID / Touch ID    │
       │─────────────────────────────────>│
       │  2. Validation biométrique       │
       │<─────────────────────────────────│
       │                                  │
       │  3. Récupère credentials chiffrés│
       │     (email + K_auth dérivée)     │
       │─────────────────────────────────>│
       │  {email, encryptedCredentials}   │
       │<─────────────────────────────────│
       │                                  │
       │  4. Déchiffre avec clé biométrie │
       │  5. Exécute flux SRP standard    │
       │     (challenge → verify)         │
       │                                  │

2.3 Flux erreur (uniforme)

Cas d'erreur possibles :
- Email inconnu
- Mot de passe incorrect
- Compte non validé
- Compte suspendu
- Erreur serveur

                    ┌──────────────────────────────┐
Tous les cas ──────>│  Délai aléatoire 50-150ms    │
                    │  + Réponse HTTP 401          │
                    │  + Message neutre identique  │
                    └──────────────────────────────┘

2bis. Diagrammes Mermaid

Graphe de dépendances des composants

graph TD
    subgraph UI["Composants UI"]
        LS[LoginScreen.tsx]
        BLP[BiometricLoginPrompt.tsx]
        LF[LoginForm.tsx]
        PD[PasswordDisclaimer.tsx]
        AEM[AuthErrorMessage.tsx]
    end

    subgraph Services["Services"]
        SRP[srp.ts]
        API[api.ts]
        KD[keyDerivation.ts]
        STO[storage.ts]
        BIO[biometric.ts]
    end

    subgraph State["State Management"]
        UA[useAuth.ts]
        UAS[useAuthStore.ts]
        UB[useBiometric.ts]
    end

    subgraph Backend["Backend existant"]
        AC[auth.controller.ts]
        SS[srp.service.ts]
        SSS[srp-session-store.service.ts]
    end

    subgraph Infra["Infrastructure"]
        RED[(Redis)]
    end

    LS --> LF
    LS --> PD
    LS --> AEM
    LS --> BLP
    LS --> UA

    BLP --> UB
    BLP --> BIO

    LF --> UA

    UA --> SRP
    UA --> API
    UA --> KD
    UA --> STO
    UA --> UAS

    UB --> BIO
    UB --> STO

    API -->|HTTP| AC
    AC --> SS
    SS --> SSS
    SSS --> RED

    SRP -.->|Argon2id| KD
    BIO -.->|SecureStore| STO

Diagramme de séquence : connexion SRP-6a nominale

sequenceDiagram
    actor U as Utilisateur
    participant LS as LoginScreen
    participant UA as useAuth
    participant KD as keyDerivation.ts
    participant SRP as srp.ts
    participant API as api.ts
    participant AC as auth.controller.ts
    participant SS as srp.service.ts
    participant SSS as srp-session-store
    participant RED as Redis

    U->>LS: Saisit email + mot de passe
    LS->>UA: login(email, password)

    Note over UA,API: Phase 1 — Challenge
    UA->>API: GET /auth/srp-params
    API->>AC: getSrpParams()
    AC-->>API: {N, g, version}
    API-->>UA: {N, g, version}

    UA->>SRP: generateEphemeral()
    SRP-->>UA: {a, A}

    UA->>API: POST /auth/login/challenge {email, A}
    API->>AC: challenge(email, A)
    AC->>SS: createChallenge(email, A)
    SS->>SSS: storeSession(sessionId, {A, b, B, v})
    SSS->>RED: SET srp:session:id TTL=300s
    RED-->>SSS: OK
    AC-->>API: {salt, B}
    API-->>UA: {salt, B}

    Note over UA,SRP: Phase 2 — Dérivation + Preuve
    UA->>KD: deriveKauth(password, salt)
    Note right of KD: Argon2id(pwd, salt)<br/>OWASP params
    KD-->>UA: K_auth

    UA->>SRP: computeProof(A, B, K_auth, salt)
    Note right of SRP: x = SHA3-256(salt || K_auth)<br/>u = SHA3-256(PAD(A)||PAD(B))<br/>S = (B - k·g^x)^(a+u·x) mod N<br/>K = SHA3-256(S)<br/>M1 = SHA3-256(A || B || K)
    SRP-->>UA: {M1, K}

    Note over UA,API: Phase 3 — Verify
    UA->>API: POST /auth/login/verify {email, M1}
    API->>AC: verify(email, M1)
    AC->>SSS: getSession(sessionId)
    SSS->>RED: GET srp:session:id
    RED-->>SSS: {A, b, B, v}
    AC->>SS: verifyProof(session, M1)
    Note right of SS: Calcule S, K, M1_expected<br/>Vérifie M1 === M1_expected
    SS-->>AC: {M2, jwt}
    AC->>SSS: deleteSession(sessionId)
    SSS->>RED: DEL srp:session:id
    AC-->>API: {accessToken, M2}
    API-->>UA: {accessToken, M2}

    Note over UA,SRP: Phase 4 — Authentification mutuelle
    UA->>SRP: verifyServer(A, M1, K, M2)
    Note right of SRP: M2_expected = SHA3-256(A||M1||K)<br/>Vérifie M2 === M2_expected
    SRP-->>UA: OK

    UA->>STO: storeToken(accessToken)
    UA->>UAS: setMasterKey(K_master)
    UA-->>LS: Authentifié
    LS-->>U: Navigation écran principal

Diagramme de séquence : connexion facilitée (biométrie)

sequenceDiagram
    actor U as Utilisateur
    participant BLP as BiometricLoginPrompt
    participant UB as useBiometric
    participant BIO as biometric.ts
    participant STO as storage.ts
    participant UA as useAuth

    U->>BLP: Appui bouton biométrie
    BLP->>UB: authenticateWithBiometric()
    UB->>BIO: promptBiometric()
    BIO->>BIO: Face ID / Touch ID
    alt Biométrie réussie
        BIO-->>UB: success
        UB->>STO: getSecureCredentials()
        Note right of STO: Keychain protégé<br/>par biométrie
        STO-->>UB: {email, encryptedKauth}
        UB->>UB: déchiffre credentials
        UB->>UA: login(email, K_auth)
        Note over UA: Exécute flux SRP standard<br/>(cf. diagramme nominal)
        UA-->>BLP: Authentifié
        BLP-->>U: Navigation écran principal
    else Biométrie annulée / échouée
        BIO-->>UB: failure
        UB-->>BLP: cancelled
        BLP-->>U: Retour écran login classique
    end

3. Mapping invariants vers mecanismes

Invariant ID Exigence Mécanisme Composant Observable Risque
INV-01 Toute authentification DOIT utiliser SRP-6a avec observable contractuel Protocole challenge/response RFC 5054, endpoints /auth/login/challenge + /auth/login/verify srp.ts, srp.service.ts Observable contractuel : 2 appels séquentiels (challenge → verify), payload sans champ password, échange A/B/M1/M2 Moyen
INV-02 Mot de passe JAMAIS transmis en clair Dérivation locale K_auth, ForbidPasswordGuard keyDerivation.ts, Backend guard Analyse réseau : payload sans champ password Critique
INV-04 Terme « mot de passe » DOIT être utilisé Label statique "Mot de passe" dans UI LoginForm.tsx Inspection UI : label exact Faible
INV-05 Connexion fondatrice et facilitée DOIVENT être visuellement différenciées UI conditionnelle avec éléments distincts (libellé, titre, icône) selon mode LoginScreen.tsx, BiometricLoginPrompt.tsx Inspection UI : au moins 1 élément distinct parmi libellé/titre/message d'aide/icône entre les 2 modes Faible
INV-06 Différenciation visuelle NE DOIT PAS persister après connexion Aucun state loginMode persisté, navigation sans paramètre de mode Navigation React, useAuth Navigation UI : écrans post-login strictement identiques quel que soit le mode utilisé, aucun indicateur visible Faible
INV-07 Biométrie DOIT être optionnelle Toggle dans Settings, fallback login fondateur toujours disponible useBiometric.ts, SettingsScreen.tsx UI propose toujours login classique même si biométrie activée Faible
INV-08 Biométrie DOIT pouvoir être désactivée globalement et/ou par appareil Toggle global + liste appareils avec flag device_id biometric.ts, storage.ts Paramètres : 2 niveaux de désactivation fonctionnels Moyen
INV-09 Messages d'erreur NE DOIVENT PAS permettre d'inférence exploitable Message unique hardcodé, délai constant 50-150ms AuthErrorMessage.tsx, Backend middleware Tests négatifs : même texte exact + même timing pour tous les cas d'échec Élevé
INV-11 Rappel d'irrécupérabilité avec texte exact obligatoire Composant PasswordDisclaimer avec texte statique de la spec PasswordDisclaimer.tsx Inspection UI : texte exact "Votre mot de passe est connu de vous seul. Il ne peut pas être récupéré ni communiqué, y compris à la demande." Faible

Note : INV-03 (aucun humain ne peut connaître le mot de passe) et INV-10 (login = acte engageant) sont des exigences de gouvernance non testables par les scénarios applicatifs (cf. spec).


4. Mapping criteres d'acceptation vers mecanismes

Critère ID Exigence spec Mécanisme(s) Composant Observable Risque
CA-01 La connexion fondatrice utilise SRP-6a Protocole challenge/response, échange A/B/M1/M2 srp.ts, useAuth.loginBackend() Traces de flux : 2 appels séquentiels /auth/login/challenge puis /auth/login/verify, payload sans champ password Moyen
CA-02 Le mot de passe n'est jamais transmis en clair Dérivation locale K_auth + SRP, ForbidPasswordGuard keyDerivation.ts, srp.ts, Backend guard Analyse réseau : payload contient uniquement email, A, M1 Critique
CA-03 Les écrans fondatrice et facilitée sont distincts UI conditionnelle selon hasBiometricCredentials LoginScreen.tsx, BiometricLoginPrompt.tsx Inspection UI : libellé/titre/icône distincts entre les 2 modes Faible
CA-04 Aucun indicateur de mode ne persiste après login Pas de state persistant du mode de connexion Navigation React, useAuth Navigation UI : écrans post-login identiques quel que soit le mode utilisé Faible
CA-05 La biométrie est désactivable globalement Toggle global dans Settings + suppression credentials SettingsScreen.tsx, biometric.ts Paramètres : switch "Biométrie" désactivable, effet immédiat Faible
CA-06 La biométrie est désactivable par appareil Flag par device_id dans SecureStore biometric.ts, storage.ts Paramètres : liste appareils avec toggle individuel Moyen
CA-07 Les messages d'erreur sont identiques Message unique hardcodé, aucune variation AuthErrorMessage.tsx Tests négatifs : même texte pour email inconnu, mdp incorrect, compte invalide Élevé
CA-08 Tout écran de login affiche le rappel d'irrécupérabilité avec texte exact Composant PasswordDisclaimer avec texte statique PasswordDisclaimer.tsx, LoginScreen.tsx Inspection UI : texte exact "Votre mot de passe est connu de vous seul..." visible Faible
CA-09 Message d'erreur générique correspond exactement au texte requis Constante ERROR_MESSAGE avec texte exact de la spec constants.ts, AuthErrorMessage.tsx Inspection UI : texte exact "La connexion a échoué. Vérifiez vos informations et réessayez." Élevé

5. Mapping tests vers mecanismes et observables

Test ID Référence spec Mécanisme(s) Point(s) d'observation Niveau
TC-INV-01 INV-01, CA-01 Header X-Auth-Protocol Observable contractuel : Header X-Auth-Protocol: SRP-6a-v1 présent dans réponses HTTP /auth/login/challenge et /auth/login/verify Integration
TC-LOGIN-01 CA-01, CA-02 SRP challenge + verify JWT retourné, user authentifié, header X-Auth-Protocol: SRP-6a-v1 vérifié Integration
TC-LOGIN-02 INV-09, CA-07 Erreur email inconnu Message exact "La connexion a échoué...", HTTP 401 E2E
TC-LOGIN-03 INV-09, CA-07 Erreur mot de passe incorrect Message strictement identique à TC-LOGIN-02 E2E
TC-LOGIN-04 INV-09 Timing email inconnu vs incorrect Delta < 50ms entre les cas Sec
TC-LOGIN-05 INV-02 Requêtes réseau Aucun champ password dans payload E2E
TC-LOGIN-06 INV-01 Vecteurs SRP RFC 5054 M1, M2 conformes aux vecteurs Unit
TC-LOGIN-07 INV-01 M2 invalide Exception levée, pas de JWT stocké Unit
TC-BIO-01 INV-07 Désactivation biométrie Login classique toujours disponible E2E
TC-BIO-02 INV-07 Biométrie sans login préalable Prompt biométrie non disponible E2E
TC-BIO-03 INV-08, CA-05 Désactivation globale Toggle effectif, credentials supprimés Integration
TC-BIO-04 INV-08, CA-06 Désactivation par appareil Toggle par device_id effectif Integration
TC-UI-01 INV-11, CA-08 Disclaimer affiché Texte exact spec visible dans DOM Unit
TC-UI-02 INV-05, CA-03 Mode biométrie vs classique Au moins 1 élément distinct (libellé/titre/icône) Unit
TC-UI-03 INV-06, CA-04 Non-persistance après login Écrans post-login identiques quel que soit le mode E2E
TC-UI-04 INV-04 Label "Mot de passe" Terme exact utilisé dans UI Unit
TC-ERR-01 CA-09 Message erreur exact Texte "La connexion a échoué. Vérifiez vos informations et réessayez." Unit
TC-ARGON-01 INV-02 Dérivation K_auth Argon2id paramètres OWASP Unit

6. Gestion des erreurs

6.1 Message d'erreur unique (conformite spec ERR-01/02, CA-07, CA-09)

Texte exact obligatoire pour tout echec d'authentification :

La connexion a échoué. Vérifiez vos informations et réessayez.

Ce message unique est affiché pour tous les cas suivants, sans distinction :

Situation interne Message utilisateur Comportement
Email inconnu Message générique unique Identique
Mot de passe incorrect Message générique unique Identique
Compte non validé Message générique unique Identique
Compte suspendu Message générique unique Identique
Session SRP expirée Message générique unique Identique
Erreur serveur Message générique unique Identique
Erreur réseau Message générique unique Identique
M2 invalide Message générique unique Identique

6.2 Cas particulier : biometrie (ERR-03)

Situation Comportement
Biométrie annulée Retour silencieux à l'écran de login (pas de message)
Biométrie échouée Retour à l'écran de login (pas de message d'erreur textuel)

6.3 Principe d'indiscernabilite (conformite INV-09, ERR-01/02)

Aucun code d'erreur distinct n'est expose ni loggue de maniere a permettre une inference.

Aspect Exigence Implementation
Code HTTP Unique 401 Unauthorized pour tout echec auth
Message utilisateur Unique Texte exact de la spec, sans variation
Timing reponse Indiscernable Delai aleatoire 50-150ms applique a tous les cas
Logs backend Non discriminants Log unique auth.login.attempt avec success: true/false uniquement
State frontend Unique Etat error sans distinction de cause

6.4 Logging securise et non-discriminant

// AUTORISÉ - Log unique non discriminant
logger.info('auth.login.attempt', {
  emailHash: sha256(email),
  success: false,  // Seule information : succès ou échec
  timestamp: Date.now()
});

// INTERDIT - Logs discriminants
logger.info('auth.login.failure.invalid_email', ...);    // JAMAIS
logger.info('auth.login.failure.wrong_password', ...);   // JAMAIS
logger.info('auth.login.failure.account_suspended', ...); // JAMAIS

Conformite CA-07/CA-09/INV-09 : Aucun element observable (message, code, timing, log) ne permet de distinguer la cause de l'echec.


7. Impacts securite

7.1 Risques identifies

Risque Probabilité Impact Mitigation
Timing attack (énumération) Moyenne Élevé Délai aléatoire 50-150ms
Brute force Moyenne Élevé Rate limiting + lockout progressif
Replay attack Faible Moyen Session TTL 5 min + nonce unique
Man-in-the-middle Faible Critique TLS 1.3 + certificate pinning
Credentials en mémoire Moyenne Élevé Zeroize après usage
Biométrie bypass Faible Moyen Biométrie = confort, pas autonome

7.2 Mitigations implementees

Mitigation Composant Vérification
ForbidPasswordGuard Backend Test E2E payload avec password rejeté
Délai aléatoire Backend middleware Mesure temps réponse
Rate limiting RateLimitGuard Test 10+ tentatives
Session Redis TTL srp-session-store.service.ts Test expiration
Certificate pinning api.ts (Expo) Test MITM
Zeroize buffers zeroize.ts Test mémoire

7.3 Journalisation securite

Événement Niveau Données loggées
Login success INFO sha256(email), sha256(userId), timestamp
Login failure WARN sha256(email), raison générique, IP hashé
Rate limit triggered WARN sha256(email), IP hashé, count
M2 mismatch ALERT sha256(email), session_id (alerte critique)
Biometric enrollment INFO sha256(userId), device_id hashé

7.4 Conformite

Exigence Statut Commentaire
RGPD (pas de stockage mot de passe) Conforme SRP-6a : seul verifier stocké
OWASP Authentication Conforme Argon2id, rate limiting, anti-énumération
ANSSI (authentification forte) Conforme Zero-Knowledge + option MFA biométrie

8. Hypotheses techniques

ID Hypothèse Impact si faux
H-01 Argon2id disponible en production (EAS build) Fallback PBKDF2 moins sécurisé en Expo Go
H-02 SecureStore disponible sur iOS Pas de stockage sécurisé = pas de biométrie
H-03 Face ID / Touch ID enrollment existant Biométrie non proposée si non configuré
H-04 Connexion réseau disponible Mode offline non supporté pour login
H-05 Backend SRP opérationnel Login impossible, fallback mode démo
H-06 Redis disponible pour sessions SRP Sessions en mémoire (moins scalable)
H-07 Horloge client synchronisée Session TTL potentiellement incorrecte

9. Points de vigilance (risques, dette, pieges)

9.1 Risques techniques

  • Latence SRP : 2 requêtes séquentielles + Argon2id (~500ms). Prévoir UX loading approprié.
  • Expo Go vs EAS : Argon2id non disponible en Expo Go. Tester en build EAS.
  • Biométrie iOS : Comportement différent Face ID vs Touch ID. Tester les deux.
  • Session Redis : Si Redis down, login impossible. Prévoir fallback ou circuit breaker.

9.2 Dette technique identifiee

  • Mode démo avec auth locale : à supprimer avant production.
  • Refresh token non implémenté : JWT unique avec expiration.
  • Rotation credentials biométrie : non planifiée.

9.3 Pieges a eviter

  • Ne jamais logger le mot de passe, même partiellement.
  • Ne jamais différencier les messages d'erreur selon le cas.
  • Ne jamais permettre la biométrie sans auth initiale préalable.
  • Toujours vérifier M2 côté client (authentification mutuelle).
  • Toujours zeroize les buffers contenant des secrets après usage.

9.4 Limites connues

  • JavaScript : strings immutables, zeroize limité pour K_auth string.
  • Expo SecureStore : 2KB max par clé, suffisant pour credentials.
  • Biométrie : contournable si device jailbreaké.

10. Hors perimetre

Élément exclu Justification User Story associée
Inscription utilisateur Déjà implémenté PD-23
Récupération mot de passe Hors scope PD-99 Future US
Réinitialisation mot de passe Hors scope PD-99 Future US
Changement mot de passe Hors scope PD-99 Future US
MFA (TOTP, SMS) Non prévu V1 Future US
SSO (OAuth, SAML) Non prévu Future US
Gestion sessions multiples Hors scope Future US
Mode offline Non supporté Future US
Dérivation K_master Déjà implémenté PD-33
Stockage K_master Keychain Déjà implémenté PD-98
Chiffrement Zero-Knowledge Déjà implémenté PD-97

Document généré selon le template PD-XX-plan.md