Aller au contenu

PD-276 — PV Envelope : Conformité RFC 9106 (Argon2id) et Metadata Binding contractuel

1. Objectif

Cette user story doit porter la conformité formelle PV Envelope de 21/24 à 24/24 checks Prolog, sans modifier les règles de vérification, en contractualisant :

  • la présence d’un service serveur Argon2Service de validation de paramètres (et non de dérivation de clé métier),
  • l’exposition d’une configuration Argon2id centralisée et vérifiable,
  • le metadata binding cryptographique anti-substitution sur les enveloppes,
  • la migration de schéma associée dans vault_secure.key_envelopes.

Contexte technique cible (obligatoire) : ProbatioVault-backend = NestJS + TypeORM + PostgreSQL 16+.

2. Périmètre / Hors périmètre

Inclus

  • Validation serveur contractuelle des paramètres Argon2id déclarés par le client.
  • Exposition des paramètres Argon2id de référence via un point d’accès backend.
  • Ajout de metadata_tag sur vault_secure.key_envelopes avec migration progressive en 2 temps.
  • Vérification du metadata tag avant tout déchiffrement logique d’enveloppe.
  • Mise à jour du générateur de faits pour produire les faits Prolog requis.
  • Maintien des 21 checks déjà conformes.
  • Contrôles probatoires de non-présence de secrets en clair (scans mémoire post-opération + vérification d’artefacts temporaires), sans prétendre à une preuve exhaustive.

Exclu

  • Dérivation Argon2id du mot de passe côté serveur (interdite).
  • Changement des fichiers Prolog de contrôle (pv_envelope_compliance.pl).
  • Refonte des checks déjà verts.
  • Déploiement d’une librairie native Argon2 côté backend.
  • Paramétrage adaptatif client par type d’appareil (hors périmètre de cette story).
  • Preuve absolue/exhaustive d’absence de secrets en clair sur tous chemins d’exécution (hors périmètre d’automatisation).

3. Définitions

  • Argon2id : fonction KDF conforme RFC 9106.
  • OWASP 2024 minimums : plancher de sécurité pour paramètres KDF.
  • Metadata Binding : HMAC liant métadonnées critiques et enveloppe pour empêcher la substitution.
  • metadata_tag : tag binaire de binding stocké en base.
  • K_binding : clé de binding dérivée de K_master_user via HKDF avec contexte dédié.
  • Check Prolog : règle formelle de conformité auditable.
  • State LEGACY : enveloppe historique sans metadata_tag (phase transitoire).
  • State BOUND : enveloppe avec metadata_tag valide.
  • State TAMPERED : enveloppe avec tag invalide détecté.
  • État terminal : état sans transition sortante autorisée.

4. Invariants (non négociables)

ID Règle Justification
INV-276-01-zeroknowledge Le backend ne dérive jamais de clé utilisateur à partir d’un password ; il ne fait que valider des paramètres déclarés. Respect architecture Zero-Knowledge
INV-276-02-argon2-minima Toute configuration client avec memory < 65536 KiB, iterations < 3, parallelism < 4, type != 2, hashLength != 32 bytes est rejetée. Conformité RFC 9106 + OWASP
INV-276-03-argon2-bounds Paramètres contractuels backend : memory défaut 65536 KiB, bornes [65536, 1048576] KiB; iterations défaut 3, bornes [3, 10]; parallelism défaut 4, bornes [4, 16]; type défaut 2, bornes [2,2]; hashLength défaut 32 bytes, bornes [32,32]. Hors bornes: erreur de validation, rejet. Bornes numériques testables, anti-dérive
INV-276-04-config-centralisee La configuration Argon2id de référence est unique, centralisée, et exposable telle quelle par API backend. Vérifiabilité Prolog + cohérence client/serveur
INV-276-05-metadata-binding Chaque enveloppe post-migration complète doit porter un metadata_tag (32 bytes) calculé sur algorithm || version || envelope_type || device_id. Prévention substitution inter-device
INV-276-06-separation-cles K_binding doit être dérivée via HKDF avec contexte strict ProbatioVault::MetadataBinding::v1; réutilisation directe de K_master_user interdite. Isolation cryptographique
INV-276-07-verify-before-access Toute opération d’accès logique à une enveloppe vérifie d’abord metadata_tag; tag invalide => accès refusé (erreur sécurité). Intégrité obligatoire
INV-276-08-envelope-encryption Tout artefact cryptographique temporaire (clé, fragment, DEK, ReKey) est chiffré au repos (AES-256-GCM ou HSM envelope). Aucun secret en clair en base. Statut PD-276 : exigence probatoire partielle (preuves attendues : scans mémoire post-opération + vérification artefacts temporaires), et non preuve exhaustive universelle. Invariant crypto-proof obligatoire, avec modalité probatoire explicite
INV-276-09-prolog-facts Les faits formels requis existent : service(argon2, 'Argon2Service'), service_method(argon2, validateParams), entity_column(key_envelope, metadata_tag, bytea). Le serveur valide les paramètres Argon2id sans dériver de clé métier. Passage des checks 10/22/19 + cohérence zero-knowledge
INV-276-10-non-regression Les checks précédemment conformes restent conformes ; toute baisse de conformité est non acceptable. Baseline minimale contractuelle : les 21 checks existants conservent exactement le même statut OK/KO avant et après déploiement. Sécurité de régression
INV-276-11-state-machine Les transitions d’état de binding sont exhaustivement documentées (autorisées ou interdites) ; état terminal explicite interdit en sortie. Exigence gates transitions

5. Flux nominaux

Flux F1 — Validation Argon2id (serveur)

  1. Le client soumet les paramètres KDF déclarés lors d’enregistrement/rotation.
  2. Le backend compare aux bornes contractuelles (INV-276-02, INV-276-03).
  3. Si conformes, la requête continue ; sinon rejet explicite.
  4. Le backend reste non-dérivant vis-à-vis du password (INV-276-01).

Flux F2 — Exposition configuration Argon2id

  1. Un appel API interne/externe autorisé lit la configuration centralisée.
  2. Les valeurs renvoyées correspondent exactement au contrat INV-276-03.
  3. Le client peut s’aligner sans divergence locale non déclarée.

Flux F3 — Création/rotation d’enveloppe avec metadata binding

  1. Le backend prépare/valide les métadonnées (algorithm, version, envelope_type, device_id).
  2. Le binding cryptographique est calculé avec K_binding dérivée (INV-276-06).
  3. metadata_tag est persisté dans vault_secure.key_envelopes.metadata_tag.
  4. En phase finale de migration, metadata_tag est obligatoire (NOT NULL).

Flux F4 — Accès enveloppe (unwrap logique)

  1. Lecture de l’enveloppe et de son metadata_tag.
  2. Vérification du tag avant restitution de toute donnée exploitable.
  3. En cas de validité : accès autorisé ; invalidité : rejet sécurité immédiat (< 100 ms de bout en bout pour la décision de rejet).

Flux F5 — Conformité formelle

  1. Génération des faits formels.
  2. Exécution de l’audit Prolog.
  3. Résultat attendu : 24/24 OK sans modification des règles de contrôle.

Stratégie de migration DDL (contractuelle)

  • Objet : vault_secure.key_envelopes.metadata_tag.
  • Type actuel/cible : absent -> BYTEA NULL (phase 1) -> BYTEA NOT NULL (phase 2).
  • Backfill : calcul tag pour enregistrements LEGACY avant passage NOT NULL.
  • Politique phase 1 (contractuelle) : les enveloppes LEGACY (metadata_tag IS NULL) restent accessibles en lecture seule ; un warning d’audit est loggé ; la rotation vers BOUND est recommandée mais non forcée.
  • Impact triggers existants : aucun changement de comportement autorisé ; baseline obligatoire avant/après déploiement sur les 21 checks (OK/KO identiques) ; tout impact détecté = anomalie bloquante.
  • Impact workers/services dépendants : lecture/écriture compatibles phase transitoire puis obligation stricte.
  • Down migration : suppression de la contrainte NOT NULL puis suppression colonne (retour état initial contractuel).
  • Contraintes ajoutées : longueur logique attendue 32 bytes; toute valeur non conforme rejetée.

Atomicité multi-composant

Aucun flux DB + queue/append-only/Merkle n’est identifié dans cette story.
Aucune exigence d’atomicité multi-composant applicable.

Contraintes inter-modules

Aucune route d’un autre module à protéger explicitement n’est identifiée dans le besoin fourni.
Aucune contrainte inter-module applicable.

SLA temporels

Aucune transition d’état temporelle (TTL, expiration, deadline métier) n’est demandée dans ce besoin.
Aucune transition temporelle identifiée.
Décision de rejet sécurité sur metadata_tag invalide : < 100 ms (borne opérationnelle).

5bis. Diagrammes Mermaid

Diagramme d’états — Cycle de vie du metadata binding (INV-276-11)

stateDiagram-v2
    [*] --> LEGACY_NULL_TAG : Enveloppe existante\n(pré-migration)

    LEGACY_NULL_TAG --> BOUND_TAG_VALID : Backfill / Rotation conforme\n(INV-276-05, INV-276-06)
    LEGACY_NULL_TAG --> TAMPERED_TAG_INVALID : Incohérence détectée\n(INV-276-07)

    BOUND_TAG_VALID --> TAMPERED_TAG_INVALID : Altération détectée\n(INV-276-07)

    TAMPERED_TAG_INVALID --> [*] : État terminal\n(résolution manuelle uniquement)

    note right of LEGACY_NULL_TAG
        Phase 1 : accès lecture seule
        Warning audit loggé
        Rotation recommandée (CA-276-13)
    end note

    note right of BOUND_TAG_VALID
        metadata_tag 32 bytes valide
        Accès complet autorisé
    end note

    note left of TAMPERED_TAG_INVALID
        Accès refusé — HTTP 422 < 100 ms
        Aucune transition sortante
        (INV-276-07, E-06)
    end note

Diagramme de séquence — F1 : Validation Argon2id (INV-276-01, INV-276-02, INV-276-03)

sequenceDiagram
    participant Client
    participant Backend as Backend (NestJS)
    participant Argon2Svc as Argon2Service
    participant Config as Config centralisée

    Client->>Backend: POST /enroll ou /rotate<br/>{ memory, iterations, parallelism, type, hashLength }
    Backend->>Config: Lecture bornes contractuelles (INV-276-03)
    Config-->>Backend: { min/max memory, iterations, parallelism, type, hashLength }
    Backend->>Argon2Svc: validateParams(clientParams, bornes)

    alt Paramètres conformes
        Argon2Svc-->>Backend: OK (validation seule, pas de dérivation — INV-276-01)
        Backend-->>Client: 200 — Requête acceptée
    else Paramètres hors bornes (E-01/E-02/E-03/E-04)
        Argon2Svc-->>Backend: REJET (erreur de validation)
        Backend-->>Client: 422 — Paramètres KDF non conformes
    end

Diagramme de séquence — F3 : Création enveloppe avec metadata binding (INV-276-05, INV-276-06)

sequenceDiagram
    participant Backend as Backend (NestJS)
    participant HKDF as HKDF Derivation
    participant HMAC as HMAC Computation
    participant DB as PostgreSQL (vault_secure)

    Backend->>Backend: Prépare métadonnées<br/>(algorithm, version, envelope_type, device_id)
    Backend->>HKDF: Dérive K_binding depuis K_master_user<br/>contexte = "ProbatioVault::MetadataBinding::v1" (INV-276-06)
    HKDF-->>Backend: K_binding (clé isolée)
    Backend->>HMAC: HMAC(K_binding, algorithm || version || envelope_type || device_id)
    HMAC-->>Backend: metadata_tag (32 bytes) (INV-276-05)
    Backend->>DB: INSERT key_envelopes SET metadata_tag = <tag>
    DB-->>Backend: OK

Diagramme de séquence — F4 : Accès enveloppe avec vérification binding (INV-276-07)

sequenceDiagram
    participant Client
    participant Backend as Backend (NestJS)
    participant HKDF as HKDF Derivation
    participant HMAC as HMAC Verification
    participant DB as PostgreSQL (vault_secure)

    Client->>Backend: GET /envelope/:id (unwrap logique)
    Backend->>DB: SELECT metadata_tag, metadata FROM key_envelopes
    DB-->>Backend: { metadata_tag, algorithm, version, envelope_type, device_id }

    alt metadata_tag IS NULL (LEGACY — phase 1)
        Backend-->>Client: 200 — Lecture seule + warning audit (CA-276-13)
    else metadata_tag présent
        Backend->>HKDF: Dérive K_binding (INV-276-06)
        HKDF-->>Backend: K_binding
        Backend->>HMAC: Recalcule tag attendu
        HMAC-->>Backend: expected_tag

        alt tag valide (BOUND)
            Backend-->>Client: 200 — Données enveloppe restituées
        else tag invalide (TAMPERED — E-06)
            Backend-->>Client: 422 — Accès refusé < 100 ms (INV-276-07)
        end
    end

6. Cas d’erreur

  • E-01 Paramètres Argon2 insuffisants : rejet de la requête, erreur de validation explicite.
  • E-02 Paramètres Argon2 hors bornes max : rejet de la requête, erreur de validation explicite.
  • E-03 Paramètres Argon2 manquants/incomplets : rejet de la requête, erreur de validation explicite.
  • E-04 Type KDF non Argon2id (type != 2) : rejet de la requête, erreur de conformité.
  • E-05 metadata_tag absent en phase finale : rejet, non-conformité de données.
  • E-06 metadata_tag invalide (tampering/substitution) : refus immédiat d’accès (< 100 ms), erreur sécurité HTTP 422 Unprocessable Entity côté contrat d’API.
  • E-07 Faits Prolog manquants : audit non conforme, livraison refusée.
  • E-08 Régression sur checks existants : livraison refusée.

7. Critères d’acceptation (testables)

ID Critère Observable
CA-276-01 Audit formel atteint 24/24 Exécution audit Prolog = 24 checks OK
CA-276-02 Argon2Service est déclaré et détectable Fait service(argon2, 'Argon2Service') présent
CA-276-03 Méthode de validation détectable Fait service_method(argon2, validateParams) présent (validation seule, sans dérivation métier)
CA-276-04 Bornes minimales OWASP appliquées Cas < minimum rejetés systématiquement
CA-276-05 Bornes maximales contractuelles appliquées Cas > maximum rejetés systématiquement
CA-276-06 Configuration centralisée exposée Endpoint renvoie valeurs exactes du contrat
CA-276-07 Colonne metadata_tag déclarée formellement Fait entity_column(key_envelope, metadata_tag, bytea) présent
CA-276-08 Metadata binding vérifié avant accès Tag invalide => accès refusé (HTTP 422, décision < 100 ms)
CA-276-09 Migration progressive valide Phase 1 nullable puis phase 2 NOT NULL, sans rupture contractuelle
CA-276-10 Migration réversible down() restaure l’état antérieur contractuel
CA-276-11 Non-régression conformité Les 21 checks antérieurement OK restent OK avec statut OK/KO identique avant/après déploiement
CA-276-12 Invariant encryption repos respecté (probatoire) Aucun secret temporaire en clair détecté dans le périmètre probatoire (scans + échantillonnage + audit), sans exigence de preuve exhaustive
CA-276-13 Politique transitoire LEGACY phase 1 appliquée metadata_tag IS NULL => accès lecture seule autorisé, warning loggé, rotation vers BOUND recommandée non forcée

8. Scénarios de test (Given / When / Then)

  • T-01 Minima exacts Argon2
  • Given paramètres memory=65536, iterations=3, parallelism=4, type=2, hashLength=32
  • When validation serveur
  • Then validation acceptée

  • T-02 Mémoire insuffisante

  • Given memory=65535
  • When validation serveur
  • Then rejet explicite

  • T-03 Itérations insuffisantes

  • Given iterations=2
  • When validation serveur
  • Then rejet explicite

  • T-04 Type invalide

  • Given type!=2
  • When validation serveur
  • Then rejet explicite

  • T-05 Paramètre manquant

  • Given hashLength absent
  • When validation serveur
  • Then rejet explicite

  • T-06 Création enveloppe liée

  • Given métadonnées complètes et K_binding dérivable
  • When création/rotation enveloppe
  • Then metadata_tag est présent et de longueur 32 bytes

  • T-07 Tampering tag

  • Given enveloppe persistée puis metadata_tag modifié
  • When accès enveloppe
  • Then refus immédiat (< 100 ms, HTTP 422)

  • T-08 Substitution inter-device

  • Given enveloppe A déplacée vers un autre device_id
  • When vérification de binding
  • Then refus immédiat (< 100 ms, HTTP 422)

  • T-09 Migration phase 1

  • Given base avant migration
  • When application migration phase 1
  • Then colonne metadata_tag BYTEA NULL existe

  • T-10 Migration phase 2

  • Given backfill terminé
  • When application migration phase 2
  • Then metadata_tag devient obligatoire (NOT NULL)

  • T-11 Audit conformité

  • Given faits générés et règles Prolog inchangées
  • When lancement audit
  • Then résultat 24/24 OK

  • T-12 Réversibilité migration

  • Given migration appliquée
  • When exécution down()
  • Then schéma revient à l’état contractuel précédent

  • T-13 Politique LEGACY phase 1

  • Given enveloppe LEGACY_NULL_TAG en phase 1 (metadata_tag IS NULL)
  • When accès de consultation
  • Then accès lecture seule autorisé, warning loggé, rotation vers BOUND_TAG_VALID recommandée et non forcée

  • T-14 Transition LEGACY vers TAMPERED

  • Given enveloppe historique sans tag (state LEGACY_NULL_TAG) et tag invalide calculé/détecté lors d’une tentative d’accès
  • When vérification de binding
  • Then transition LEGACY_NULL_TAG -> TAMPERED_TAG_INVALID observée et accès refusé (HTTP 422)

  • T-15 Vérification faits/checks ciblés

  • Given génération de faits candidate
  • When exécution de la vérification de consommation
  • Then les 3 faits émis (service(argon2,'Argon2Service'), service_method(argon2, validateParams), entity_column(key_envelope, metadata_tag, bytea)) sont exactement ceux consommés par check_10, check_19, check_22 (pas de dépendance à des artefacts runtime hors périmètre)

9. Hypothèses explicites

ID Hypothèse Impact si faux
H-276-01 Le projet cible est ProbatioVault-backend (NestJS/TypeORM/PostgreSQL). Mauvais contrat technique, non-conformité Gate
H-276-02 Le check Prolog consomme uniquement les faits générés et non des artefacts runtime hors périmètre ; vérification imposée par test dédié liant explicitement check_10, check_19, check_22 aux 3 faits attendus. Risque de 24/24 non atteignable malgré conformité code
H-276-03 Les enregistrements LEGACY peuvent être backfillés avant activation NOT NULL. Blocage migration phase 2
H-276-04 Les bornes max Argon2 proposées (memory<=1048576 KiB, iterations<=10, parallelism<=16) sont acceptables côté sécurité/perf produit. Ajustement contractuel requis
H-276-05 Le code d’erreur sécurité contractuel attendu pour tag invalide est 422 Unprocessable Entity. Alignement API appliqué (plus d’ambiguïté)
H-276-06 Aucun trigger legacy n’impose de contrainte incompatible sur key_envelopes. Risque incident migration
H-276-07 Les journaux d’audit (événements horodatés/signés) sont un prérequis d’observabilité déjà couvert par d’autres stories ; PD-276 les consomme mais n’en redéfinit pas les invariants. Réduit la capacité probatoire si absent, sans invalider la sémantique métier de PD-276

10. Contraintes techniques et points à clarifier

10.1 Contraintes techniques (contractuelles)

  • Stack obligatoire : NestJS + TypeORM + PostgreSQL 16+.
  • Aucun framework non cible (ex. Spring Boot) n’est applicable.
  • Le serveur n’installe pas de dépendance Argon2 native pour hasher.
  • Le backend doit rester strictement compatible avec l’architecture Zero-Knowledge.
  • Toute règle ci-dessus est testable, sauf les éléments explicitement qualifiés probatoires (CA-276-12).

10.2 Points à clarifier (données manquantes)

ID Point à clarifier Impact
Q-276-01 Valeurs maximales officielles Argon2 à valider produit/sécurité (au-delà des minima OWASP). Peut modifier INV-276-03 et jeux de tests
Q-276-02 Clôturé : en phase 1, LEGACY reste accessible en lecture seule avec warning loggé ; rotation recommandée non forcée. Transitions d’état transitoires désormais déterministes
Q-276-03 Clôturé : mapping erreur sécurité E-06 fixé à HTTP 422 Unprocessable Entity. Alignement tests d’acceptation finalisé
Q-276-04 Clôturé : référence épique = PD-189 (Epic crypto-proof). Traçabilité documentaire finalisée
Q-276-05 Nom exact du script/générateur des faits Prolog en CI. Traçabilité et reproductibilité audit

Machine à états (transitions inverses explicites)

  • États :
  • LEGACY_NULL_TAG
  • BOUND_TAG_VALID
  • TAMPERED_TAG_INVALID (terminal)

  • Transitions sortantes :

  • LEGACY_NULL_TAG -> BOUND_TAG_VALID : AUTORISÉE (backfill/rotation conforme)
  • LEGACY_NULL_TAG -> TAMPERED_TAG_INVALID : AUTORISÉE (si incohérence détectée)
  • BOUND_TAG_VALID -> LEGACY_NULL_TAG : INTERDITE (downgrade sécurité interdit)
  • BOUND_TAG_VALID -> TAMPERED_TAG_INVALID : AUTORISÉE (détection altération)
  • TAMPERED_TAG_INVALID -> * : INTERDITE (état terminal, résolution manuelle uniquement)

  • Comportement downgrade/retour :

  • Données existantes : conservées mais accès interdit en état TAMPERED.
  • Quotas/limites sécurité : réappliqués immédiatement.
  • Déverrouillage fonctionnel : interdit sans remédiation explicite hors flux nominal.

  • Mention obligatoire :

  • Aucune transition retour applicable vers LEGACY_NULL_TAG depuis un état sécurisé.

Références

  • Epic : PD-189 (Epic crypto-proof)
  • JIRA : PD-276
  • Repos concernés : ProbatioVault-backend (principal), ProbatioVault-doc (templates/références)
  • Documents associés :
  • docs/normes/pv-envelope/formal/pv_envelope_compliance.pl
  • docs/normes/AUDIT-SYNTHESIS.md
  • docs/normes/pv-envelope/
  • RFC 9106
  • OWASP Password Storage Cheat Sheet