Aller au contenu

Spécification Technique — PD-107 Authentification Biométrique iOS

Version: 1.0 Date: 2026-02-14 Auteur: ChatGPT (GPT-5.3-codex) Statut: Prêt pour Gate 3 (CONFORMITY_CHECK) Références: PD-24/25 (SRP-6a), PD-33/97 (crypto), PD-37 (audit HSM), PD-42 (Zero-Knowledge)


1) Architecture technique

1.1 Diagramme de composants

flowchart LR
  UI[UI React Native\nSettings + Unlock Screen] --> UO[Unlock Orchestrator]
  UO --> LS[LocalAuth Adapter\nexpo-local-authentication]
  UO --> KC[Keychain Adapter\nnative iOS SecItem\nbiometryCurrentSet]
  UO --> CS[Crypto Service\nAES-GCM / HKDF / Argon2id bridge]
  UO --> PS[Policy Engine\n(timeout, boot, attempts)]
  UO --> SS[Secure Storage\nmetadata non sensible]
  UO --> AQ[Audit Queue locale\nappend-only hash chain]
  AQ --> API[NestJS Audit API]
  API --> HSM[HSM Signing Service]

  SRP[SRP-6a Auth Module] --> UO
  CS --> KM[K_encryption lifecycle\nmémoire volatile]

1.2 Flux biométrie -> déverrouillage (nominal)

sequenceDiagram
  participant U as User
  participant App as Unlock Orchestrator
  participant LA as LocalAuthentication
  participant KC as iOS Keychain
  participant C as Crypto Service
  participant A as Audit API

  U->>App: Ouvre l'app
  App->>App: Policy checks (boot/timeout/attempts/session)
  App->>LA: Prompt Face ID / Touch ID
  LA-->>App: Success
  App->>KC: Lire secret intermédiaire (biometryCurrentSet)
  KC-->>App: K_bio_intermediate
  App->>C: Déchiffrer envelope local + dériver clé session
  C-->>App: K_encryption en mémoire (volatile)
  App->>A: BIOMETRIC_AUTH_SUCCESS (queued if offline)
  App-->>U: Coffre-fort déverrouillé (<1s cible)

1.3 Intégration avec modules existants

  • PD-33/PD-97: réutilisation AES-GCM + KDF; aucun secret persistant en clair.
  • PD-24/PD-25: SRP-6a reste obligatoire pour login initial et fallback mot de passe.
  • PD-37: événements audités côté backend, horodatage serveur + signature HSM.
  • PD-42: respect de la hiérarchie de clés; la biométrie ne remplace pas le secret cryptographique racine.

1.4 Décision d'implémentation Expo/iOS

  • Cible recommandée: Expo prebuild (managed + config plugin natif).
  • Motif: besoin explicite de kSecAccessControlBiometryCurrentSet + kSecAttrAccessibleWhenUnlockedThisDeviceOnly.
  • expo-local-authentication: prompt biométrique UX.
  • Keychain sécurisé: module natif iOS (bridge) pour garantir les attributs de sécurité requis.

2) Spécifications fonctionnelles détaillées

F-107-01 — Détection capacité biométrique

  • Description: détecte disponibilité biométrie + type (FACE_ID/TOUCH_ID) et état d'enrôlement.
  • Entrées: état device iOS, permissions biométrie.
  • Sorties: BiometricCapability { available, enrolled, type, reasonIfUnavailable }.
  • Pré-conditions: app lancée.
  • Post-conditions: UI settings adaptée (toggle activable/inactivable).
  • Erreurs: capteur indisponible -> ERR-107-001.

F-107-02 — Activation biométrie

  • Description: active biométrie après confirmation mot de passe.
  • Entrées: mot de passe valide (SRP session active), consentement explicite utilisateur.
  • Sorties: état ENABLED, secret intermédiaire stocké Keychain, log BIOMETRIC_ENABLED.
  • Pré-conditions: F-107-01.available=true, utilisateur authentifié.
  • Post-conditions: clé intermédiaire protégée biométrie; compteur échecs remis à 0.
  • Erreurs: mot de passe invalide ERR-107-010, Keychain write fail ERR-107-020.

F-107-03 — Désactivation biométrie

  • Description: supprime les artefacts biométriques locaux.
  • Entrées: action utilisateur toggle OFF.
  • Sorties: état DISABLED, suppression item Keychain + metadata, log BIOMETRIC_DISABLED.
  • Pré-conditions: biométrie active.
  • Post-conditions: biométrie non utilisable sans réactivation.
  • Erreurs: suppression Keychain partielle ERR-107-021 (retry + fail-safe disabled).

F-107-04 — Déverrouillage biométrique nominal

  • Description: déverrouille via Face ID/Touch ID.
  • Entrées: session locale verrouillée, biométrie active.
  • Sorties: accès coffre, K_encryption en mémoire volatile, log BIOMETRIC_AUTH_SUCCESS.
  • Pré-conditions: policy autorise biométrie (pas boot lock, pas timeout >30 min, pas lockout).
  • Post-conditions: durée unlockDurationMs mesurée; compteur échecs reset.
  • Erreurs: annulation user ERR-107-031, mismatch biométrique ERR-107-030.

F-107-05 — Fallback mot de passe

  • Description: bascule vers authentification mot de passe SRP-6a.
  • Entrées: choix utilisateur ou policy imposée.
  • Sorties: authentification via flux SRP standard, log PASSWORD_AUTH_FALLBACK.
  • Pré-conditions: écran unlock affiché.
  • Post-conditions: si succès, accès normal + possibilité de réinitialiser état lockout.
  • Erreurs: SRP fail ERR-107-040.

F-107-06 — Gestion échecs biométriques

  • Description: verrouille biométrie locale après 3 échecs consécutifs.
  • Entrées: événements d'échec LocalAuthentication.
  • Sorties: compteur incrémenté, REQUIRE_PASSWORD si >=3, log BIOMETRIC_AUTH_FAILURE.
  • Pré-conditions: biométrie active.
  • Post-conditions: mot de passe obligatoire jusqu'à succès SRP.
  • Erreurs: désynchronisation compteur ERR-107-050.

F-107-07 — Révocation sur changement biométrique système

  • Description: invalide l'accès biométrique si set biométrique iOS modifié.
  • Entrées: lecture Keychain invalide / domaine biométrique modifié.
  • Sorties: état REVOKED, purge artefacts, fallback mot de passe, log BIOMETRIC_REVOKED_SYSTEM_CHANGE.
  • Pré-conditions: biométrie active.
  • Post-conditions: réactivation manuelle obligatoire.
  • Erreurs: impossible de distinguer cause -> ERR-107-060 (traité comme révocation).

F-107-08 — Politique timeout/inactivité

  • Description: exige mot de passe après >30 minutes d'inactivité.
  • Entrées: timestamps app lifecycle, dernier unlock fort.
  • Sorties: REQUIRE_PASSWORD si seuil dépassé.
  • Pré-conditions: app revient foreground.
  • Post-conditions: pas de déverrouillage biométrique tant que SRP non refait.
  • Erreurs: horloge incohérente ERR-107-070 (fail-safe -> mot de passe).

F-107-09 — Révocation après événements forts

  • Description: impose réactivation manuelle après réinstallation ou changement mot de passe.
  • Entrées: app installId changé, event backend PASSWORD_CHANGED.
  • Sorties: état DISABLED, logs BIOMETRIC_DISABLED reason.
  • Pré-conditions: événement détecté.
  • Post-conditions: utilisateur doit repasser F-107-02.
  • Erreurs: event backend manquant ERR-107-080 (pessimiste -> disable).

F-107-10 — Journalisation probatoire

  • Description: journalise tous événements auth, append-only, horodatés serveur, signés HSM.
  • Entrées: événements locaux, deviceId, userId, contexte.
  • Sorties: payload audit envoyé backend + ACK signé.
  • Pré-conditions: connectivité ou file locale persistante.
  • Post-conditions: aucun événement perdu (at-least-once + idempotency key).
  • Erreurs: API indisponible ERR-107-090 (queue locale chiffrée + retry exponentiel).

3) Invariants techniques (INV-107-XX)

ID Invariant Vérification technique
INV-107-01 La biométrie ne remplace pas le mot de passe cryptographique State machine impose SRP sur événements forts (boot, timeout, lockout, pwd change) + tests E2E
INV-107-02 Biométrie ne dérive jamais K_encryption directement K_encryption uniquement via envelope + secret intermédiaire, revue code crypto
INV-107-03 Biométrie déverrouille un secret encapsulé seulement Contrat de UnlockOrchestrator: jamais de deriveFromBiometryRaw
INV-107-04 Aucun bypass SRP-6a Guards backend/session; route unlock locale n'émet jamais token auth serveur
INV-107-05 Aucune clé brute persistée en clair Audit storage + tests snapshot storage vide de secrets
INV-107-06 Données biométriques brutes jamais collectées Utilisation exclusive API iOS LocalAuthentication (bool success/failure)
INV-107-07 Changement biométrie système révoque automatiquement Keychain access control biometryCurrentSet; test device réel
INV-107-08 3 échecs biométriques => mot de passe obligatoire Compteur local signé + E2E lockout
INV-107-09 Inactivité >30 min => mot de passe obligatoire Policy engine + tests timer simulé
INV-107-10 Tous événements auth tracés et signés Contrat audit + intégration backend HSM + vérification signature

4) Interfaces et contrats

4.1 Types TypeScript (services)

export type BiometryType = "FACE_ID" | "TOUCH_ID" | "NONE";

export interface BiometricCapability {
  available: boolean;
  enrolled: boolean;
  biometryType: BiometryType;
  reasonIfUnavailable?: string;
}

export interface UnlockPolicyContext {
  appBootTs: string;
  lastStrongAuthTs?: string; // password/SRP success
  lastUnlockTs?: string;
  failedBiometricAttempts: number;
  maxAttempts: 3;
  inactivityTimeoutMs: number; // 30 * 60 * 1000
  deviceRebootDetected: boolean;
  passwordChangedAt?: string;
}

export type UnlockDecision =
  | { type: "PROMPT_BIOMETRIC" }
  | { type: "REQUIRE_PASSWORD"; reason: string };

export interface KeychainSecretRecord {
  version: 1;
  userId: string;
  deviceId: string;
  wrappedUnlockBlob: string; // base64 AES-GCM ciphertext
  keyId: string;
  createdAt: string;
}

export interface BiometricService {
  getCapability(): Promise<BiometricCapability>;
  prompt(reason: string): Promise<{ ok: boolean; errorCode?: string }>;
}

export interface KeychainService {
  putBiometricProtectedSecret(alias: string, valueBase64: string): Promise<void>;
  getBiometricProtectedSecret(alias: string): Promise<string>;
  deleteSecret(alias: string): Promise<void>;
}

export interface UnlockOrchestrator {
  enableBiometricWithPassword(password: string): Promise<void>;
  disableBiometric(reason: string): Promise<void>;
  evaluateUnlockPolicy(ctx: UnlockPolicyContext): UnlockDecision;
  unlockWithBiometric(): Promise<void>;
  unlockWithPassword(password: string): Promise<void>;
}

4.2 Signatures des hooks React

export interface UseBiometricSettingsResult {
  capability: BiometricCapability;
  enabled: boolean;
  loading: boolean;
  enable: (password: string) => Promise<void>;
  disable: (reason?: string) => Promise<void>;
  refresh: () => Promise<void>;
  error?: { code: string; message: string };
}

export declare function useBiometricSettings(): UseBiometricSettingsResult;

export interface UseAppUnlockResult {
  state:
    | "LOCKED"
    | "PROMPTING_BIOMETRIC"
    | "UNLOCKED"
    | "REQUIRE_PASSWORD"
    | "REVOKED";
  failedAttempts: number;
  unlockWithBiometric: () => Promise<void>;
  unlockWithPassword: (password: string) => Promise<void>;
  resetError: () => void;
  error?: { code: string; message: string; recoverable: boolean };
}

export declare function useAppUnlock(): UseAppUnlockResult;

4.3 Schéma JSON des logs d'audit

{
  "eventId": "uuid-v7",
  "eventType": "BIOMETRIC_AUTH_SUCCESS",
  "userId": "string",
  "deviceId": "string",
  "storyId": "PD-107",
  "tsClient": "2026-02-14T12:34:56.123Z",
  "tsServer": "2026-02-14T12:34:56.456Z",
  "payload": {
    "biometryType": "FACE_ID",
    "attemptCount": 0,
    "unlockDurationMs": 420,
    "reason": "string optional"
  },
  "integrity": {
    "hash": "sha256-hex",
    "prevHash": "sha256-hex",
    "signature": "hsm-signature-base64",
    "signatureKeyId": "audit-hsm-key-2026-01"
  }
}

5) États et transitions

5.1 Diagramme d'état biométrie (lifecycle)

stateDiagram-v2
  [*] --> DISABLED
  DISABLED --> ENABLING: toggle ON + password OK
  ENABLING --> ENABLED: keychain write OK
  ENABLING --> DISABLED: erreur activation
  ENABLED --> DISABLED: toggle OFF
  ENABLED --> REVOKED: biometry changed / reinstall / password changed
  REVOKED --> DISABLED: purge complete
  DISABLED --> [*]

5.2 Machine à états du déverrouillage

stateDiagram-v2
  [*] --> STARTUP_CHECK
  STARTUP_CHECK --> REQUIRE_PASSWORD: boot|timeout|lockout|policy_fail
  STARTUP_CHECK --> PROMPT_BIOMETRIC: policy_ok
  PROMPT_BIOMETRIC --> UNLOCKED: biometric_success + decrypt_ok
  PROMPT_BIOMETRIC --> BIOMETRIC_FAILED: biometric_fail
  BIOMETRIC_FAILED --> PROMPT_BIOMETRIC: attempts < 3
  BIOMETRIC_FAILED --> REQUIRE_PASSWORD: attempts >= 3
  REQUIRE_PASSWORD --> UNLOCKED: password_success(SRP)
  REQUIRE_PASSWORD --> REQUIRE_PASSWORD: password_fail
  UNLOCKED --> LOCKED: app_background/lock_event
  LOCKED --> STARTUP_CHECK: app_foreground

6) Gestion des erreurs

Code Condition Message utilisateur Récupération
ERR-107-001 Biométrie indisponible "Face ID/Touch ID non disponible sur cet appareil." Proposer mot de passe; désactiver toggle
ERR-107-010 Mot de passe invalide à l'activation "Mot de passe incorrect." Retry limité + anti-bruteforce existant SRP
ERR-107-020 Échec écriture Keychain "Impossible d'activer la biométrie." Retry technique; si échec persistant -> support
ERR-107-021 Échec suppression Keychain "Désactivation incomplète, nouvelle tentative requise." Retry + purge au prochain lancement
ERR-107-030 Échec reconnaissance biométrique "Authentification biométrique échouée." Nouvelle tentative si <3
ERR-107-031 Prompt annulé utilisateur "Authentification annulée." Option mot de passe immédiate
ERR-107-040 Échec auth mot de passe/SRP "Impossible de vous authentifier." Retry SRP standard
ERR-107-050 Compteur échecs incohérent "Vérification de sécurité requise." Forcer mot de passe + reset compteur
ERR-107-060 Changement biométrique détecté "Vos données biométriques ont changé. Réactivez la biométrie." Forcer mot de passe + réactivation manuelle
ERR-107-070 Contexte temps invalide "Session expirée. Mot de passe requis." Forcer mot de passe
ERR-107-080 Événement sécurité manquant "Vérification de sécurité requise." Désactiver biométrie par précaution
ERR-107-090 Audit backend indisponible "Connexion limitée. Action enregistrée localement." Queue locale chiffrée + retry

7) Considérations de sécurité

7.1 STRIDE simplifié

Menace Exemple Contrôle
S (Spoofing) Usurpation identité locale Biometry iOS + fallback SRP-6a sur cas forts
T (Tampering) Altération logs locaux Queue append-only hash chain + signature backend HSM
R (Repudiation) Contestation d'action Horodatage serveur + signature HSM
I (Information Disclosure) Extraction secret storage Keychain ThisDeviceOnly + aucune clé en clair
D (Denial of Service) Lock biométrie abusif Fallback mot de passe toujours disponible
E (Elevation of Privilege) Bypass biométrie/policy Machine d'état stricte + guard clauses + tests E2E

7.2 Surface d'attaque identifiée

  • Écran unlock (brute force local, shoulder surfing).
  • Keychain access path (mauvaise config access control).
  • Mémoire runtime JS (persistence non voulue).
  • Sync audit offline->online (replay/duplication).

7.3 Contrôles implémentés

  • kSecAccessControlBiometryCurrentSet + WhenUnlockedThisDeviceOnly.
  • Timeout 30 min + lockout 3 essais.
  • Zeroization best-effort sur buffers natifs/bridge.
  • Idempotency key + nonce côté API audit.
  • Blur app en background + nettoyage écran sensible.

8) Compatibilité et contraintes

  • iOS supporté: minimum iOS 15.0, recommandé iOS 16+.
  • Expo/Hermes:
  • Hermes sans WebAssembly natif -> crypto sensible via module natif/bridge, pas wasm.
  • Expo managed pur insuffisant pour garanties Keychain fines; adopter prebuild + plugin natif.
  • Dépendances externes:
  • expo-local-authentication (prompt biométrique).
  • expo-secure-store (métadonnées non sensibles uniquement, optionnel).
  • Module natif Keychain iOS (SecItem) pour access control strict.
  • Tests device réel obligatoires:
  • changement empreinte/visage,
  • redémarrage device,
  • réinstallation app,
  • performance unlock < 1s.

Annexes d'implémentation (contrats opérationnels)

  • Les événements BIOMETRIC_ENABLED, BIOMETRIC_DISABLED, BIOMETRIC_AUTH_SUCCESS, BIOMETRIC_AUTH_FAILURE, BIOMETRIC_REVOKED_SYSTEM_CHANGE, PASSWORD_AUTH_FALLBACK sont obligatoires.
  • Aucun flux ne doit produire un état UNLOCKED sans passer par unlockWithBiometric() valide ou unlockWithPassword() SRP valide.
  • Toute erreur non classifiée doit tomber en REQUIRE_PASSWORD (fail-safe sécurisé).