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_tagsurvault_secure.key_envelopesavec 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 deK_master_uservia 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_tagvalide. - 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)¶
- Le client soumet les paramètres KDF déclarés lors d’enregistrement/rotation.
- Le backend compare aux bornes contractuelles (INV-276-02, INV-276-03).
- Si conformes, la requête continue ; sinon rejet explicite.
- Le backend reste non-dérivant vis-à-vis du password (INV-276-01).
Flux F2 — Exposition configuration Argon2id¶
- Un appel API interne/externe autorisé lit la configuration centralisée.
- Les valeurs renvoyées correspondent exactement au contrat INV-276-03.
- Le client peut s’aligner sans divergence locale non déclarée.
Flux F3 — Création/rotation d’enveloppe avec metadata binding¶
- Le backend prépare/valide les métadonnées (
algorithm,version,envelope_type,device_id). - Le binding cryptographique est calculé avec
K_bindingdérivée (INV-276-06). metadata_tagest persisté dansvault_secure.key_envelopes.metadata_tag.- En phase finale de migration,
metadata_tagest obligatoire (NOT NULL).
Flux F4 — Accès enveloppe (unwrap logique)¶
- Lecture de l’enveloppe et de son
metadata_tag. - Vérification du tag avant restitution de toute donnée exploitable.
- 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¶
- Génération des faits formels.
- Exécution de l’audit Prolog.
- 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 versBOUNDest 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/KOidentiques) ; 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_tagabsent en phase finale : rejet, non-conformité de données. - E-06
metadata_taginvalide (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
hashLengthabsent - When validation serveur
-
Then rejet explicite
-
T-06 Création enveloppe liée
- Given métadonnées complètes et
K_bindingdérivable - When création/rotation enveloppe
-
Then
metadata_tagest présent et de longueur 32 bytes -
T-07 Tampering tag
- Given enveloppe persistée puis
metadata_tagmodifié - 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 NULLexiste -
T-10 Migration phase 2
- Given backfill terminé
- When application migration phase 2
-
Then
metadata_tagdevient 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_TAGen phase 1 (metadata_tag IS NULL) - When accès de consultation
-
Then accès lecture seule autorisé, warning loggé, rotation vers
BOUND_TAG_VALIDrecommandé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_INVALIDobservé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 parcheck_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_TAGBOUND_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_TAGdepuis 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.pldocs/normes/AUDIT-SYNTHESIS.mddocs/normes/pv-envelope/- RFC 9106
- OWASP Password Storage Cheat Sheet