PD-277 — Spécification canonique anti-rejeu nonce et PKI certificate binding (v2)¶
1. Objectif¶
La User Story PD-277 impose la mise en conformité du module legal-pre pour supprimer les 2 non-conformités bloquantes restantes de l’audit formel PV PRE, en ajoutant des garanties contractuelles de: - non-réutilisation de nonce lors de reEncrypt(); - liaison cryptographique explicite entre un LegalReKey et les certificats PKI des deux parties; - traçabilité de conformité via régénération des faits Prolog et revalidation complète de l’audit (24 checks).
Objectif de résultat: 24/24 checks BLOQUANTS à l’état OK.
2. Périmètre / Hors périmètre¶
Inclus¶
- Extension contractuelle du modèle
LegalReKeypour porter: - historique des nonces utilisés (
used_nonces); - identifiant de certificat propriétaire (
owner_certificate_id); - identifiant de certificat destinataire (
recipient_certificate_id). - Contrôle anti-rejeu sur le flux métier
reEncrypt(). - Contrôle de binding PKI sur le flux métier
generateReKey(). - Transactionnalité atomique entre vérification nonce, insertion nonce et re-chiffrement.
- Régénération de
_generated-facts.plalignée sur les nouveaux attributs contractuels. - Couverture de tests unitaires et d’intégration des deux mécanismes.
- Vérification de non-régression sur les 22 checks déjà conformes.
- Vérification explicite qu’aucun nouvel état métier (
StatusEnum) n’est introduit pour les entités Legal*.
Exclu¶
- Les 5 checks de complétude non bloquants (cron destruction, cron expiration, ETSI trusted list, blockchain anchoring, revocation propagation).
- Toute modification du service PRE bas niveau (
pre.service.ts,umbral.provider.ts). - Toute évolution fonctionnelle hors module
legal-pre.
3. Définitions¶
LegalReKey: artefact métier représentant une clé de re-chiffrement légale.Nonce: valeur unique fournie par requête pour prévenir le rejeu.Anti-rejeu: rejet d’une opération si le nonce a déjà été utilisé dans le périmètre défini.PKI certificate binding: contrainte liant unLegalReKeyaux certificats des parties (owner/recipient).Fail-closed: en cas d’erreur de validation, l’opération est refusée.Check bloquant: règle d’audit dont l’échec rend la conformité invalide.Audit Prolog: exécution de référencerun_audit.sur les faits générés.
4. Format du nonce (contractuel)¶
- Longueur et forme: UUID v4 strict, 36 caractères, format
xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx. - Encodage: ASCII lowercase uniquement.
- Génération:
crypto.randomUUID()côté serveur uniquement, jamais côté client. - Normalisation: aucune normalisation appliquée (le format UUID v4 lowercase est canonique).
- Validation: tout nonce hors format contractuel est rejeté en fail-closed avant traitement crypto.
5. Invariants (non négociables)¶
| ID | Règle | Justification |
|---|---|---|
| INV-277-01-fail-closed | Toute anomalie de nonce, de binding certificat, de lecture de données de contrôle ou d’intégrité provoque un rejet explicite de l’opération (generateReKey ou reEncrypt). | Exigence sécurité crypto et critère d’acceptation fail-closed. |
| INV-277-02-nonce-unique | Pour un LegalReKey donné, un nonce déjà enregistré comme utilisé ne peut jamais être accepté une seconde fois. | Supprimer le risque de rejeu (CHECK 23). |
| INV-277-03-nonce-persist-before-success | Une opération reEncrypt validée en succès doit laisser une trace persistée du nonce utilisé dans le périmètre défini, avant retour de succès au client. | Empêcher une fenêtre de rejeu due à persistance absente/tardive. |
| INV-277-04-pki-binding-mandatory | La création/émission d’un LegalReKey est interdite sans owner_certificate_id et recipient_certificate_id valides et cohérents avec le mandat. | Supprimer l’absence de liaison PKI (CHECK 24). |
| INV-277-05-binding-immutability | Les identifiants de certificats liés à un LegalReKey sont immuables après création. | Éviter substitution postérieure de certificats. |
| INV-277-06-envelope-encryption | Le chiffrement at-rest est hérité de l’infrastructure (AES-256-GCM via PostgreSQL TDE ou Vault transit). Ce contrôle est validé par configuration et preuves d’exploitation, non par test unitaire de logique métier. | Invariant obligatoire domaine crypto/crypto-proof. |
| INV-277-07-audit-traceability | Les faits Prolog doivent refléter l’état contractuel réel (champs présents + règles vérifiables) avant exécution de l’audit final. | Conformité vérifiable et traçable. |
| INV-277-08-state-transitions | Aucune transition d’état métier nouvelle n’est introduite par PD-277; les règles concernent validation et rejet opérationnels uniquement. | Empêcher ambiguïté de machine à états non spécifiée. |
6. Contrat de persistance et DDL¶
Politique used_nonces¶
used_noncesest un array JSONB rattaché à chaqueLegalReKey.- TTL des entrées
used_nonces= durée de vie duLegalReKey. - Aucune purge séparée des nonces.
- À la destruction du
LegalReKey, les nonces sont détruits avec l’entité.
DDL contractuel¶
used_nonces JSONB NOT NULL DEFAULT '[]'::jsonb
owner_certificate_id VARCHAR(255) NOT NULL
recipient_certificate_id VARCHAR(255) NOT NULL
Immuabilité certificats¶
owner_certificate_idetrecipient_certificate_idsont écrits à la création.- Toute tentative de modification ultérieure est rejetée en fail-closed.
7. Atomicité et concurrence¶
Pour reEncrypt, les opérations suivantes sont exécutées de manière atomique: 1. vérification que le nonce est inédit pour le LegalReKey; 2. insertion du nonce dans used_nonces; 3. re-chiffrement.
Contrat technique: - transaction PostgreSQL avec isolation SERIALIZABLE, ou - verrouillage applicatif équivalent (advisory lock) scope LegalReKey, - et rollback complet si une étape échoue.
Aucun succès métier n’est retourné si l’une des 3 étapes échoue.
8. Faits Prolog attendus (canonique)¶
% CHECK 23 — Anti-rejeu nonce
entity_column(legal_re_key, used_nonces, jsonb).
% CHECK 24 — PKI Certificate Binding
entity_column(legal_re_key, owner_certificate_id, varchar).
entity_column(legal_re_key, recipient_certificate_id, varchar).
Ces faits sont obligatoires dans _generated-facts.pl avant exécution de run_audit..
9. Flux nominaux¶
Flux F1 — Génération de ReKey avec binding PKI¶
- Le système reçoit une demande
generateReKey. - Le système résout l’identité owner et recipient selon le mandat valide.
- Le système vérifie l’existence et la validité des certificats PKI requis pour les deux parties.
- Le système lie contractuellement
owner_certificate_idetrecipient_certificate_idauLegalReKey. - Le système persiste le
LegalReKeyavec ces liaisons. - Le système retourne un succès uniquement si toutes les validations sont satisfaites.
Résultat attendu: tout LegalReKey nouvellement créé est lié à un couple de certificats valide et immuable.
Flux F2 — Re-chiffrement avec anti-rejeu nonce¶
- Le système reçoit une demande
reEncryptcontenant un nonce. - Le système valide le nonce selon le contrat UUID v4 lowercase ASCII.
- Le système exécute atomiquement la vérification nonce inédit + insertion nonce + re-chiffrement.
- Le système retourne un succès uniquement après commit.
Résultat attendu: la première utilisation d’un nonce valide est acceptée, toute réutilisation est rejetée.
Flux F3 — Régénération des faits de conformité¶
- Les faits
_generated-facts.plsont régénérés à partir de l’état de code et de schéma en vigueur. - Les faits incluent explicitement les faits canoniques CHECK 23 et CHECK 24.
- L’audit Prolog est exécuté sur ces faits.
Résultat attendu: alignement complet entre artefacts de conformité et comportement contractuel.
Flux F4 — Validation globale de conformité¶
- Exécution de la suite de tests ciblés PD-277.
- Exécution de l’audit Prolog complet.
- Vérification de non-régression des checks déjà conformes.
- Vérification explicite d’absence de nouveaux
StatusEnumsur entités Legal*.
Résultat attendu: 24/24 checks bloquants à OK sans nouvelle transition métier.
Stratégie migration DDL (obligatoire)¶
- Modification de colonne existante: non.
- Ajouts de colonnes: oui (
used_nonces,owner_certificate_id,recipient_certificate_id). - Backfill: non requis si migration atomique et contraintes respectées.
- Down migration: exigée (suppression propre des colonnes ajoutées + restauration du schéma initial).
- Contraintes ajoutées: NOT NULL + defaults contractuels définis dans cette spécification.
- Impact triggers/workers dépendants: vérification obligatoire avant clôture.
9bis. Diagrammes Mermaid¶
Diagramme d’état — Cycle de vie d’un nonce dans le périmètre d’un LegalReKey¶
Référence : INV-277-02 (unicité nonce), INV-277-03 (persistance avant succès), INV-277-01 (fail-closed)
stateDiagram-v2
[*] --> Reçu : reEncrypt(nonce)
Reçu --> Rejeté_Format : Format non UUID v4 lowercase\n[INV-277-01 fail-closed]
Reçu --> Validé_Format : Format UUID v4 conforme
Validé_Format --> Rejeté_Rejeu : Nonce déjà dans used_nonces\n[INV-277-02 nonce-unique]
Validé_Format --> Persisté : Insertion atomique dans used_nonces\n[INV-277-03 persist-before-success]
Persisté --> Succès : Commit transaction + retour succès
Persisté --> Rollback : Échec re-chiffrement\n[INV-277-01 fail-closed]
Rollback --> [*]
Rejeté_Format --> [*]
Rejeté_Rejeu --> [*]
Succès --> [*] Diagramme de séquence — F1 : Génération de ReKey avec binding PKI¶
Référence : INV-277-04 (binding obligatoire), INV-277-05 (immuabilité), INV-277-01 (fail-closed)
sequenceDiagram
participant Client
participant LegalPreService
participant MandateValidator
participant PKI as PKI Certificate Store
participant DB as PostgreSQL
Client->>LegalPreService: generateReKey(mandateId)
LegalPreService->>MandateValidator: validateMandate(mandateId)
MandateValidator-->>LegalPreService: {ownerId, recipientId}
LegalPreService->>PKI: getCertificate(ownerId)
PKI-->>LegalPreService: ownerCertificate
LegalPreService->>PKI: getCertificate(recipientId)
PKI-->>LegalPreService: recipientCertificate
alt Certificat owner ou recipient absent/invalide [INV-277-04]
LegalPreService-->>Client: PRE_CERTIFICATE_BINDING_FAILED [INV-277-01]
else Certificats valides
LegalPreService->>DB: INSERT LegalReKey (owner_certificate_id, recipient_certificate_id, used_nonces=[])
Note over DB: Binding immuable après création [INV-277-05]
DB-->>LegalPreService: OK
LegalPreService-->>Client: LegalReKey créé avec binding PKI
end Diagramme de séquence — F2 : Re-chiffrement avec anti-rejeu nonce (transaction atomique)¶
Référence : INV-277-02 (unicité nonce), INV-277-03 (persistance avant succès), INV-277-01 (fail-closed), INV-277-06 (chiffrement at-rest)
sequenceDiagram
participant Client
participant LegalPreService
participant DB as PostgreSQL (SERIALIZABLE)
participant PRE as Module PRE (re-chiffrement)
Client->>LegalPreService: reEncrypt(reKeyId, nonce, payload)
LegalPreService->>LegalPreService: Valider format nonce UUID v4 lowercase ASCII
alt Format invalide [INV-277-01]
LegalPreService-->>Client: ERR-NONCE-FORMAT (fail-closed)
end
rect rgb(240, 248, 255)
Note over LegalPreService,DB: Transaction atomique SERIALIZABLE
LegalPreService->>DB: BEGIN SERIALIZABLE
LegalPreService->>DB: SELECT used_nonces FROM legal_re_key WHERE id = reKeyId FOR UPDATE
DB-->>LegalPreService: used_nonces[]
alt Nonce déjà dans used_nonces [INV-277-02]
LegalPreService->>DB: ROLLBACK
LegalPreService-->>Client: PRE_NONCE_REPLAY_DETECTED (fail-closed)
else Nonce inédit
LegalPreService->>DB: UPDATE used_nonces = used_nonces || nonce [INV-277-03]
LegalPreService->>PRE: reEncrypt(reKey, payload)
PRE-->>LegalPreService: ciphertext
alt Échec re-chiffrement [INV-277-01]
LegalPreService->>DB: ROLLBACK
LegalPreService-->>Client: ERR-PERSISTENCE-CONTROL (fail-closed)
else Succès
LegalPreService->>DB: COMMIT
Note over DB: Données at-rest chiffrées AES-256-GCM [INV-277-06]
LegalPreService-->>Client: ciphertext (succès)
end
end
end 10. Cas d’erreur¶
ERR-NONCE-MISSING: nonce absent -> rejet.ERR-NONCE-FORMAT: nonce hors format contractuel -> rejet.PRE_NONCE_REPLAY_DETECTED: nonce déjà utilisé pour ceLegalReKey-> rejet.PRE_CERTIFICATE_BINDING_FAILED: certificat owner ou recipient absent/invalide/incompatible mandat -> rejet.ERR-PERSISTENCE-CONTROL: impossible de persister les données de contrôle (nonces/binding) -> rejet.ERR-PROLOG-FACTS-OUTDATED: faits non régénérés ou incohérents -> audit invalide.ERR-AUDIT-NONCOMPLIANT: résultat audit < 24/24 -> livraison refusée.
11. Critères d’acceptation (testables)¶
| ID | Critère | Observable |
|---|---|---|
| CA-277-01 | Un LegalReKey créé contient owner_certificate_id et recipient_certificate_id valides. | Lecture persistée: les deux attributs existent et sont non nuls sur création réussie. |
| CA-277-02 | Toute création sans certificats valides est rejetée (PRE_CERTIFICATE_BINDING_FAILED). | generateReKey retourne erreur explicite, aucun LegalReKey valide créé. |
| CA-277-03 | Un nonce inédit UUID v4 lowercase ASCII est accepté une seule fois pour un LegalReKey donné. | 1er appel reEncrypt succès, 2e appel même nonce rejet PRE_NONCE_REPLAY_DETECTED. |
| CA-277-04 | Toute erreur nonce/certificat suit le mode fail-closed. | Aucune opération partiellement validée; statut d’erreur déterministe. |
| CA-277-05 | Les faits Prolog régénérés exposent les nouveaux mécanismes. | _generated-facts.pl contient les faits canoniques CHECK 23/24. |
| CA-277-06 | L’invariant chiffrement at-rest est satisfait via configuration infra. | Preuves de configuration TDE/Vault transit conformes (contrôle de config). |
| CA-277-07 | L’audit Prolog retourne 24/24 checks BLOQUANTS OK sans régression des 22 checks initiaux. | Sortie run_audit. sans échec sur checks 23 et 24 et sans dégradation 1..22. |
| CA-277-08 | Aucune transition d’état métier nouvelle n’est introduite. | Vérification des StatusEnum Legal* identiques avant/après PD-277. |
| CA-277-09 | La migration TypeORM de la story s’exécute sans erreur en montée/descente. | Commandes migration up/down terminent avec code succès. |
12. Scénarios de test (Given / When / Then)¶
-
ST-277-01 (PKI nominal)
Given un mandat valide et deux certificats valides
WhengenerateReKeyest exécuté
Then leLegalReKeyest créé avecowner_certificate_id+recipient_certificate_idpersistés. -
ST-277-02 (PKI owner absent/invalide)
Given un mandat valide et certificat owner absent ou invalide
WhengenerateReKeyest exécuté
Then l’opération est rejetée avecPRE_CERTIFICATE_BINDING_FAILED. -
ST-277-03 (PKI recipient absent/invalide)
Given un mandat valide et certificat recipient absent ou invalide
WhengenerateReKeyest exécuté
Then l’opération est rejetée avecPRE_CERTIFICATE_BINDING_FAILED. -
ST-277-04 (Nonce premier usage)
Given unLegalReKeyvalide et un nonce UUID v4 lowercase ASCII inédit
WhenreEncryptest exécuté
Then l’opération réussit et le nonce est marqué utilisé dansused_nonces. -
ST-277-05 (Nonce rejeu)
Given unLegalReKeyvalide et un nonce déjà utilisé
WhenreEncryptest exécuté
Then l’opération est rejetée (PRE_NONCE_REPLAY_DETECTED). -
ST-277-06 (Atomicité et concurrence)
Given plusieurs requêtes concurrentes avec le même nonce et le mêmeLegalReKey
WhenreEncryptest exécuté en parallèle
Then au plus une requête réussit, les autres sont rejetées, et aucun état intermédiaire incohérent n’est persisté. -
ST-277-07 (Conformité Prolog)
Given les faits Prolog régénérés après modifications
When l’audit complet est lancé
Then les checks 23 et 24 sont OK et le total bloquant est 24/24. -
ST-277-08 (Pas de nouvel état métier)
Given le référentiel pré-PD-277 desStatusEnumdes entités Legal*
When la suite complète est exécutée post-PD-277
Then aucun nouvel état/transition n’apparaît dans les enums métiers.
13. Hypothèses explicites¶
| ID | Hypothèse | Impact si faux |
|---|---|---|
| H-277-01 | Le module cible est ProbatioVault-backend (NestJS + TypeORM + PostgreSQL). | Si faux, la présente spec technique est invalide et doit être réémise. |
| H-277-02 | MandateValidatorService reste la source de vérité pour validation PKI côté domaine légal. | Si faux, la règle de binding doit être redéfinie et tous les tests adaptés. |
| H-277-03 | Le périmètre anti-rejeu est le LegalReKey (et non global plateforme). | Si faux, les règles de collision nonce changent et les critères CA-277-03 doivent être revus. |
| H-277-04 | L’ajout des champs n’impose pas de refonte des APIs externes consommées par d’autres domaines. | Si faux, une extension de périmètre inter-modules est nécessaire. |
| H-277-05 | Les checks Prolog 23/24 sont dérivés des faits applicatifs régénérés. | Si faux, la preuve de conformité 24/24 est non recevable. |
14. Contraintes techniques¶
Contraintes techniques (obligatoire)¶
- Projet cible:
ProbatioVault-backend. - Stack contractuelle: NestJS + TypeORM + PostgreSQL.
- Module impacté:
legal-preuniquement. - Interdiction de changement de stack (pas de Spring Boot, pas de Swift/SwiftUI).
- Comparaisons sécuritaires conformes aux learnings (
crypto.timingSafeEqual()lorsque applicable).
Bornes numériques obligatoires¶
| Paramètre | Valeur défaut | Min | Max | Unité | Comportement hors bornes |
|---|---|---|---|---|---|
| Checks bloquants requis à la clôture | 24 | 24 | 24 | checks | Résultat < 24 -> non conforme, livraison refusée |
| Checks bloquants total de référence | 24 | 24 | 24 | checks | Divergence -> audit invalide tant que référentiel non clarifié |
| Longueur nonce UUID v4 | 36 | 36 | 36 | caractères | Rejet ERR-NONCE-FORMAT |
SLA temporels¶
- Aucune transition temporelle métier nouvelle introduite par PD-277.
- TTL
used_noncesaligné sur durée de vie duLegalReKey.
Transitions inverses (machine à états)¶
- Aucune transition retour applicable introduite par PD-277.
- Aucun nouvel état métier explicite.
Contraintes inter-modules¶
- Interaction identifiée avec composants PKI/mandat (validation inter-service).
- Aucune contrainte cross-route hors module explicitement ajoutée par PD-277.
15. Références¶
- Epic : PD-189 (CRYPTO)
- JIRA :
PD-277 - Repos concernés :
ProbatioVault-backend(principal),ProbatioVault-doc(template documentaire) - Documents associés :
- Expression de besoin PD-277 (fournie)
- Learnings: PD-63, PD-41, PD-238
- Commande d’audit Prolog:
swipl -l _generated-facts.pl -l pv_pre_compliance.pl -g "run_audit." -t "halt."