PD-41 — Plan d'implémentation
📚 Navigation User Story
| Document | | | ---------- | -- | | 📋 [Spécification](PD-41-specification.md) | | | 🛠️ **Plan d'implémentation** | *(ce document)* | | 🧪 [Tests](PD-41-tests.md) | | | ✅ [Critères d'acceptation](PD-41-acceptability.md) | | | 📝 [Retour d'expérience](PD-41-rex.md) | | [← Retour à crypto-proof](../PD-189-epic.md) · [↑ Index User Story](index.md)
1. Découpage en composants
Architecture cible
src/modules/crypto/pre/
├── pre.module.ts # Module NestJS
├── pre.service.ts # Service principal (façade)
├── pre.config.ts # Configuration et validation
├── interfaces/
│ ├── pre.interface.ts # Types PRE (ReKey, Capsule, CFrag)
│ ├── pre-artefacts.interface.ts # Formats PRE-1.0 (annexe 11)
│ └── pre-errors.interface.ts # Codes d'erreur normatifs
├── providers/
│ ├── umbral.provider.ts # Wrapper Umbral (bibliothèque)
│ └── umbral-mock.provider.ts # Mock pour tests unitaires
├── validators/
│ ├── artefact.validator.ts # Validation des formats PRE-1.0
│ ├── context.validator.ts # Validation contextId cohérence
│ └── size-limit.validator.ts # Limites de taille (INV-07)
├── dto/
│ ├── generate-rekey.dto.ts # DTO generateReKey()
│ ├── re-encrypt.dto.ts # DTO reEncrypt()
│ └── validate.dto.ts # DTO validate()
└── __tests__/
├── pre.service.spec.ts # Tests unitaires
├── pre.integration.spec.ts # Tests d'intégration
└── fixtures/ # Blocs JSON PRE-1.0 référence
Composants et responsabilités
| Composant | Responsabilité |
PreModule | Module NestJS, export du service, injection des dépendances |
PreService | Façade publique : generateReKey(), reEncrypt(), validate(), getHealth() |
PreConfig | Configuration (limites de taille, timeouts), validation fail-fast au démarrage |
UmbralProvider | Wrapper de la bibliothèque Umbral, opérations cryptographiques pures |
ArtefactValidator | Validation des formats PRE-1.0 (structure, encodage, champs obligatoires) |
ContextValidator | Vérification cohérence contextId entre artefacts d'une même opération |
SizeLimitValidator | Contrôle des limites de taille (champs, artefacts, requête, cardinalité) |
2. Flux techniques
Flux N1 — Health/Readiness
Client PreService UmbralProvider
│ │ │
│──── getHealth() ──────────>│ │
│ │──── isReady() ───────────>│
│ │<──── { ready, version } ──│
│<─── { serviceVersion, │ │
│ umbralVersion, │ │
│ status: ready|not } ──│ │
Flux N2 — generateReKey()
Client PreService Validators UmbralProvider
│ │ │ │
│── generateReKey(dto) ─────>│ │ │
│ │── validateDto ─>│ │
│ │<─ ok/reject ────│ │
│ │── checkSize ───>│ │
│ │<─ ok/reject ────│ │
│ │── generateKFrags ──────────────────>│
│ │<─ KFrag[] ──────────────────────────│
│ │── buildReKeyArtefact ───────────────│
│ │── auditLog (sans secrets) ──────────│
│<── ReKey (PRE-1.0) ────────│ │ │
Flux N3 — reEncrypt()
Client PreService Validators UmbralProvider
│ │ │ │
│── reEncrypt(dto) ─────────>│ │ │
│ │── validateArtefacts ─>│ │
│ │<─ ok/reject ──────────│ │
│ │── validateContextId ─>│ │
│ │<─ ok/reject ──────────│ │
│ │── checkSize ─────────>│ │
│ │<─ ok/reject ──────────│ │
│ │── reEncrypt(capsule, kfrag) ────────>│
│ │<─ CFrag ────────────────────────────│
│ │── buildCFragArtefact ───────────────│
│ │── auditLog (sans secrets) ──────────│
│<── CFrag (PRE-1.0) ────────│ │ │
Flux N4 — validate()
Client PreService Validators
│ │ │
│── validate(artefact) ─────>│ │
│ │── validateFormat ──>│
│ │<─ errors[] ────────│
│ │── validateContext ─>│
│ │<─ errors[] ────────│
│ │── auditLog ────────│
│<── { verdict, codes[] } ───│ │
2b. Diagrammes Mermaid
Graphe de dépendances des composants
graph TD
PreModule["PreModule<br/><i>Module NestJS</i>"]
PreService["PreService<br/><i>Façade publique</i>"]
PreConfig["PreConfig<br/><i>Configuration</i>"]
UmbralProvider["UmbralProvider<br/><i>Wrapper Umbral</i>"]
ArtefactValidator["ArtefactValidator<br/><i>Formats PRE-1.0</i>"]
ContextValidator["ContextValidator<br/><i>Cohérence contextId</i>"]
SizeLimitValidator["SizeLimitValidator<br/><i>Limites de taille</i>"]
AuditLogger["AuditLogger<br/><i>Journalisation sécurisée</i>"]
PreModule --> PreService
PreModule --> PreConfig
PreService --> UmbralProvider
PreService --> ArtefactValidator
PreService --> ContextValidator
PreService --> SizeLimitValidator
PreService --> AuditLogger
PreService --> PreConfig
SizeLimitValidator --> PreConfig
ArtefactValidator -.->|"valide formats"| UmbralProvider
Séquence — generateReKey (Flux N2)
sequenceDiagram
participant C as Client
participant PS as PreService
participant AV as ArtefactValidator
participant SV as SizeLimitValidator
participant UP as UmbralProvider
participant AL as AuditLogger
C->>PS: generateReKey(dto)
PS->>AV: validateDto(dto)
alt DTO invalide
AV-->>PS: INVALID_PUBLIC_KEY / INVALID_PARAMETERS
PS->>AL: logError(contextId, errorCode)
PS-->>C: throw PreException
end
AV-->>PS: ok
PS->>SV: checkSize(dto)
alt Dépassement
SV-->>PS: PAYLOAD_TOO_LARGE
PS->>AL: logError(contextId, errorCode)
PS-->>C: throw PreException
end
SV-->>PS: ok
PS->>UP: generateKFrags(ownerPk, recipientPk, t, n)
UP-->>PS: KFrag[]
PS->>PS: buildReKeyArtefact(KFrag[], contextId)
PS->>AL: logSuccess(contextId, artefactType)
PS-->>C: ReKey (PRE-1.0)
Séquence — reEncrypt (Flux N3)
sequenceDiagram
participant C as Client
participant PS as PreService
participant AV as ArtefactValidator
participant CV as ContextValidator
participant SV as SizeLimitValidator
participant UP as UmbralProvider
participant AL as AuditLogger
C->>PS: reEncrypt(dto)
PS->>AV: validateArtefacts(capsule, reKey)
alt Artefact invalide
AV-->>PS: INVALID_ARTEFACT / INVALID_FORMAT
PS->>AL: logError(contextId, errorCode)
PS-->>C: throw PreException
end
AV-->>PS: ok
PS->>CV: validateContextId(capsule, reKey)
alt contextId divergent
CV-->>PS: INVALID_CONTEXT
PS->>AL: logError(contextId, errorCode)
PS-->>C: throw PreException
end
CV-->>PS: ok
PS->>SV: checkSize(dto)
SV-->>PS: ok
PS->>UP: reEncrypt(capsule, kfrag)
alt Erreur Umbral
UP-->>PS: REKEY_MISMATCH / CRYPTO_FAILURE
PS->>AL: logError(contextId, errorCode)
PS-->>C: throw PreException
end
UP-->>PS: CFrag
PS->>PS: buildCFragArtefact(CFrag, contextId)
PS->>AL: logSuccess(contextId, artefactType)
PS-->>C: CFrag (PRE-1.0)
3. Mapping invariants → mécanismes
| Invariant ID | Exigence | Mécanisme | Composant | Observable | Risque |
| INV-01 | Non-divulgation plaintext | Aucune opération de déchiffrement exposée ; UmbralProvider ne manipule que des artefacts PRE | UmbralProvider, PreService | Absence de méthode decrypt() ; logs sans payload | Faible |
| INV-02 | Pas de clés privées requises | API accepte uniquement des clés publiques (33 bytes compressées) ; validation stricte du format | ArtefactValidator, DTOs | Rejet si champ ressemble à clé privée (>33 bytes) | Faible |
| INV-03 | Versionnage obligatoire | Champ formatVersion requis et validé ; rejet si absent ou inconnu | ArtefactValidator | Code UNSUPPORTED_VERSION si invalide | Faible |
| INV-04 | Validation déterministe | Même entrée → même résultat ; pas de randomisation dans la validation | ArtefactValidator, ContextValidator | Tests de non-régression TC-VAL-02 | Faible |
| INV-05 | Fail-closed | Try/catch global avec rejet explicite ; aucun résultat partiel en cas d'erreur | PreService (wrapper) | Absence d'artefact partiel dans réponses d'erreur | Moyen |
| INV-06 | Liaison contextId | Extraction et comparaison de contextId sur tous les artefacts d'une opération | ContextValidator | Code INVALID_CONTEXT si divergence | Moyen |
| INV-07 | Limites de taille | Validation pré-traitement avec limites configurables (champs, artefacts, requête, cardinalité) | SizeLimitValidator, PreConfig | Code PAYLOAD_TOO_LARGE si dépassement | Moyen |
| INV-08 | Audit sans secrets | Logger dédié avec liste blanche de champs ; sanitization avant log | PreService, AuditLogger | Inspection logs : seuls contextId, keyIds, formatVersion, artefactType, errorCode, timestamp | Moyen |
4. Mapping critères d'acceptation → mécanismes
| Critère ID | Mécanisme(s) | Composant | Observable | Risque |
| CA1 | Validation formatVersion contre liste whitelist ["PRE-1.0"] | ArtefactValidator | Rejet avec UNSUPPORTED_VERSION | Faible |
| CA2 | Validation taille/encodage clé publique (44 chars base64url = 33 bytes secp256k1) | ArtefactValidator | Rejet avec INVALID_PUBLIC_KEY | Faible |
| CA3 | Vérification cohérence ReKey/clés publiques lors de reEncrypt | UmbralProvider, ArtefactValidator | Rejet avec REKEY_MISMATCH | Moyen |
| CA4 | Validation structure Capsule (ciphertextDigest 32 bytes, capsule ≤1024 bytes) | ArtefactValidator | Rejet avec INVALID_ARTEFACT | Faible |
| CA5 | Construction ReKey conforme PRE-1.0 avec tous champs obligatoires | PreService.buildReKeyArtefact() | Artefact JSON valide selon annexe 11 | Faible |
| CA6 | Construction CFrag conforme PRE-1.0 avec capsuleDigest, threshold | PreService.buildCFragArtefact() | Artefact JSON valide selon annexe 11 | Faible |
| CA7 | Retour verdict VALID/INVALID avec codes stables (table normative) | PreService.validate() | Réponse { verdict, codes[] } | Faible |
| CA8 | Liste blanche de champs loggables ; sanitization systématique | AuditLogger | Logs contiennent uniquement champs autorisés | Moyen |
| CA9 | Aucune API n'accepte de clé privée ; validation format entrées | DTOs, ArtefactValidator | Rejet si champ suspect (taille privKey) | Faible |
| CA10 | Limites configurables avec valeurs par défaut strictes | PreConfig, SizeLimitValidator | Rejet PAYLOAD_TOO_LARGE | Moyen |
| CA11 | Try/catch global + rejet explicite sans artefact partiel | PreService (décorateur/wrapper) | Réponse erreur sans données partielles | Moyen |
| CA12 | Endpoint health exposant version service, version Umbral, status | PreService.getHealth() | JSON { serviceVersion, umbralVersion, status } | Faible |
| CA13 | Extraction et comparaison contextId avant toute opération | ContextValidator | Rejet INVALID_CONTEXT si incohérence | Moyen |
| CA14 | Mapping code → situation dans le code ; tests de stabilité | Constantes, tests TC-NR-01 | Codes identiques sur mêmes erreurs | Faible |
5. Mapping tests (TC-*) → mécanismes + observables
| Test ID | Référence spec | Mécanisme(s) | Point(s) d'observation | Niveau de test visé |
| TC-NOM-01 | Flux N1, CA12 | getHealth() | JSON | Unit + Integration |
| TC-NOM-02 | Flux N2, CA5, INV-02 | generateReKey(), buildReKeyArtefact() | ReKey PRE-1.0 valide, pas de privKey | Unit + Integration |
| TC-NOM-03 | Flux N3, CA6 | reEncrypt(), buildCFragArtefact() | CFrag PRE-1.0 valide | Unit + Integration |
| TC-CTX-01 | INV-06, CA13 | ContextValidator.validateContextCoherence() | Succès si contextId cohérent | Unit |
| TC-VAL-01 | Flux N4, CA7, INV-04 | validate() | { verdict: VALID, codes: [] } | Unit |
| TC-VAL-02 | CA7, INV-04 | validate() répété | Mêmes entrées → mêmes résultats | Unit |
| TC-SEC-01 | CA8, INV-01/08 | AuditLogger, inspection logs | Absence champs interdits | Integration |
| TC-AUD-01 | CA8, INV-08 | AuditLogger | Entrée audit pour chaque opération | Integration |
| TC-SEC-02 | CA9, INV-02 | Validation DTOs | Rejet si privKey fournie | Unit |
| TC-ERR-01 | E1, CA1 | ArtefactValidator.validateFormatVersion() | UNSUPPORTED_VERSION | Unit |
| TC-ERR-02 | E2, CA2 | ArtefactValidator.validatePublicKey() | INVALID_PUBLIC_KEY | Unit |
| TC-ERR-03 | E3 | ArtefactValidator.validateThreshold() | INVALID_PARAMETERS | Unit |
| TC-ERR-04 | E4, CA3 | UmbralProvider.reEncrypt() | REKEY_MISMATCH | Unit + Integration |
| TC-ERR-05 | E5, CA4 | ArtefactValidator.validateCapsule() | INVALID_ARTEFACT | Unit |
| TC-ERR-06 | E6, CA11 | Try/catch UmbralProvider | CRYPTO_FAILURE (non déterministe) | Unit (mock) |
| TC-ERR-07 | E7, CA10, INV-07 | SizeLimitValidator | PAYLOAD_TOO_LARGE | Unit |
| TC-ERR-08 | E8, CA12 | UmbralProvider.isReady() | SERVICE_NOT_READY | Unit + Integration |
| TC-ERR-09 | E9, CA13, INV-06 | ContextValidator | INVALID_CONTEXT | Unit |
| TC-FMT-REKEY-01 | CA5 | ArtefactValidator.validateReKey() | Rejet clé publique invalide | Unit |
| TC-FMT-REKEY-02 | CA5 | ArtefactValidator.validateReKey() | Rejet threshold incohérent | Unit |
| TC-FMT-CAPS-01 | CA4 | ArtefactValidator.validateCapsule() | Rejet digest invalide | Unit |
| TC-FMT-CFRAG-01 | CA6 | ArtefactValidator.validateCFrag() | Rejet capsuleDigest invalide | Unit |
| TC-INV-01 | INV-01 | Inspection code + tests | Pas de fuite plaintext observable | Code review |
| TC-NR-01 | Stabilité codes | Tous les TC-ERR-* | Codes identiques entre versions | Regression |
| TC-NR-02 | Stabilité readiness | TC-NOM-01 | Health stable post-upgrade | Regression |
| TC-NR-03 | Non fuite logs | TC-SEC-01 | Scan logs automatisé | Regression |
| TC-NEG-01 | Robustesse | Tous validators | Rejet fail-closed, pas de crash | Security |
| TC-NEG-03 | INV-08 | AuditLogger | Pas de secret en logs même si injecté | Security |
Note : TC-NEG-02 (Flood/DoS) est hors périmètre PRE (Spec H5, Tests §7). Le rate limiting relève de la couche applicative amont.
6. Gestion des erreurs
Table des codes d'erreur (normative)
| Code | Situation | Composant déclencheur | HTTP Status suggéré |
INVALID_PARAMETERS | Paramètres incohérents (threshold t > n, etc.) | ArtefactValidator | 400 |
INVALID_PUBLIC_KEY | Clé publique mal encodée ou taille incorrecte | ArtefactValidator | 400 |
INVALID_FORMAT | Artefact ne respectant pas PRE-1.0 | ArtefactValidator | 400 |
INVALID_ARTEFACT | Capsule/CFrag structurellement invalide | ArtefactValidator | 400 |
INVALID_CONTEXT | contextId absent, divergent ou multiple | ContextValidator | 400 |
UNSUPPORTED_VERSION | formatVersion inconnu | ArtefactValidator | 400 |
REKEY_MISMATCH | ReKey incompatible avec clés/capsule | UmbralProvider | 400 |
PAYLOAD_TOO_LARGE | Dépassement limite de taille | SizeLimitValidator | 413 |
CRYPTO_FAILURE | Erreur interne Umbral | UmbralProvider | 500 |
SERVICE_NOT_READY | Umbral indisponible | PreService | 503 |
Implémentation fail-closed
// Décorateur ou wrapper pour toutes les méthodes publiques
async executeWithFailClosed<T>(operation: () => Promise<T>): Promise<T> {
try {
return await operation();
} catch (error) {
// Log sans secrets (contextId, errorCode uniquement)
this.auditLogger.logError({
contextId: extractContextIdSafe(error),
errorCode: mapToNormativeCode(error),
timestamp: new Date().toISOString(),
});
// Toujours rejeter avec code normatif, jamais de résultat partiel
throw new PreException(mapToNormativeCode(error));
}
}
7. Impacts sécurité
Risques et mitigations
| Risque | Probabilité | Impact | Mitigation |
| Fuite de clé privée dans logs | Faible | Critique | Liste blanche de champs loggables ; revue code |
| Injection de données dans contextId/metadata | Moyenne | Moyen | Validation stricte ASCII 16-128 chars ; sanitization |
| DoS via payloads volumineux | Moyenne | Moyen | Limites de taille multi-niveaux ; validation pré-décodage |
| Erreur partielle exposant état interne | Faible | Élevé | Fail-closed systématique ; pas de résultat partiel |
| Timing attack sur validation | Faible | Faible | Validation complète même en cas d'erreur précoce |
Journalisation sécurisée
Champs autorisés (liste blanche) : - contextId - issuerKeyId / recipientKeyId (identifiants, pas les clés) - formatVersion - artefactType - errorCode - timestamp
Champs interdits (liste noire normative) : - plaintext - payload - capsule (contenu complet) - kfrag / cfrag (contenu complet) - privateKey - Tout secret dérivé
- eIDAS : traçabilité des opérations PRE sans exposition de secrets
- RGPD : pas de données personnelles dans les artefacts PRE (contextId opaque)
- PKCS#11 : non applicable (PRE est purement logiciel)
8. Hypothèses techniques
| ID | Hypothèse | Impact si faux |
| H-01 | Une bibliothèque Umbral compatible Node.js/TypeScript est disponible (ex: @aspect-dev/pyumbral bindings ou implémentation JS) | Blocage complet ; nécessite évaluation alternatives (Rust+WASM, Python subprocess) |
| H-02 | Le format PRE-1.0 (annexe 11) est stable et ne changera pas pendant l'implémentation | Refactoring des validators si changement |
| H-03 | Les limites de taille (annexe B) sont suffisantes pour les cas d'usage ProbatioVault | Ajustement configuration sans impact code |
| H-04 | secp256k1 est la courbe utilisée pour toutes les clés PRE | Adaptation si autre courbe (P-256, etc.) |
| H-05 | Le service PRE est appelé uniquement depuis le backend (Spec H5) — authN/authZ/rate limiting hors périmètre PRE | Contrainte architecturale formalisée dans la spec |
| H-06 | Les opérations Umbral sont synchrones et rapides (<100ms) | Sinon : ajouter queuing/async |
9. Points de vigilance (risques, dette, pièges)
Risques identifiés
- Bibliothèque Umbral : Peu de bindings Node.js matures. Évaluer :
umbral-pre (Rust + NAPI) pyumbral via child process -
Implémentation TypeScript pure (risque sécurité)
-
Testabilité TC-ERR-06 : Impossible de provoquer déterministiquement une erreur Umbral interne. Couverture indirecte via mock.
-
Performance : Si volume élevé de reEncrypt(), considérer :
- Connection pooling vers Umbral
- Cache des validations
- Batch processing
Dette technique acceptée
- Les seuils de taille (annexe B) sont informatifs ; valeurs exactes à définir lors de l'implémentation
- Le code
NON_AUTHORIZED est hors périmètre PRE (géré par couche amont)
Pièges à éviter
- Ne jamais logger les artefacts complets (capsule, kfrag, cfrag)
- Ne jamais exposer de méthode
decrypt() même en interne - Ne jamais accepter de champ
privateKey dans les DTOs - Toujours valider le
contextId AVANT toute opération cryptographique - Toujours retourner un code normatif (pas de message d'erreur libre)
10. Hors périmètre
- Chiffrement symétrique des documents (AES-GCM) — géré ailleurs
- PKI / Gestion des identités — hors périmètre PRE
- Politiques d'accès métier (qui peut partager quoi) — couche applicative
- Réseau décentralisé NuCypher (staking, nodes) — non utilisé
- Rate limiting / AuthN — couche infrastructure amont
- Révocation des ReKeys — à définir dans une US séparée
- Résistance quantique — hors périmètre actuel