Aller au contenu

PD-282 — Plan d'implementation

1. Contexte

  • Story : PD-282 — ProofEnvelope : scellement HSM (PROOF-14) + materiel validation eIDAS (PROOF-05b)
  • Gate 3 : RESERVE (8.25/10 v3) — reserves sur Mode B test nominal, OCSP revoked, P95
  • Auteur : Claude Opus 4.6 (etape 4)
  • Date : 2026-03-02

2. Decisions architecturales

DA-01 — Extension de LegalCompositeProof (pas de table separee)

  • Decision : Ajouter une colonne envelope_seal JSONB nullable a la table existante legal_composite_proof
  • Rationale : Evite un JOIN, le trigger PD-272 existant protege deja l'immutabilite, la colonne nullable gere la retro-compatibilite
  • Alternatives ecartees : Table separee proof_envelope_seal avec FK ; stockage dans le JSONB verification_material existant
  • Trade-offs : Le code doit gerer les preuves sans sceau (NULL pour pre-PD-282)

DA-02 — Etat UNSEALED transitoire en memoire

  • Decision : Pas de colonne seal_status. L'INSERT est toujours atomique en etat SEALED
  • Rationale : Le trigger PD-272 bloque tout UPDATE. Persister en UNSEALED puis UPDATE vers SEALED est impossible sans modifier le trigger
  • Alternatives ecartees : Colonne seal_status avec trigger conditionnel
  • Trade-offs : Pas d'etat intermediaire observable en DB (acceptable car construction transitoire)

DA-03 — SHA3-384 en software + CKM_ECDSA (raw) dans HSM

  • Decision : Pre-hash SHA3-384 en software (js-sha3) puis signature ECDSA P-384 raw dans le HSM
  • Rationale : CloudHSM ne supporte pas necessairement CKM_ECDSA_SHA3_384. Le pattern pre-hash + raw sign est deja utilise dans LegalAuditTrailService
  • Alternatives ecartees : CKM_ECDSA_SHA384 avec double hashing
  • Trade-offs : Conforme a l'identifiant algorithm ; peut necessiter ajout ECDSA_RAW dans SignatureAlgorithm enum

DA-04 — Scellement integre dans generateProof()

  • Decision : Le scellement est la derniere etape de generateProof(), pas une methode separee
  • Rationale : Le trigger PD-272 bloque les UPDATE — la preuve doit etre inseree complete
  • Alternatives ecartees : Methode sealProof(proofId) separee avec UPDATE

DA-05 — @peculiar/asn1-ocsp pour OCSP

  • Decision : Utiliser @peculiar/asn1-ocsp pour l'encodage/decodage ASN.1 OCSP
  • Rationale : TypeScript pur, maintenu, compatible @peculiar/x509
  • Alternatives ecartees : Package npm ocsp ; appel OpenSSL CLI via child_process
  • Compatibilite ESM/CJS : Les packages @peculiar/* sont ESM-first avec exports CJS via package.json exports field. Le backend NestJS est en CJS (tsconfig module: commonjs). Verification : require('@peculiar/asn1-ocsp') fonctionne grace aux exports CJS declares. En cas d'incompatibilite : dynamic import await import('@peculiar/asn1-ocsp') en fallback.

DA-06 — Chaines de certificats intermediaires via ConfigService

  • Decision : Les intermediaires et root sont configures via NestJS ConfigService (env vars ou fichiers PEM)
  • Rationale : Le HSM stocke le leaf cert. Les intermediaires sont stables et geables en config
  • Alternatives ecartees : Stocker toute la chaine dans le HSM ; telecharger dynamiquement via AIA

3. Adressage des reserves Gate 3

Reserve ECT-01 — Mode B sans test nominal

  • Action plan : Le service OfflineVerificationService implemente Mode A ET Mode B. Le test nominal Mode B sera ajoute au test suite par l'agent-verifier (TC-NOM-xx-MODE-B).

Reserve ECT-02 — OCSP revoked non contractualise

  • Action plan : Le VerificationMaterialAssembler rejette la finalisation si status=revoked detecte (exception OCSP_CERT_REVOKED). Contractualise dans le code-contract module verification-material-assembly.

Reserve DIV-01 — signedAt non protege par le sceau

  • Action plan : Accepte par design (signedAt est dans envelopeSeal, exclu du hash). Mitigation existante : validationTimestamp dans le hash + token TSA PD-81. Le plan documente cette decision comme acceptable.

Adressage ecarts Gate 5 v1

INV-282-07 — Artefacts crypto temporaires (BLQ Gate 5)

Le pipeline PD-282 ne persiste AUCUN artefact crypto temporaire sur disque ni en base : - Le hash SHA3-384 est calcule en memoire (variable Buffer, garbage-collected apres INSERT) - La signature ECDSA est executee dans le HSM — la cle privee ne quitte jamais le HSM - Le canonicalJson (string) est une variable locale, jamais ecrite sur disque - Aucun fichier temporaire, aucun blob de staging, aucun cache de signature

Conclusion : INV-282-07 est satisfait par conception — il n'existe pas d'artefact crypto temporaire a chiffrer. L'exigence "chiffre au repos" s'applique aux modules qui produisent des artefacts persistants (ex: DEK dans PD-X, ReKey dans PD-Y). PD-282 ne produit que des valeurs transitoires en memoire.

Dispositif de verification (TC-INV-07) : Le test verifie par audit statique que : 1. Aucune ecriture fichier temporaire n'existe dans le code du module seal/verification 2. Le canonicalJson est une const locale (pas de member/property assignee a un service) 3. Les tests unitaires mockent le HSM sans jamais persister de signature intermediaire 4. Review code Gate 7 verifie l'absence de persistance non contractuelle

TC-NOM-08 — Simulation crash (BLQ Gate 5)

Strategie de test d'atomicite par injection de faute transactionnelle : 1. Pre-commit crash : Le test injecte une erreur (jest.spyOn(queryRunner, 'commitTransaction').mockRejectedValueOnce(new Error('simulated crash'))) dans la transaction avant commit. Verification : aucune donnee persistee (SELECT COUNT(*) FROM legal_composite_proof WHERE ... = 0). 2. Post-commit crash : Le test laisse le commit reussir puis simule un crash applicatif (kill du handler post-commit). Verification : la preuve est persistee avec sceau valide (reconciliation non necessaire car l'INSERT atomique a reussi). 3. Points d'observation : assertions sur l'etat de la table + verification du sceau via OfflineVerificationService.verify() sur la preuve inseree.

OCSP revoked — Ajout contractuel (MAJ Gate 5)

Le rejet sur status=revoked est un ajout contractuel impose par la Reserve ECT-02 de Gate 3 (OCSP revoked non specifie → BLOQUANT). Ce n'est pas un ecart par rapport a la spec : c'est la resolution d'un ecart identifie en Gate 3. La spec sera amendee en consequence pour contractualiser ce comportement.

Mode B — Verification en ligne constitutive (MAJ Gate 5)

Correction : en Mode B, la verification en ligne (OCSP live, CRL publics) est constitutive (non optionnelle). Le service OfflineVerificationService en Mode B DOIT : 1. Verifier le sceau (comme Mode A) 2. Verifier les chaines de certificats (comme Mode A) 3. En plus : requeter les serveurs OCSP/CRL publics pour confirmer la non-revocation des certificats au moment de la verification Si la verification en ligne echoue (timeout, erreur), Mode B retourne valid: false avec reason: 'online_verification_failed'.

Framework de test — Jest (MAJ Gate 5)

Framework : Jest (standard NestJS, deja configure dans le projet). Runner : npm run test (unitaires), npm run test:e2e (integration). Variables CI necessaires : DATABASE_URL, HSM_PROVIDER=mock, NODE_ENV=test.

Notes issues du dossier Gate 3 v2

Ref Sujet Resolution
HD-02 JCS + jsonb round-trip Le sceau est calcule sur la forme pre-INSERT. Le stockage jsonb ne modifie pas le blob envelope_seal car c'est un objet JSON opaque.
HD-03 Concurrence finalisation generateProof() est appele dans une transaction avec le lock existant. Pas de double scellement possible.
AMB-04 Mecanisme reconciliation Reference : le reconciliation handler existant dans le module legal-pre couvre le rattrapage post-crash.
AMB-05 Ordre temporel L'implementation assemble verificationMaterial PUIS calcule le sceau. Ordre documente dans le code-contract.
RSC-01 OCSP cert scellement Hors perimetre PD-282 (le certificat HSM ProbatioVault n'a pas de responder OCSP public). A evaluer dans une story dediee si necessaire.

4. Decomposition en modules

4.1 agent-entity (C1-C4)

Module : proof-envelope-entity - Interfaces TypeScript : EnvelopeSeal, EnhancedVerificationMaterial, OcspResponseSnapshot, SealedProofEnvelope - Extension entite : colonne envelope_seal dans LegalCompositeProof - Migration TypeORM : PD282-AddEnvelopeSealColumn.ts - Module DI : registration dans LegalPreModule

Fichiers : - src/modules/legal-pre/interfaces/proof-envelope-seal.interfaces.ts - src/modules/legal-pre/entities/legal-composite-proof.entity.ts - src/database/migrations/*-PD282-AddEnvelopeSealColumn.ts - src/modules/legal-pre/legal-pre.module.ts

4.2 agent-seal (C5-C9)

Module : envelope-seal-pipeline - Service principal : EnvelopeSealService — pipeline JCS, SHA3-384, ECDSA P-384 HSM - Service detection : SecretDetectionService — scan patterns interdits - Service validation : EnvelopeFormatValidatorService — regex contractuelles - Extension hash : hashSha3_384() dans HashService

Pipeline de scellement : 1. Valider format de l'enveloppe (format validation) 2. Scanner pour secrets (secret detection) 3. Exclure envelopeSeal de l'objet 4. Canonicaliser JCS RFC 8785 5. Hash SHA3-384 6. Signer ECDSA P-384 via HSM (raw sign) 7. Assembler envelopeSeal : {algorithm, signature, kid, signedAt, certificateChain}

Fichiers : - src/modules/legal-pre/services/envelope-seal.service.ts - src/modules/legal-pre/services/secret-detection.service.ts - src/modules/legal-pre/services/envelope-format-validator.service.ts - src/modules/crypto/hash.service.ts - src/modules/legal-pre/services/legal-composite-proof.service.ts

4.3 agent-verification (C10-C11)

Module : verification-material-assembly - Service assembleur : VerificationMaterialAssembler — collecte chaines, OCSP, timestamps - Service OCSP : OcspClientService — requetes OCSP paralleles, timeout configurable

Pipeline d'assemblage : 1. Recuperer chaines TSA et eIDAS (config + references existantes) 2. Requeter OCSP en parallele (Promise.allSettled) 3. Si timeout total → policy OCSP_UNAVAILABLE, ocspResponses=[] 4. Si status=revoked → exception OCSP_CERT_REVOKED (rejet finalisation) 5. Normaliser certSerialNumber en uppercase 6. Assembler verificationMaterial avec validationTimestamp courant

Fichiers : - src/modules/legal-pre/services/verification-material-assembler.service.ts - src/modules/legal-pre/services/ocsp-client.service.ts

4.4 agent-verifier (C12)

Module : offline-verification - Service : OfflineVerificationService — verification Mode A et Mode B

Pipeline de verification : 1. Extraire envelopeSeal de l'enveloppe 2. Reconstruire l'objet sans envelopeSeal 3. Canonicaliser JCS RFC 8785 (meme implementation que scellement) 4. Hash SHA3-384 5. Extraire cle publique de certificateChain[0] (leaf cert) 6. Verifier signature ECDSA P-384 via crypto.createVerify() (dsaEncoding: 'ieee-p1363') 7. Verifier chaine de certificats 8. Mode B uniquement : verification OCSP en ligne constitutive (echec → valid=false)

Fichiers : - src/modules/legal-pre/services/offline-verification.service.ts

4.5 Diagrammes Mermaid

Graphe de dependances inter-modules

graph LR
    subgraph "PD-282 — Modules"
        ENTITY["proof-envelope-entity<br/>(agent-entity)"]
        SEAL["envelope-seal-pipeline<br/>(agent-seal)"]
        VERIF_MAT["verification-material-assembly<br/>(agent-verification)"]
        OFFLINE["offline-verification<br/>(agent-verifier)"]
    end

    subgraph "Services internes"
        SEAL_SVC["EnvelopeSealService"]
        SECRET["SecretDetectionService"]
        FORMAT["EnvelopeFormatValidatorService"]
        OCSP["OcspClientService"]
        VERIF_ASM["VerificationMaterialAssembler"]
        OFFLINE_SVC["OfflineVerificationService"]
    end

    subgraph "Dependances existantes"
        PD272["PD-272<br/>Immutability Trigger"]
        PD81["PD-81<br/>LegalCompositeProof / TSA"]
        PD37["PD-37<br/>JsonCanonicalizeService"]
        PD280["PD-280<br/>Modele d'etats"]
        HSM["CloudHSM<br/>ECDSA P-384"]
        HASH["HashService<br/>SHA3-384"]
    end

    subgraph "Dependances externes"
        PECULIAR["@peculiar/asn1-ocsp<br/>@peculiar/asn1-x509"]
        JSSHA3["js-sha3"]
    end

    SEAL --> ENTITY
    VERIF_MAT --> ENTITY
    OFFLINE --> ENTITY
    OFFLINE --> SEAL

    SEAL_SVC --> FORMAT
    SEAL_SVC --> SECRET
    SEAL_SVC --> PD37
    SEAL_SVC --> HASH
    SEAL_SVC --> HSM

    VERIF_ASM --> OCSP
    OCSP --> PECULIAR

    OFFLINE_SVC --> PD37
    OFFLINE_SVC --> HASH
    OFFLINE_SVC --> OCSP

    ENTITY --> PD272
    ENTITY --> PD81
    ENTITY --> PD280

    HASH --> JSSHA3

Sequence — Pipeline de scellement (generateProof)

sequenceDiagram
    participant Caller as LegalCompositeProofService
    participant FMT as EnvelopeFormatValidatorService
    participant SEC as SecretDetectionService
    participant JCS as JsonCanonicalizeService<br/>(PD-37)
    participant HASH as HashService
    participant HSM as CloudHSM
    participant VMA as VerificationMaterialAssembler
    participant OCSP as OcspClientService
    participant DB as PostgreSQL

    Caller->>VMA: assembleVerificationMaterial(proofEnvelope)
    activate VMA
    VMA->>VMA: Recuperer chaines TSA + eIDAS (ConfigService)
    VMA->>OCSP: queryAll(certificates)
    activate OCSP
    OCSP-->>VMA: OcspResponseSnapshot[]
    deactivate OCSP
    alt status = revoked
        VMA-->>Caller: throw OCSP_CERT_REVOKED
    end
    VMA-->>Caller: EnhancedVerificationMaterial
    deactivate VMA

    Caller->>Caller: Attacher verificationMaterial a l'enveloppe

    Caller->>FMT: validate(proofEnvelope)
    activate FMT
    FMT-->>Caller: OK / throw FORMAT_ERROR
    deactivate FMT

    Caller->>SEC: scan(proofEnvelope)
    activate SEC
    SEC-->>Caller: OK / throw SECRET_DETECTED
    deactivate SEC

    Caller->>Caller: Exclure envelopeSeal de l'objet
    Caller->>JCS: canonicalize(envelopeWithoutSeal)
    activate JCS
    JCS-->>Caller: canonicalJson (string)
    deactivate JCS

    Caller->>HASH: hashSha3_384(canonicalJson)
    activate HASH
    HASH-->>Caller: digest (Buffer 48 bytes)
    deactivate HASH

    Caller->>HSM: sign(CKM_ECDSA, digest, kid)
    activate HSM
    HSM-->>Caller: signature (ieee-p1363)
    deactivate HSM

    Caller->>Caller: Assembler EnvelopeSeal
    Caller->>DB: INSERT legal_composite_proof (atomique, trigger PD-272)
    activate DB
    DB-->>Caller: OK (SEALED)
    deactivate DB

Sequence — Verification offline (Mode A) et en ligne (Mode B)

sequenceDiagram
    participant Client as Appelant
    participant OVS as OfflineVerificationService
    participant JCS as JsonCanonicalizeService<br/>(PD-37)
    participant HASH as HashService
    participant CRYPTO as crypto.verify()
    participant OCSP as OcspClientService

    Client->>OVS: verify(sealedEnvelope, mode)
    activate OVS

    OVS->>OVS: Extraire envelopeSeal
    OVS->>OVS: Reconstruire objet sans envelopeSeal

    OVS->>JCS: canonicalize(envelopeWithoutSeal)
    JCS-->>OVS: canonicalJson

    OVS->>HASH: hashSha3_384(canonicalJson)
    HASH-->>OVS: digest

    OVS->>OVS: Extraire publicKey de certificateChain[0]

    OVS->>CRYPTO: verify(null, digest, publicKey, signature)
    CRYPTO-->>OVS: true / false

    alt signature invalide
        OVS-->>Client: { valid: false, reason: 'invalid_signature' }
    end

    OVS->>OVS: Verifier chaine de certificats (trustedRoots obligatoire)

    alt Mode B
        OVS->>OCSP: queryAll(certificateChain)
        activate OCSP
        OCSP-->>OVS: OcspResponseSnapshot[]
        deactivate OCSP
        alt verification en ligne echouee
            OVS-->>Client: { valid: false, reason: 'online_verification_failed' }
        end
    end

    OVS-->>Client: { valid: true }
    deactivate OVS

5. Couverture des invariants

Invariant Module(s) Composant(s)
INV-282-01 seal-pipeline EnvelopeSealService
INV-282-02 seal-pipeline EnvelopeSealService (excl. envelopeSeal)
INV-282-03 seal-pipeline JCS canonicalisation + SHA3-384 + ECDSA
INV-282-04 verification-material, offline-verification VerificationMaterialAssembler, OfflineVerificationService
INV-282-05 verification-material VerificationMaterialAssembler (validationPolicy)
INV-282-06 secret-detection SecretDetectionService
INV-282-07 seal-pipeline Hash non reversible, cle dans HSM
INV-282-08 entity Trigger PD-272 existant
INV-282-09 entity SEALED terminal (pas d'UPDATE possible)
INV-282-10 entity Pas de SEALED->UNSEALED (trigger PD-272)
INV-282-11 offline-verification kid + certificateChain embarquee
INV-282-12 format-validation EnvelopeFormatValidatorService

6. Dependances externes

Dependance Usage Statut
PD-272 (immutability trigger) Protection immutabilite Disponible
PD-81 (LegalCompositeProof) Token TSA dans ProofEnvelope Disponible
PD-280 (modele d'etats) PENDING/INDETERMINATE Disponible
PD-37 (JsonCanonicalizeService) Canonicalisation RFC 8785 Disponible
js-sha3 SHA3-384 hash Dependance existante
@peculiar/asn1-ocsp Encodage/decodage OCSP ASN.1 A ajouter
@peculiar/asn1-x509 Parsing certificats X.509 A ajouter

7. Strategie de tests

Niveau Couverture Agent
Unitaire Chaque service isole (mock HSM, mock OCSP) Chaque agent
Integration Pipeline complet generateProof() avec seal et verify agent-verifier
Negatif TC-NEG-01..10 (rejets format, secrets, rotation, replay) agent-seal + agent-verifier
Non-regression PD-272 trigger, PD-81 TSA, PD-280 etats agent-entity
Mode A Verification offline (no network). Observabilite air-gap : mock HttpService/HttpModule, assertion expect(httpService.get/post).not.toHaveBeenCalled() pour prouver zero appel reseau PV. agent-verifier
Mode B Verification online (OCSP live, CRL) agent-verifier

8. Risques et mitigations

Risque Probabilite Impact Mitigation
CloudHSM ne supporte pas CKM_ECDSA raw Faible Haut Pre-hash SHA3-384 + raw sign deja valide sur LegalAuditTrailService
Interop SHA3-384 sign(HSM) / verify(Node.js) Faible Haut Le pattern pre-hash SHA3-256 + raw ECDSA est valide avec LegalAuditTrailService. Pour SHA3-384, un test d'interoperabilite explicite (sign via HSM + verify via crypto.createVerify('SHA3-384') avec dsaEncoding ieee-p1363) DOIT etre execute en CI. Cf. TC-INV-xx-INTEROP dans le plan de tests.
@peculiar/asn1-ocsp incompatible Faible Moyen Fallback : ocsp npm package
OCSP responders intermittents Moyen Moyen Timeout + policy OCSP_UNAVAILABLE + monitoring 10% seuil
JCS round-trip jsonb Faible Haut envelope_seal est un blob JSON opaque, pas reconstruit depuis jsonb