Aller au contenu

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 LegalReKey pour 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.pl aligné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 un LegalReKey aux 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érence run_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_nonces est un array JSONB rattaché à chaque LegalReKey.
  • TTL des entrées used_nonces = durée de vie du LegalReKey.
  • 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_id et recipient_certificate_id sont é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

  1. Le système reçoit une demande generateReKey.
  2. Le système résout l’identité owner et recipient selon le mandat valide.
  3. Le système vérifie l’existence et la validité des certificats PKI requis pour les deux parties.
  4. Le système lie contractuellement owner_certificate_id et recipient_certificate_id au LegalReKey.
  5. Le système persiste le LegalReKey avec ces liaisons.
  6. 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

  1. Le système reçoit une demande reEncrypt contenant un nonce.
  2. Le système valide le nonce selon le contrat UUID v4 lowercase ASCII.
  3. Le système exécute atomiquement la vérification nonce inédit + insertion nonce + re-chiffrement.
  4. 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é

  1. Les faits _generated-facts.pl sont régénérés à partir de l’état de code et de schéma en vigueur.
  2. Les faits incluent explicitement les faits canoniques CHECK 23 et CHECK 24.
  3. 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é

  1. Exécution de la suite de tests ciblés PD-277.
  2. Exécution de l’audit Prolog complet.
  3. Vérification de non-régression des checks déjà conformes.
  4. Vérification explicite d’absence de nouveaux StatusEnum sur 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 ce LegalReKey -> 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
    When generateReKey est exécuté
    Then le LegalReKey est créé avec owner_certificate_id + recipient_certificate_id persistés.

  • ST-277-02 (PKI owner absent/invalide)
    Given un mandat valide et certificat owner absent ou invalide
    When generateReKey est exécuté
    Then l’opération est rejetée avec PRE_CERTIFICATE_BINDING_FAILED.

  • ST-277-03 (PKI recipient absent/invalide)
    Given un mandat valide et certificat recipient absent ou invalide
    When generateReKey est exécuté
    Then l’opération est rejetée avec PRE_CERTIFICATE_BINDING_FAILED.

  • ST-277-04 (Nonce premier usage)
    Given un LegalReKey valide et un nonce UUID v4 lowercase ASCII inédit
    When reEncrypt est exécuté
    Then l’opération réussit et le nonce est marqué utilisé dans used_nonces.

  • ST-277-05 (Nonce rejeu)
    Given un LegalReKey valide et un nonce déjà utilisé
    When reEncrypt est 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ême LegalReKey
    When reEncrypt est 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 des StatusEnum des 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-pre uniquement.
  • 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_nonces aligné sur durée de vie du LegalReKey.

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."