PD-38 — Retour d'expérience (REX)
📚 Navigation User Story
| Document | | | ---------- | -- | | 📋 [Spécification](PD-38-specification.md) | | | 🛠️ [Plan d'implémentation](PD-38-plan.md) | | | ✅ [Critères d'acceptation](PD-38-acceptability.md) | | | 📝 **Retour d'expérience** | *(ce document)* | [← Retour à crypto-proof](../PD-189-epic.md) · [↑ Index User Story](index.md)
1. Résumé exécutif
| Élément | Valeur |
| Objectif initial | Implémenter le calcul d'empreinte SHA3-256 sur documents chiffrés pour garantir leur intégrité probatoire |
| Résultat obtenu | Fonctionnalité complète après correction des 3 écarts identifiés |
| Verdict d'acceptabilité | ✅ ACCEPTÉ (2026-01-05) |
| Tests contractuels | 4 suites, 113 tests PASS |
L'implémentation initiale présentait un écart bloquant majeur : le hash était fourni par le client au lieu d'être calculé côté serveur sur le fichier chiffré stocké sur S3. Après correction, la garantie probatoire est assurée.
2. Points fluides
- Spécification claire : Les exigences cryptographiques (SHA3-256, NIST FIPS 202) étaient bien définies.
- Bibliothèques matures :
js-sha3 et node:crypto ont fourni les primitives nécessaires sans difficulté. - Architecture modulaire : La séparation
HashService / HashStreamService permet une réutilisation aisée. - Tests vectoriels : Les vecteurs NIST ont permis une validation cryptographique fiable.
- Streaming natif : Le support des fichiers volumineux via streaming était déjà prévu dans le plan.
3. Points difficiles
- Calcul hash côté serveur vs client : L'implémentation initiale passait le hash fourni par le client, ce qui cassait la garantie probatoire. Cette erreur n'a été détectée qu'en revue d'acceptabilité.
- Dépendances inter-modules : La correction E-01 a nécessité l'import de
UploadModule dans DocumentsModule pour accéder à HashStreamService et S3MultipartAdapter. - Mocks de tests : L'ajout des dépendances S3 a imposé la mise à jour de tous les mocks dans les tests du service et du controller.
- Comparaison constant-time : L'utilisation de
=== pour comparer les hashes n'a pas été immédiatement identifiée comme un problème de sécurité.
4. Hypothèses révélées tardivement
| Hypothèse | Impact |
| Le hash devait être calculé côté backend | L'implémentation initiale supposait que le client pouvait fournir le hash, ce qui contredisait l'objectif probatoire |
L'endpoint /documents/:id/hash était nécessaire | Non explicité initialement comme endpoint dédié, mais requis pour la restitution au client |
| La comparaison des hashes devait être constant-time | Mentionné dans le plan mais non vérifié lors de l'implémentation |
| Le fichier doit être récupéré depuis S3 pour calcul | Le flux d'upload stocke d'abord sur S3, le hash est calculé a posteriori |
5. Invariants complexes
| Invariant | Complexité | Référence |
| Hash calculé sur ciphertext uniquement | Moyenne | Nécessite accès au fichier S3 après upload, pas au flux client direct |
| Unicité hash par utilisateur | Faible | Contrainte SQL + vérification RLS avant insertion |
| Hash jamais en clair dans logs | Faible | Validation par revue de code |
| Fichier vide = rejet systématique | Faible | Validation en entrée de HashService |
6. Dette technique
| Dette | Impact | Priorité |
| Pas de tests d'intégration E2E avec S3 réel | Les mocks simulent S3, un test avec MinIO serait plus robuste | Basse |
hashFile() deprecated mais non supprimé | Alias conservé pour compatibilité, à supprimer dans une prochaine version | Basse |
| Absence de métriques de performance | Pas de mesure du temps de calcul sur gros fichiers en production | Basse |
7. Risques résiduels
| Risque | Probabilité | Impact | Mitigation |
| Performance sur fichiers > 1GB | Faible | Moyen | Streaming implémenté, à valider en charge |
| Erreur S3 pendant calcul hash | Faible | Moyen | L'opération échoue proprement, fichier déjà uploadé reste sur S3 |
| Collision SHA3-256 | Négligeable | Critique | Probabilité 2^-128, acceptable pour usage probatoire |
Timing attack sur verify() | Très faible | Faible | Mitigé par timingSafeEqual |
8. Améliorations de processus
| Amélioration | Bénéfice attendu |
| Revue d'acceptabilité dès la fin du dev | Détection précoce des écarts bloquants comme E-01 |
| Checklist "source de confiance" | Vérifier systématiquement que les données critiques (hash, signatures) sont calculées côté serveur |
| Tests de sécurité automatisés | Intégrer des vérifications constant-time dans le linter ou les tests |
| Template de test hash | Fournir des vecteurs NIST pré-intégrés pour toute US cryptographique |
9. Enseignements clés
-
Le hash probatoire DOIT être calculé côté serveur : Toute donnée ayant valeur probatoire ne peut jamais être fournie par le client sans revalidation.
-
Les dépendances inter-modules doivent être anticipées : L'accès à S3 depuis le module Documents a nécessité un refactoring des imports et exports.
-
La sécurité des comparaisons cryptographiques n'est pas intuitive : L'utilisation de === sur des hashes semble correcte mais expose à des timing attacks.
-
La revue d'acceptabilité est un filet de sécurité essentiel : Sans cette revue, l'écart E-01 aurait pu passer en production, compromettant la valeur probatoire.
-
Les tests unitaires ne suffisent pas à valider l'architecture : Les tests passaient avec le hash client, seule l'analyse du flux métier a révélé le problème.
Annexes
Commits de référence
| Commit | Description |
4cd7be7 | Fix E-01/E-02/E-03 : calcul hash côté serveur, endpoint dédié, constant-time |
761ef85 | Fix Prettier formatting |
Fichiers modifiés
| Fichier | Modification |
src/modules/crypto/hash.service.ts | Ajout timingSafeEqual dans verify() |
src/modules/documents/services/document-secure.service.ts | Calcul hash depuis S3 via HashStreamService |
src/modules/documents/documents.controller.ts | Ajout endpoint GET /documents/:id/hash |
src/modules/documents/dto/create-document-secure-request.dto.ts | Suppression fileHash du DTO client |
src/modules/documents/documents.module.ts | Import UploadModule |
src/modules/upload/upload.module.ts | Export S3MultipartAdapter |