Retour
/conformite/oracle-temporel-authentification.md

Fuite par le temps à l'authentification — preuve et méthode

Comment nous savons que l'authentification de l'interface web ne révèle pas, par sa durée de réponse, ce que son message d'erreur refuse de dire. Méthodologie, défauts trouvés, données brutes, et commandes pour tout rejouer.

Ce document répond à une question précise, posée par un client, un auditeur, ou l'équipe elle-même dans deux ans :

« Comment savez-vous que l'authentification ne fuit pas par le timing ? »

Il donne la réponse avec ses données, et non sous forme d'affirmation.


1. Ce qui est en jeu

L'écran de connexion affiche un message d'échec invariant : il ne dit jamais laquelle des causes s'applique — compte inconnu, verrouillé, expiré, mot de passe faux, ou compte valide sans rôle LFSi. Ce choix est délibéré et documenté ; il empêche deux fuites :

Mais un message invariant ne suffit pas. Si un cas de refus traverse moins de travail que les autres, l'écart de durée le trahit, et l'oracle se rétablit par le chronomètre. C'est cette seconde propriété — l'invariance en temps — que ce document établit.


2. Deux fuites réelles, trouvées et fermées

Aucune n'avait été détectée par les tests, les linters ou la revue de code. Seule la mesure les a révélées.

Défaut Écart mesuré Correctif
roleOf() n'était appelé que pour le cas « sans rôle » — deux lectures de fichiers de plus que les autres refus +71 µs de médiane 6f597c7 — résolution inconditionnelle, chemin de refus unique
Le champ d'un compte verrouillé (préfixé !) était passé à crypt(3), qui échouait sans calculer yescrypt 0,40 ms contre 7,5 ms — facteur 19 eda86e3 — leurre utilisé dès que l'empreinte n'est pas exploitable

Le second est le plus grave : « ce compte est verrouillé » devenait lisible à distance, avec 7 ms d'écart quand la gigue d'un réseau local se compte en dixièmes de milliseconde. Un seul échantillon suffisait.

Au passage, le leurre lui-même (dummyHash, censé faire payer un calcul complet aux comptes inexistants) n'était pas une chaîne de réglage yescrypt valide : crypt(3) y répondait par son jeton d'échec, donc sans calculer. Tout compte inexistant empruntait le chemin court. Il est désormais produit par crypt(3) lui-même et sa calculabilité est vérifiée par un test.


3. Méthode

Le banc mesure six cas — les cinq refus plus le succès — et compare leurs distributions de latence.

Entrelacement. Un tour = un échantillon de chaque cas. Mesurer les cas en séquence attribuerait au premier toute dérive de la machine pendant la campagne.

Ordre tiré au hasard à chaque tour, graine journalisée. Sans mélange, un effet de position dans le tour est indiscernable d'un effet de chemin de code — les deux se sont révélés de magnitude comparable. Sans graine, deux campagnes ne seraient plus comparables et aucune anomalie ne serait ré-observable.

Assertion d'issue avant chronométrage. Chaque cas doit produire son motif attendu, sous peine d'échec immédiat : « six chemins distincts » est une garantie, pas une espérance.

Refus de mesurer plutôt que mesurer faux. Le banc s'abstient, en disant pourquoi, si la libc ne sait pas calculer le format du leurre, ou si l'empreinte fournie n'est pas celle du mot de passe présenté.

Critères de lecture, chiffrés

Le banc calcule et tranche lui-même :

  1. Oracle plausible si l'étendue entre les six médianes dépasse le tiers de la dispersion p10–p90 intra-cas — l'écart entre cas devient alors du même ordre que le bruit propre à chacun, donc séparable par moyennage.
  2. Suspect même sous le seuil si un même cas occupe le même extrême sur deux campagnes — un chemin plus court se classe toujours du même côté.

Critère abandonné, et pourquoi : « un écart qui change de signe est du bruit ». Il comparait chaque cas à une référence elle-même bruitée ; quand celle-ci dérive, les six écarts se décalent en bloc. Une campagne réelle les a tous rendus négatifs. Le signe ne jugeait rien.


4. Données brutes

Toutes les campagnes ci-dessous ont été menées sur le nœud de build (aarch64, libxcrypt avec yescrypt), 100 tours après chauffe.

4.1 Campagnes retenues — instrument à ordre mélangé

Référence de comparaison : « mot de passe erroné », c'est-à-dire ce qu'un attaquant obtient en tâtonnant.

# graine rapport étendue/dispersion plus rapide plus lent « sans rôle » vs réf.
A 1786874463865153751 0,219 mdp erroné expiré +41,1 µs
B 1786874488064079929 0,095 mdp erroné verrouillé +26,5 µs
C 1786874521955218987 0,069 verrouillé absent +1,2 µs
D 1786874548956841500 0,091 mdp erroné absent +6,5 µs
E 1786876191158328033 0,046 mdp erroné absent +2,8 µs

Seuil : 0,333. Les cinq campagnes sont sous le seuil, la meilleure d'un facteur sept.

Campagne E, détail (la plus propre ; mot de passe de référence de même longueur que les autres) :

cas p10 médiane p90 écart / réf.
1 absent 7,005 ms 7,161 ms 7,459 ms +20,8 µs
2 verrouillé 6,996 ms 7,151 ms 7,388 ms +10,8 µs
3 expiré 7,013 ms 7,157 ms 7,536 ms +16,6 µs
4 mdp erroné 6,989 ms 7,140 ms 7,610 ms référence
5 sans rôle 7,020 ms 7,143 ms 7,327 ms +2,8 µs
6 succès 7,032 ms 7,155 ms 7,480 ms +14,6 µs

Médianes par position dans le tour : 7,151 / 7,148 / 7,148 / 7,150 / 7,144 / 7,173 ms — l'ordre étant mélangé, un écart ici désignerait la machine, non un chemin.

4.2 Combinaison des cinq campagnes

Pour la paire qui compte — « sans rôle » contre « mot de passe erroné » — l'erreur-type de la médiane est estimée depuis la dispersion (p90 − p10 ≈ 2,56 σ, ET ≈ 1,253 σ/√n) :

campagne écart ET écart/ET
A +41,1 µs 22,2 µs +1,85
B +26,5 µs 23,2 µs +1,14
C +1,2 µs 19,1 µs +0,06
D +6,5 µs 21,4 µs +0,30
E +2,8 µs 22,2 µs +0,12

Pondération inverse-variance : +14,4 µs ± 9,6 µs, soit z = 1,49 — sous le seuil usuel de 2, et décroissant à mesure que l'instrument s'améliorait (1,72 → 1,60 → 1,49), ce qui est la signature d'un résidu d'instrument et non d'un chemin.

Sur la campagne E, l'étendue entre les six cas vaut 0,94 erreur-type d'une seule médiane, et l'étendue par position la dépasse : le bruit de la machine domine toute différence de chemin.

4.3 Campagnes écartées, et pourquoi

Elles figurent ici parce qu'un dossier qui ne montrerait que les mesures favorables ne vaudrait rien.

campagnes défaut de l'instrument conséquence
avant c1f540b ordre d'entrelacement fixe position dans le tour et cas confondus — étendues de même ordre (68/74/25/32 µs contre 100/45/27/40 µs)
avant 4fc4122 l'empreinte de la fixture n'était pas celle du mot de passe présenté !ok l'emportait avant role == "" : trois cas sur six retombaient sur le même chemin
avant 8538267 mot de passe de référence plus court que les autres (7 octets contre 10) le cas de référence différait par deux choses à la fois
avant eda86e3 dummyHash non calculable le cas « compte absent » empruntait un chemin court

Chacune de ces campagnes a imprimé un tableau parfaitement crédible. C'est la raison d'être des garde-fous décrits en §3.


5. Rejouer la mesure

Sur le nœud de build ou la cible — partout où crypt(3) calcule yescrypt :

h="$(mkpasswd -m yescrypt motdepasse)"
LFSI_MESURE_LATENCE=1 LFSI_LATENCE_HASH="$h" \
  go test -count=1 -run TestMesureLatenceEntrelacee -v ./cmd/privhelper

Pour rejouer une campagne à l'identique, ajouter sa graine :

LFSI_LATENCE_GRAINE=1786876191158328033

Le banc refuse de s'exécuter — en indiquant le motif — si l'hôte ne sait pas calculer le format du leurre, ou si l'empreinte ne correspond pas au mot de passe de la mesure.


6. Conclusion, et ce qu'elle ne dit pas

Les deux fuites identifiées sont fermées, et la propriété d'invariance temporelle est vérifiée par cinq campagnes indépendantes, toutes sous le seuil, avec un résidu de +14,4 µs à z = 1,49.

Sur l'exploitabilité du résidu : 14 µs face à une gigue réseau de plusieurs centaines de microsecondes, avec un verrouillage anti-martèlement à 5 tentatives par quart d'heure, par compte et par adresse, place toute exploitation statistique hors de portée pratique.

Ce que ce document n'affirme pas :

Toute modification de actionAuth doit rejouer cette mesure — la règle est inscrite au brief de réalisation, avec ses critères chiffrés.