Aller au contenu

PD-39 — Plan d'Implémentation TSA RFC 3161 par Batch Cryptographique

Version : 3.0 Date : 2025-12-23 Modification : Architecture NTS via ntpd-rs (RFC 8915)


0. Prérequis Infrastructure — ntpd-rs (Horloge NTS)

0.1 Contexte

Les invariants 8 et 9 de la spécification exigent une horloge de référence authentifiée (NTS, RFC 8915). Il n'existe pas de client NTS viable en Node.js. La solution retenue est d'utiliser ntpd-rs (Pendulum), un démon NTS mature en Rust, pour discipliner l'horloge système.

0.2 Architecture ntpd-rs

┌─────────────────────────────────────────────────────────────────┐
│                        Serveur Application                       │
├─────────────────────────────────────────────────────────────────┤
│  ┌──────────────────┐     ┌─────────────────────────────────┐  │
│  │  ReferenceClockService│────▶│ Horloge Système (disciplinée) │  │
│  └──────────────────┘     └─────────────────────────────────┘  │
│           │                              ▲                       │
│           ▼                              │                       │
│  ┌──────────────────┐                    │                       │
│  │ /var/run/ntpd-rs/│◀───────────────────┤                       │
│  │   metrics.sock   │      Synchronisation                      │
│  └──────────────────┘                    │                       │
├─────────────────────────────────────────────────────────────────┤
│                     ntpd-rs daemon                               │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │ • NTS-KE (TLS 1.3) + NTP avec cookies AEAD              │    │
│  │ • Pool NTS: time.cloudflare.com, nts.netnod.se          │    │
│  │ • Métriques: offset, jitter, stratum, leap indicator    │    │
│  │ • Fichier statut: /var/run/ntpd-rs/observe.json         │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

0.3 Configuration ntpd-rs requise

Fichier /etc/ntpd-rs/ntpd-rs.toml :

# Configuration ntpd-rs pour ProbatioVault (PD-39)
# NTS obligatoire, pas de fallback NTP non authentifié

[[source]]
mode = "nts"
address = "time.cloudflare.com:4460"

[[source]]
mode = "nts"
address = "nts.netnod.se:4460"

[[source]]
mode = "nts"
address = "nts.time.nl:4460"

[observability]
# Socket Unix pour métriques
metrics-exporter-listen = "/var/run/ntpd-rs/metrics.sock"
# Fichier JSON de statut (polling par ReferenceClockService)
observation-path = "/var/run/ntpd-rs/observe.json"

[synchronization]
# Seuil de panic si dérive > 1000ms
panic-threshold = { value = 1000 }
# Pas de step, uniquement slew pour éviter sauts
single-step-panic-threshold = { forward = "off", backward = "off" }

[source-defaults]
# Minimum 2 sources NTS valides pour synchronisation
min-agreeing-sources = 2

0.4 Validation de la synchronisation

Le ReferenceClockService valide la synchronisation via le fichier observe.json :

interface NtpdRsObservation {
  version: number;
  time: {
    offset: number;      // Offset en secondes (doit être < 0.005 = 5ms)
    uncertainty: number; // Incertitude en secondes
  };
  system: {
    stratum: number;     // Doit être ≤ 3
    leap: 'none' | 'insert' | 'delete' | 'unknown';
    root_delay: number;
    root_dispersion: number;
  };
  sources: Array<{
    address: string;
    poll_interval: number;
    reach: number;       // Bitmask de réussite (0xFF = 8/8)
    state: 'active' | 'unreachable' | 'rejected';
  }>;
}

Critères de validation : - offset < 5ms (0.005s) - stratum ≤ 3 - Au moins 2 sources avec state === 'active' - leap !== 'unknown'

0.5 Monitoring et Alertes

Métrique Seuil Warning Seuil Critique Action
offset > 1ms > 5ms REJET opérations TSA
stratum > 2 > 3 Alerte ops
Sources actives < 3 < 2 REJET opérations TSA
Fichier observe.json Âge > 10s Âge > 30s REJET opérations TSA

0.6 Déploiement

# Installation ntpd-rs (Debian/Ubuntu)
curl -sSL https://github.com/pendulum-project/ntpd-rs/releases/latest/download/ntpd-rs_amd64.deb -o ntpd-rs.deb
sudo dpkg -i ntpd-rs.deb

# Désactiver NTP/chronyd existant
sudo systemctl disable --now systemd-timesyncd
sudo systemctl disable --now chronyd

# Configurer et démarrer ntpd-rs
sudo cp /path/to/ntpd-rs.toml /etc/ntpd-rs/ntpd-rs.toml
sudo systemctl enable --now ntpd-rs

# Vérifier statut
cat /var/run/ntpd-rs/observe.json | jq '.time.offset, .sources[].state'

1. Découpage en Composants

1.1 Architecture Modulaire

src/modules/tsa/
├── tsa.module.ts                    # Module NestJS principal
├── constants/
│   ├── tsa.constants.ts             # Algorithmes autorisés
│   └── qtsa-registry.ts             # Registre QTSA qualifiées (OID, certificats)
├── enums/
│   ├── batch-status.enum.ts         # OPEN, SEALED, TIMESTAMPED, FAILED
│   ├── validation-result.enum.ts    # Résultats de validation TST
│   └── revocation-status.enum.ts    # GOOD, REVOKED, UNKNOWN, INDETERMINATE
├── entities/
│   ├── timestamp-batch.entity.ts    # Entité batch horodatage
│   ├── batch-item.entity.ts         # Élément individuel du batch
│   ├── timestamp-token.entity.ts    # Jeton TST stocké
│   ├── batch-seal.entity.ts         # Scellement cryptographique du batch
│   └── clock-attestation.entity.ts  # Attestation d'horloge de référence
├── dto/
│   ├── create-batch.dto.ts          # Création de batch
│   ├── add-item.dto.ts              # Ajout d'élément au batch
│   ├── seal-batch.dto.ts            # Scellement de batch
│   └── inclusion-proof.dto.ts       # Preuve d'inclusion (format RFC 9162)
├── interfaces/
│   ├── qtsa-client.interface.ts     # Interface client QTSA
│   ├── merkle-tree.interface.ts     # Interface arbre Merkle normalisé
│   ├── reference-clock.interface.ts # Interface horloge de référence
│   └── seal-provider.interface.ts   # Interface scellement cryptographique
├── services/
│   ├── batch.service.ts             # Gestion du cycle de vie batch
│   ├── batch-seal.service.ts        # Scellement cryptographique probatoire
│   ├── merkle.service.ts            # Construction arbre Merkle (RFC 6962/9162)
│   ├── tsa-client.service.ts        # Client RFC 3161
│   ├── tst-validator.service.ts     # Validation des jetons
│   ├── inclusion-proof.service.ts   # Génération/vérification preuves
│   ├── trusted-list.service.ts      # Validation QTSA via Trusted List
│   ├── qtsa-qualification.service.ts # Vérification qualification QTSA
│   ├── reference-clock.service.ts   # Horloge de référence NTS
│   ├── clock-attestation.service.ts # Génération attestations temporelles
│   └── revocation.service.ts        # CRL/OCSP avec politique probatoire
├── processors/
│   └── batch-timestamp.processor.ts # Worker BullMQ pour horodatage
├── guards/
│   └── batch-sealed.guard.ts        # Empêche modification post-scellement
└── utils/
    ├── hash.utils.ts                # Fonctions de hachage normalisées
    ├── asn1.utils.ts                # Parsing/encodage ASN.1 pour TST
    └── canonical-json.utils.ts      # Sérialisation JSON canonique (RFC 8785)

1.2 Description des Composants

Composant Responsabilité Dépendances
BatchService Gestion cycle de vie: création, ajout, demande de scellement BatchSealService, MerkleService
BatchSealService Scellement cryptographique probatoire avec signature interne HSM/KMS, MerkleService
MerkleService Construction arbre RFC 6962, calcul empreinte agrégée HashUtils, CanonicalJson
TsaClientService Émission requêtes RFC 3161, réception TST HTTP, ASN1Utils
TstValidatorService Validation complète du jeton QtsaQualificationService, RevocationService
QtsaQualificationService Vérification qualification eIDAS via Trusted List TrustedListService
InclusionProofService Génération et vérification preuves RFC 9162 MerkleService
TrustedListService Téléchargement et validation Trusted Lists ETSI Signature ETSI
ReferenceClockService Temps de référence NTS via ntpd-rs (horloge système disciplinée) Fichier observe.json ntpd-rs
ClockAttestationService Preuve d'usage de l'horloge de référence, attestation signée ReferenceClockService, HSM
RevocationService CRL/OCSP avec politique probatoire stricte HTTP, Cache

2. Flux Techniques

2.1 Flux Nominal — Constitution, Scellement et Horodatage d'un Batch

sequenceDiagram
    participant Client
    participant BatchService
    participant BatchSealService
    participant MerkleService
    participant ClockAttestation
    participant TsaClient
    participant QTSA
    participant TstValidator
    participant QtsaQualification
    participant DB

    Client->>BatchService: createBatch()
    BatchService->>DB: INSERT batch (status=OPEN)
    BatchService-->>Client: batchId

    loop Pour chaque donnée
        Client->>BatchService: addItem(batchId, hash)
        BatchService->>DB: Vérifier status=OPEN
        BatchService->>DB: INSERT batch_item
    end

    Client->>BatchService: sealBatch(batchId)
    BatchService->>DB: Vérifier status=OPEN, items > 0
    BatchService->>MerkleService: buildTree(items)
    MerkleService->>MerkleService: Tri lexicographique
    MerkleService->>MerkleService: Encodage RFC 6962 MTH
    MerkleService-->>BatchService: { rootHash, treeHead, treeData }

    BatchService->>BatchSealService: seal(batchId, rootHash, treeData)
    BatchSealService->>BatchSealService: Génération seal_signature (HSM/KMS)
    BatchSealService->>ClockAttestation: attest(now())
    ClockAttestation-->>BatchSealService: { nts_response, attestation_id }
    BatchSealService->>DB: INSERT batch_seal (signature, attestation)
    BatchSealService->>DB: UPDATE batch (status=SEALED)
    BatchSealService-->>BatchService: { sealId, rootHash }
    BatchService-->>Client: { sealId, rootHash, sealed_at }

    Client->>BatchService: timestamp(batchId)
    BatchService->>DB: Vérifier status=SEALED

    BatchService->>QtsaQualification: verifyQualification(qtsaConfig)
    QtsaQualification->>QtsaQualification: Vérifier Trusted List
    QtsaQualification->>QtsaQualification: Vérifier certificat TSA
    QtsaQualification->>QtsaQualification: Vérifier service type "QTS"
    QtsaQualification-->>BatchService: { qualified: true, evidence }

    BatchService->>TsaClient: requestTimestamp(rootHash)
    TsaClient->>QTSA: HTTP POST (TSQ)
    QTSA-->>TsaClient: TSR (TimeStampResponse)
    TsaClient-->>BatchService: TST (TimeStampToken)

    BatchService->>TstValidator: validate(TST, rootHash, sealTime)
    TstValidator->>TstValidator: Vérifier RFC 3161
    TstValidator->>TstValidator: Vérifier messageImprint == rootHash
    TstValidator->>TstValidator: Vérifier signature
    TstValidator->>TstValidator: Vérifier chaîne certificats
    TstValidator->>TstValidator: Vérifier CRL/OCSP (politique probatoire)
    TstValidator->>TstValidator: Vérifier Policy OID qualifiée
    TstValidator->>ClockAttestation: validateTemporalCoherence(genTime)
    ClockAttestation-->>TstValidator: { coherent, drift, attestation }
    TstValidator-->>BatchService: ValidationResult

    BatchService->>DB: UPDATE batch (status=TIMESTAMPED)
    BatchService->>DB: INSERT timestamp_token
    BatchService-->>Client: { tstId, genTime }

2.2 Flux de Scellement Cryptographique (Clôture Normative)

sequenceDiagram
    participant BatchService
    participant BatchSealService
    participant HSM
    participant ClockAttestation
    participant ReferenceClockService
    participant NtpdRs as ntpd-rs<br/>(observe.json)
    participant DB

    BatchService->>BatchSealService: seal(batchId, rootHash, treeData)

    Note over BatchSealService: Construction payload canonique
    BatchSealService->>BatchSealService: payload = canonicalJson({<br/>  batch_id,<br/>  root_hash,<br/>  item_count,<br/>  tree_algorithm: "RFC6962_MTH",<br/>  sealed_at_nts<br/>})

    BatchSealService->>ClockAttestation: requestAttestation()
    ClockAttestation->>ReferenceClockService: getAuthenticatedTime()
    ReferenceClockService->>NtpdRs: Lire observe.json
    NtpdRs-->>ReferenceClockService: { offset, stratum, sources[] }
    ReferenceClockService->>ReferenceClockService: Valider synchro NTS<br/>(offset < 5ms, stratum ≤ 3, sources ≥ 2)

    alt Synchronisation valide
        ReferenceClockService-->>ClockAttestation: { timestamp, sync_status, observation_snapshot }
    else Synchronisation invalide
        ReferenceClockService-->>ClockAttestation: ERREUR: REFERENCE_CLOCK_NOT_SYNCHRONIZED
        ClockAttestation-->>BatchSealService: REJET
    end

    ClockAttestation->>HSM: sign(observation_snapshot)
    HSM-->>ClockAttestation: attestation_signature
    ClockAttestation->>DB: INSERT clock_attestation
    ClockAttestation-->>BatchSealService: { attestation_id, timestamp, signed_observation }

    BatchSealService->>BatchSealService: payload.sealed_at_nts = attestation
    BatchSealService->>HSM: sign(payload, key_id)
    HSM-->>BatchSealService: signature (ECDSA P-384 ou Ed25519)

    BatchSealService->>DB: INSERT batch_seal {<br/>  batch_id,<br/>  payload_canonical,<br/>  signature,<br/>  attestation_id,<br/>  seal_algorithm<br/>}
    BatchSealService->>DB: UPDATE batch SET status='SEALED'

    Note over DB: À partir de ce point:<br/>- Aucun INSERT batch_item autorisé<br/>- Aucun UPDATE sur payload/signature<br/>- Vérifiable par tiers

2.3 Flux de Vérification de Qualification QTSA

sequenceDiagram
    participant QtsaQualification
    participant TrustedListService
    participant TLCache
    participant HTTP
    participant DB

    QtsaQualification->>TrustedListService: getQualifiedServices()

    alt Cache valide (< 24h)
        TrustedListService->>TLCache: getTrustedList(country)
        TLCache-->>TrustedListService: cachedList
    else Cache expiré ou absent
        TrustedListService->>HTTP: GET eu-lotl.xml (List of Trusted Lists)
        HTTP-->>TrustedListService: LOTL XML
        TrustedListService->>TrustedListService: Vérifier signature LOTL

        loop Pour chaque pays
            TrustedListService->>HTTP: GET national-tl.xml
            HTTP-->>TrustedListService: National TL XML
            TrustedListService->>TrustedListService: Vérifier signature TL
        end

        TrustedListService->>TLCache: store(lists)
    end

    TrustedListService-->>QtsaQualification: trustedServices[]

    QtsaQualification->>QtsaQualification: Filtrer ServiceTypeIdentifier = "QCertESig"<br/>ou "QTimestamp"
    QtsaQualification->>QtsaQualification: Vérifier ServiceStatus = "granted"
    QtsaQualification->>QtsaQualification: Extraire certificat TSA
    QtsaQualification->>QtsaQualification: Comparer avec certificat du TST

    QtsaQualification->>DB: INSERT qualification_evidence {<br/>  tsa_cert_hash,<br/>  trusted_list_hash,<br/>  service_name,<br/>  policy_oid,<br/>  verification_time<br/>}

    QtsaQualification-->>BatchService: { qualified: true, evidence_id }

2.4 Flux de Génération de Preuve d'Inclusion (RFC 9162)

sequenceDiagram
    participant Client
    participant InclusionProofService
    participant MerkleService
    participant DB

    Client->>InclusionProofService: getProof(batchId, itemHash)
    InclusionProofService->>DB: SELECT batch, seal, items, tree_data
    InclusionProofService->>InclusionProofService: Vérifier status IN (SEALED, TIMESTAMPED)

    InclusionProofService->>MerkleService: computeInclusionProof(treeData, itemHash)
    MerkleService->>MerkleService: Localiser feuille (index)
    MerkleService->>MerkleService: Construire chemin d'audit
    MerkleService-->>InclusionProofService: { leaf_index, audit_path[] }

    InclusionProofService->>DB: SELECT timestamp_token WHERE batch_id

    Note over InclusionProofService: Format RFC 9162 InclusionProofDataV2
    InclusionProofService->>InclusionProofService: proof = {<br/>  log_id: batch_id,<br/>  tree_size: item_count,<br/>  leaf_index,<br/>  inclusion_path: audit_path,<br/>  leaf_hash: itemHash,<br/>  root_hash,<br/>  timestamp_token: TST<br/>}

    InclusionProofService-->>Client: InclusionProofDataV2

3. Mapping Invariants → Mécanismes

# Invariant Mécanisme d'Implémentation Preuve/Vérifiabilité
1 QTSA qualifiée eIDAS sur Trusted List QtsaQualificationService vérifie: (1) signature de la LOTL EU, (2) signature de la TL nationale, (3) ServiceTypeIdentifier contient "QTimestamp", (4) ServiceStatus = "granted", (5) certificat TSA présent dans la TL. Stockage de qualification_evidence pour audit. Evidence stockée avec hash TL, reproductible
2 Policy OID vérifiée Extraction OID depuis TSTInfo.policy. Vérification contre registre qtsa-registry.ts extrait des TL (pas de whitelist manuelle). L'OID doit correspondre à une policy qualifiée eIDAS. OID tracée dans validation_result
3 Conformité RFC 3161 TstValidatorService.validateRfc3161Structure() vérifie structure ASN.1 complète via pkijs: PKIStatus=0, ContentType=SignedData, eContentType=TSTInfo, hashAlgorithm autorisé, nonce si fourni. Validation ASN.1 déterministe
4 Empreinte agrégée unique, algorithme autorisé MerkleService applique RFC 6962 Merkle Tree Hash (MTH). Algorithme SHA-256 (seul autorisé pour compatibilité CT). ALLOWED_HASH_ALGORITHMS = ['SHA-256']. Algorithme normé, reproductible
5 Batch scellé avant horodatage Transition OPEN → SEALED via BatchSealService.seal() qui génère signature cryptographique HSM + attestation NTS. BatchSealedGuard vérifie signature avant toute opération post-scellement. Aucune extension possible : FK sur batch_id avec check status=OPEN. Scellement signé vérifiable
6 Structure d'agrégation déterministe et ordonnée Format RFC 6962 MTH : (1) feuilles = SHA-256(0x00 \ \
7 Preuve d'inclusion normalisée et vérifiable Format RFC 9162 InclusionProofDataV2: {log_id, tree_size, leaf_index, inclusion_path[], leaf_hash, root_hash}. Chemin d'audit = liste de hashes avec position (L/R). Standard CT, outils tiers compatibles
8 Pas d'heure locale comme référence ReferenceClockService lit l'horloge système disciplinée par ntpd-rs (NTS RFC 8915). Validation via observe.json: offset < 5ms, stratum ≤ 3, ≥ 2 sources NTS actives. REJET si synchronisation non valide. Pas de fallback NTP non authentifié. Horloge OS synchronisée NTS, observation signée
9 Validations via horloge de référence ClockAttestationService.validateTemporalCoherence() compare genTime TST avec attestation signée. L'attestation inclut le snapshot observe.json signé par HSM. Tolérance: 5 min. Chaque opération génère une attestation signée stockée prouvant l'état de synchronisation NTS au moment T. Attestation signée HSM + snapshot ntpd-rs
10 Éléments acceptés immuables Triple protection: (1) Scellement cryptographique (signature HSM), (2) Triggers PostgreSQL interdisant UPDATE/DELETE sur batch_seal, batch_item après scellement, (3) Row-level security + audit PD-17 sur tentatives. Option: stockage S3 Glacier avec Object Lock (WORM). Signature + DB locks + audit

4. Gestion des Erreurs

4.1 Codes d'Erreur Spécifiques

enum TsaErrorCode {
  // Erreurs de batch
  BATCH_NOT_FOUND = 'TSA_BATCH_NOT_FOUND',
  BATCH_ALREADY_SEALED = 'TSA_BATCH_ALREADY_SEALED',
  BATCH_NOT_SEALED = 'TSA_BATCH_NOT_SEALED',
  BATCH_ALREADY_TIMESTAMPED = 'TSA_BATCH_ALREADY_TIMESTAMPED',
  BATCH_EMPTY = 'TSA_BATCH_EMPTY',
  BATCH_SEAL_INVALID = 'TSA_BATCH_SEAL_INVALID',

  // Erreurs QTSA
  QTSA_UNREACHABLE = 'TSA_QTSA_UNREACHABLE',
  QTSA_NOT_QUALIFIED = 'TSA_QTSA_NOT_QUALIFIED',
  QTSA_QUALIFICATION_EXPIRED = 'TSA_QTSA_QUALIFICATION_EXPIRED',
  QTSA_RESPONSE_INVALID = 'TSA_QTSA_RESPONSE_INVALID',

  // Erreurs de validation TST
  TST_RFC3161_INVALID = 'TSA_TST_RFC3161_INVALID',
  TST_SIGNATURE_INVALID = 'TSA_TST_SIGNATURE_INVALID',
  TST_CHAIN_INVALID = 'TSA_TST_CHAIN_INVALID',
  TST_REVOKED = 'TSA_TST_REVOKED',
  TST_REVOCATION_INDETERMINATE = 'TSA_TST_REVOCATION_INDETERMINATE',
  TST_POLICY_NOT_QUALIFIED = 'TSA_TST_POLICY_NOT_QUALIFIED',
  TST_HASH_MISMATCH = 'TSA_TST_HASH_MISMATCH',
  TST_TEMPORAL_DRIFT = 'TSA_TST_TEMPORAL_DRIFT',

  // Erreurs de preuve
  PROOF_ITEM_NOT_IN_BATCH = 'TSA_PROOF_ITEM_NOT_IN_BATCH',
  PROOF_VERIFICATION_FAILED = 'TSA_PROOF_VERIFICATION_FAILED',

  // Erreurs système probatoires
  TRUSTED_LIST_SIGNATURE_INVALID = 'TSA_TRUSTED_LIST_SIGNATURE_INVALID',
  TRUSTED_LIST_EXPIRED = 'TSA_TRUSTED_LIST_EXPIRED',
  SEAL_SIGNATURE_FAILED = 'TSA_SEAL_SIGNATURE_FAILED',
  HSM_UNAVAILABLE = 'TSA_HSM_UNAVAILABLE',

  // Erreurs ntpd-rs / horloge de référence
  REFERENCE_CLOCK_NOT_SYNCHRONIZED = 'TSA_REFERENCE_CLOCK_NOT_SYNCHRONIZED',
  REFERENCE_CLOCK_OFFSET_TOO_HIGH = 'TSA_REFERENCE_CLOCK_OFFSET_TOO_HIGH',
  REFERENCE_CLOCK_STRATUM_TOO_HIGH = 'TSA_REFERENCE_CLOCK_STRATUM_TOO_HIGH',
  REFERENCE_CLOCK_INSUFFICIENT_SOURCES = 'TSA_REFERENCE_CLOCK_INSUFFICIENT_SOURCES',
  NTPD_RS_OBSERVE_FILE_MISSING = 'TSA_NTPD_RS_OBSERVE_FILE_MISSING',
  NTPD_RS_OBSERVE_FILE_STALE = 'TSA_NTPD_RS_OBSERVE_FILE_STALE',
}

4.2 Politique Probatoire pour Statuts Indéterminés

Situation Comportement Probatoire Justification
CRL indisponible REJET si cache > 24h, sinon cache accepté avec warning Statut révocation doit être vérifiable
OCSP indisponible Fallback CRL obligatoire. Si CRL aussi indispo → REJET Pas de "soft-fail" en contexte probatoire
OCSP = UNKNOWN REJET avec code TST_REVOCATION_INDETERMINATE Unknown ≠ non-révoqué
Trusted List expirée REJET si > 7 jours, warning si > 48h Qualification doit être vérifiable
ntpd-rs non synchronisé REJET de l'opération si: offset > 5ms, stratum > 3, ou < 2 sources actives Invariant 8/9 non négociable
Fichier observe.json absent/périmé REJET si fichier absent ou âge > 30s ntpd-rs doit être opérationnel
HSM indisponible REJET du scellement, batch reste OPEN Pas de scellement sans signature

4.3 Stratégies de Récupération

Erreur Stratégie Retry Fallback
QTSA_UNREACHABLE Retry exponentiel 3x (1s, 5s, 30s) QTSA secondaire qualifiée
TRUSTED_LIST_EXPIRED Refresh immédiat 2x Cache si < 7 jours + alerte
REFERENCE_CLOCK_NOT_SYNCHRONIZED Attendre resync ntpd-rs Polling 5s, max 30s Aucun (rejet après timeout)
NTPD_RS_OBSERVE_STALE Vérifier service ntpd-rs Alerte ops Aucun (rejet)
HSM_UNAVAILABLE Retry 3x (1s, 5s, 15s) Aucun (rejet)
TST_REVOCATION_INDETERMINATE Log + rejet Non Aucun (rejet)

4.4 Circuit Breaker pour QTSA

interface QtsaCircuitBreaker {
  state: 'CLOSED' | 'OPEN' | 'HALF_OPEN';
  failureCount: number;
  failureThreshold: 5;
  resetTimeout: 60000; // 1 minute
  lastFailure: Date;
  // Nouveau: basculement automatique
  alternateQtsa?: QtsaConfig;
  failoverEnabled: boolean;
}

5. Impacts Sécurité

5.1 Surfaces d'Attaque et Mitigations

Surface Risque Mitigation
Replay TST Réutilisation d'un TST pour un autre batch Vérification messageImprint == rootHash calculé depuis scellement
Manipulation Merkle post-scellement Altération de l'arbre après clôture Signature HSM du scellement + triggers DB interdisant modification
Accès DB privilégié Modification directe en base Triggers PostgreSQL + Row-Level Security + audit PD-17 + option WORM Glacier
Man-in-the-Middle QTSA Interception/modification requête/réponse TLS 1.3 obligatoire, vérification certificat vs Trusted List
Horloge falsifiée Manipulation de l'heure NTS authentifié uniquement, attestations stockées
NTP spoofing Injection de temps falsifié NTS obligatoire (authentification cryptographique), pas de fallback NTP
Trusted List compromise QTSA non qualifiée acceptée Vérification signature LOTL/TL, stockage evidence
Déni de service batch Création massive de batchs Rate limiting, quota par organisation

5.2 Données Sensibles et Protection

Donnée Classification Protection Rétention
root_hash Métadonnée cryptographique Intégrité via scellement signé Durée de vie batch
seal_signature Preuve probatoire HSM pour génération, lecture seule Idem TST
tst_raw Preuve probatoire Stockage chiffré AES-256-GCM 10 ans minimum
tree_data (CBOR) Structure Merkle Intégrité via seal, backup Idem TST
clock_attestation Preuve temporelle NTS response stockée intégralement Idem TST
qualification_evidence Preuve qualification Hash TL + timestamp Idem TST
Clés HSM Secret HSM uniquement, jamais exportées N/A

5.3 Audit Trail (PD-17)

Actions auditées avec contexte probatoire:

Action Métadonnées Capturées
BATCH_CREATED batch_id, creator_id, organization_id
BATCH_ITEM_ADDED batch_id, item_hash, item_index
BATCH_SEALED batch_id, root_hash, seal_id, attestation_id
BATCH_SEAL_VERIFIED batch_id, seal_valid, verifier_id
BATCH_TIMESTAMPED batch_id, tst_id, genTime, qtsa_id
TST_VALIDATED tst_id, validation_result, evidence_id
TST_VALIDATION_FAILED tst_id, error_code, details
INCLUSION_PROOF_GENERATED batch_id, item_hash, proof_hash
INCLUSION_PROOF_VERIFIED proof_hash, valid, verifier_id
QUALIFICATION_VERIFIED qtsa_id, qualified, tl_hash
IMMUTABILITY_VIOLATION_ATTEMPT batch_id, operation, actor_id, ALERTE

6. Spécifications Normatives

6.1 Format Structure d'Agrégation (Merkle Tree)

Conformité RFC 6962 Section 2.1 (Certificate Transparency):

MTH (Merkle Tree Hash):

MTH({}) = SHA-256()  // arbre vide
MTH({d(0)}) = SHA-256(0x00 || d(0))  // feuille unique

Pour n > 1, k = 2^(floor(log2(n-1))):
MTH(D[n]) = SHA-256(0x01 || MTH(D[0:k]) || MTH(D[k:n]))

Règles de construction : 1. Préfixe feuille : 0x00 (distingue feuilles des nœuds internes) 2. Préfixe nœud : 0x01 3. Ordre des feuilles : Tri lexicographique croissant des hash en bytes 4. Encodage hash : Hexadécimal lowercase 5. Algorithme : SHA-256 exclusivement (compatibilité CT)

Sérialisation arbre (CBOR, RFC 8949) :

{
  "version": 1,
  "algorithm": "SHA-256",
  "tree_size": <uint>,
  "root_hash": <bytes32>,
  "leaves": [<bytes32>, ...],  // ordonnées
  "nodes": [<bytes32>, ...]    // ordre level-order
}

6.2 Format Preuve d'Inclusion (RFC 9162)

interface InclusionProofDataV2 {
  /** Identifiant unique du log (batch_id) */
  log_id: string;

  /** Taille de l'arbre au moment de la preuve */
  tree_size: number;

  /** Index de la feuille (0-based) */
  leaf_index: number;

  /** Chemin d'audit: liste de hashes frères */
  inclusion_path: Array<{
    hash: string;      // hex lowercase
    position: 'L' | 'R';  // position du frère
  }>;

  /** Hash de l'élément prouvé */
  leaf_hash: string;

  /** Racine Merkle */
  root_hash: string;

  /** Jeton d'horodatage RFC 3161 (base64) */
  timestamp_token: string;

  /** Scellement du batch (optionnel pour vérification) */
  batch_seal?: {
    payload_canonical: string;  // JSON canonique
    signature: string;          // base64
    algorithm: string;          // ex: "ECDSA-P384"
  };
}

Algorithme de vérification :

1. hash = SHA-256(0x00 || leaf_data)
2. Pour chaque (sibling, position) dans inclusion_path:
     Si position == 'L':
       hash = SHA-256(0x01 || sibling || hash)
     Sinon:
       hash = SHA-256(0x01 || hash || sibling)
3. Vérifier: hash == root_hash
4. Vérifier: root_hash == messageImprint du TST
5. Valider TST (signature, chaîne, révocation, qualification)

6.3 Format Scellement Batch

interface BatchSealPayload {
  /** Version du format */
  version: 1;

  /** Identifiant batch */
  batch_id: string;

  /** Racine Merkle */
  root_hash: string;

  /** Nombre d'éléments */
  item_count: number;

  /** Algorithme Merkle */
  tree_algorithm: "RFC6962_SHA256_MTH";

  /** Horodatage NTS authentifié */
  sealed_at: {
    timestamp: string;  // ISO 8601
    nts_server: string;
    nts_cookie_hash: string;  // SHA-256 du cookie AEAD
  };

  /** Hash de l'arbre sérialisé */
  tree_data_hash: string;
}

interface BatchSeal {
  /** Payload canonique (RFC 8785 JCS) */
  payload_canonical: string;

  /** Signature du payload */
  signature: string;  // base64

  /** Algorithme de signature (HsmService PD-36) */
  algorithm: "ECDSA_SHA384";  // SignatureAlgorithm.ECDSA_SHA384 (P-384, 192-bit security)

  /** Label clé HSM */
  key_label: string;  // Label dans HsmService

  /** Référence attestation NTS */
  clock_attestation_id: string;
}

7. Politique de Conservation et Archivage

7.1 Durées de Rétention

Élément Durée Minimale Justification
TST (jeton horodatage) 10 ans Valeur probatoire légale
Scellement batch 10 ans Preuve de clôture
Arbre Merkle (CBOR) 10 ans Nécessaire pour preuves d'inclusion
Attestations NTS 10 ans Preuve d'usage horloge référence
Evidence qualification 10 ans Preuve QTSA qualifiée au moment T
Audit logs 10 ans Traçabilité
Éléments du batch Selon politique métier Min = durée TST

7.2 Format d'Archivage Long Terme

Option WORM recommandée : AWS S3 Glacier avec Object Lock (Compliance mode)

interface ArchivePackage {
  /** Métadonnées */
  metadata: {
    batch_id: string;
    archived_at: string;
    format_version: 1;
    checksums: {
      sha256: string;
      sha384: string;
    };
  };

  /** Contenu archivé */
  content: {
    tst_raw: Buffer;           // DER
    batch_seal: BatchSeal;
    tree_data: Buffer;         // CBOR
    clock_attestations: ClockAttestation[];
    qualification_evidence: QualificationEvidence;
    audit_log_extract: AuditEntry[];
  };
}

7.3 Intégrité et Vérification Périodique

  • Checksum quotidien sur TST stockés
  • Vérification mensuelle : re-calcul racine Merkle vs stockée
  • Vérification annuelle : validation complète TST (signature, chaîne)
  • Alerte si divergence détectée

8. Hypothèses Techniques

8.1 Dépendances Externes

Dépendance Hypothèse Validation Fallback
LOTL EU https://ec.europa.eu/tools/lotl/eu-lotl.xml accessible Test démarrage Cache 7 jours max
TL nationales URLs dans LOTL accessibles Test démarrage Cache 7 jours max
ntpd-rs Démon NTS actif, observe.json accessible en lecture Health check 10s Aucun (rejet si non synchronisé)
QTSA Endpoint RFC 3161 sur HTTPS, réponse < 10s Health check 5min QTSA secondaire
CRL/OCSP Endpoints dans certificats accessibles Cache 24h Rejet si indisponible
HSM (PD-36) HsmService initialisé, clés provisionnées Health check 1min Aucun (rejet)

8.2 Infrastructure

Élément Exigence
Redis Disponible pour BullMQ (PD-21)
PostgreSQL Support BYTEA, triggers, RLS
HSM HsmService (PD-36) — CloudHSM via PKCS#11
S3 Optionnel: Glacier avec Object Lock pour WORM
Réseau Accès Internet sortant pour QTSA, TL, NTS

8.3 Bibliothèques

Fonctionnalité Bibliothèque Justification
Arbre Merkle @noble/hashes + custom RFC 6962 Audit sécurité, pas de dépendances
ASN.1 / RFC 3161 pkijs + asn1js Standard PKI, maintenu
NTS (horloge) ntpd-rs (externe) + lecture observe.json Pas de lib Node.js NTS viable, démon Rust mature
CBOR cbor-x Performant, TypeScript
JSON canonique json-canonicalize RFC 8785 JCS
XML Trusted Lists fast-xml-parser Parsing TL eIDAS
HSM HsmService (PD-36)src/modules/crypto/hsm/ Module interne existant, PKCS#11 via pkcs11js

HSM — Infrastructure existante (PD-36)

Le module HSM est déjà implémenté dans src/modules/crypto/hsm/. PD-39 réutilise cette infrastructure :

// Interfaces disponibles (pkcs11.interface.ts)
enum SignatureAlgorithm {
  ECDSA_SHA256 = 'ECDSA_SHA256',
  ECDSA_SHA384 = 'ECDSA_SHA384',  // ← Utilisé pour scellement PD-39 (P-384, 192-bit)
  RSA_PSS_SHA256 = 'RSA_PSS_SHA256',
  RSA_PKCS_SHA256 = 'RSA_PKCS_SHA256',
}

enum EcdsaCurve {
  P256 = 'P256',
  P384 = 'P384',  // ← Courbe pour PD-39 (sécurité long terme)
}

// HsmService (hsm.service.ts)
class HsmService {
  sign(data: Buffer, keyLabel: string, algorithm: SignatureAlgorithm): Promise<SignatureResult>;
  verify(data: Buffer, signature: Buffer, keyLabel: string, algorithm: SignatureAlgorithm): Promise<VerificationResult>;
  getPublicKey(keyLabel: string): Promise<Buffer | null>;
  generateKeyPair(keyType: KeyType, attributes: KeyAttributes): Promise<KeyGenerationResult>;
}

// Génération clé P-384 :
// hsmService.generateKeyPair(KeyType.ECDSA, { label: 'tsa-batch-seal-key', id: '...', keyType: KeyType.ECDSA, curve: EcdsaCurve.P384 });

Configuration requise (variables d'environnement) :

  • CLOUDHSM_LIBRARY_PATH/opt/cloudhsm/lib/libcloudhsm_pkcs11.so
  • CLOUDHSM_PIN — PIN Crypto User
  • CLOUDHSM_SLOT — Slot ID (défaut: 0)

Clés à provisionner dans le HSM :

Label Type Usage
tsa-batch-seal-key ECDSA P-384 Signature scellement batch
tsa-clock-attestation-key ECDSA P-384 Signature attestations NTS

9. Points de Vigilance

9.1 Risques Résiduels

Risque Probabilité Impact Mitigation Acceptation
Compromission HSM Très faible Critique Audit HSM, rotation clés Risque résiduel accepté
Indisponibilité NTS globale Très faible Bloquant Multi-serveurs, monitoring Opérations suspendues
Révocation QTSA Faible Élevé Monitoring TL, QTSA secondaire Nouveau TST sur autre QTSA
Corruption stockage Faible Élevé Checksums, backups, WORM Restauration backup

9.2 Tests Critiques

Test Type Criticité Couverture Invariant
Scellement empêche ajout Unitaire Critique 5, 10
Signature scellement valide Unitaire Critique 5, 10
Trigger DB bloque UPDATE Intégration Critique 10
Preuve inclusion valide RFC 9162 Unitaire Critique 6, 7
Rejet élément non inclus Unitaire Critique 7
Validation QTSA via TL Intégration Critique 1, 2
Rejet QTSA non qualifiée Unitaire Élevée 1
Rejet OID non qualifiée Unitaire Élevée 2
Validation TST RFC 3161 Intégration Critique 3
Rejet si NTS auth échoue Unitaire Critique 8, 9
Attestation NTS stockée Unitaire Élevée 9
Rejet si OCSP=UNKNOWN Unitaire Élevée Politique probatoire
Rejet si CRL expirée Unitaire Élevée Politique probatoire
Circuit breaker QTSA Intégration Élevée Disponibilité

9.3 Points Clarifiés (vs section 10 spécification)

Point Décision
Algorithme empreinte agrégée SHA-256 (RFC 6962 MTH)
Format structure d'agrégation RFC 6962 Merkle Tree Hash, sérialisation CBOR
Format preuve d'inclusion RFC 9162 InclusionProofDataV2
Politique de conservation 10 ans minimum, archivage WORM optionnel
Référentiel Policy OID Extraction automatique des Trusted Lists eIDAS

10. Estimation Effort par Composant

Composant Complexité Priorité Dépendances
BatchService + Entités Moyenne P0 -
BatchSealService + HSM Élevée P0 HSM disponible
MerkleService (RFC 6962) Moyenne P0 -
TsaClientService Moyenne P0 -
TstValidatorService Élevée P0 QtsaQualificationService
QtsaQualificationService Élevée P0 TrustedListService
TrustedListService Élevée P0 -
InclusionProofService (RFC 9162) Moyenne P0 MerkleService
ReferenceClockService (NTS) Élevée P0 -
ClockAttestationService Moyenne P0 ReferenceClockService
RevocationService Moyenne P1 -
Triggers PostgreSQL Faible P0 -
Tests d'intégration Élevée P0 Tous composants

Fin du plan d'implémentation PD-39 v3.0 (Architecture ntpd-rs).