diff --git a/README.md b/README.md index bcb3f65..c4a9a77 100644 --- a/README.md +++ b/README.md @@ -69,9 +69,12 @@ Ce dépôt héberge le prototype **fonctionnel et vérifiable** du premier servi - une **heure traçable jusqu'à UTC** : le service recoupe deux serveurs de laboratoires de métrologie (Observatoire de Paris, PTB) et **cesse d'émettre** dès que la dérive dépasse le seuil annoncé, comme l'exige ETSI EN 319 421 ; -- un **journal d'audit chaîné par hachage, contresigné par des TSA tierces - publiques** (FreeTSA.org, DigiCert) : chaque scellement se vérifie avec les - outils RFC 3161 standards, sans dépendre de la confiance en Open eIDAS ; +- un **journal d'audit chaîné par hachage**, écrit *avant* chaque signature : + si le journal ne peut pas être écrit, aucun jeton ne sort. Le scellement de + la tête de chaîne par des TSA tierces publiques et la réplication hors site + existent en bibliothèques (`oe-crosstsa`, `oe-replicate`) mais **ne sont pas + encore câblés dans les services** — écart déclaré dans + [docs/CONFORMITE-ETSI.md](docs/CONFORMITE-ETSI.md) ; - le tout orchestré en `docker compose`, démarrable en une commande. Ce n'est pas encore une TSA qualifiée : les écarts avec le référentiel eIDAS @@ -120,9 +123,6 @@ awk '/BEGIN CERTIFICATE/{n++} {print > (n == 1 ? "tsu.pem" : "ca.pem")}' tsa-cha openssl ts -verify -in facture.tsr -queryfile facture.tsq -CAfile ca.pem ``` -L'interface d'administration de la PKI est disponible sur - (certificat auto-signé). - ### Relire le journal d'audit ```bash @@ -167,7 +167,7 @@ crates/oe-castore/ registre de la CA (PostgreSQL, et mémoire pour les tests crates/oe-hsm/ accès PKCS#11 aux clés de signature (seule crate autorisant `unsafe`) crates/oe-timesource/ surveillance de la traçabilité de l'heure crates/oe-audit/ journal d'audit chaîné par hachage -crates/oe-crosstsa/ contreseing du journal par des TSA tierces publiques +crates/oe-crosstsa/ contreseing du journal par des TSA tierces publiques (pas encore câblé) crates/oe-enroll/ client d'enrôlement auprès de la CA crates/oe-httpapi/ endpoints HTTP (RFC 3161 + façade JSON) deploy/ images des trois services, chart Helm diff --git a/crates/oe-conformance/src/lib.rs b/crates/oe-conformance/src/lib.rs index 5052b7b..c5de71f 100644 --- a/crates/oe-conformance/src/lib.rs +++ b/crates/oe-conformance/src/lib.rs @@ -630,10 +630,10 @@ pub fn system_matrix() -> Matrix { }, Entry { requirement: Requirement { standard: "ETSI EN 319 401", clause: "§7.10", title: "Journalisation des événements et durée de conservation" }, - status: Status::Covered, - mechanism: "Journal JSON Lines chaîné par SHA-256 (oe-audit) ; durée de conservation contrôlée à la configuration par oe_conformance::check_audit_retention, branché sur bin/ca-server::Config::load.", + status: Status::Gap, + mechanism: "Journal JSON Lines chaîné par SHA-256 (oe-audit), écrit avant chaque émission dans les deux services ; durée de conservation contrôlée à la configuration par oe_conformance::check_audit_retention, mais côté CA seulement (bin/ca-server::Config::load).", test: "crates/oe-audit/src/lib.rs (deux_ecrivains_partagent_la_meme_chaine), crates/oe-conformance/src/lib.rs (check_audit_retention_accepts_the_minimum, check_audit_retention_rejects_unconfigured_and_short_durations)", - target: "", + target: "Contrôler aussi la durée de conservation au démarrage de tsa-server (docs/CPS.md B.3).", }, Entry { requirement: Requirement { standard: "ETSI EN 319 401", clause: "§7.9", title: "Intégrité démontrable des enregistrements d'audit" }, @@ -644,10 +644,10 @@ pub fn system_matrix() -> Matrix { }, Entry { requirement: Requirement { standard: "ETSI EN 319 401", clause: "§7.11", title: "Continuité d'activité et reprise après sinistre" }, - status: Status::Covered, - mechanism: "Contreseing du journal par une TSA tierce (oe-crosstsa) et réplication WebDAV hors site (oe-replicate), validés contre un vrai serveur.", + status: Status::Gap, + mechanism: "Contreseing du journal par une TSA tierce (oe-crosstsa) et réplication WebDAV hors site (oe-replicate) écrits et testés contre un vrai serveur, mais appelés par aucun binaire : l'intégrité et la survie du journal reposent sur le contrôle d'accès et la sauvegarde de son volume.", test: "crates/oe-crosstsa/tests/against_local_server.rs (seals_a_digest_against_a_real_rfc3161_server), crates/oe-replicate/tests/against_local_server.rs (replicates_content_via_webdav_put)", - target: "", + target: "Constat J-1 : câbler dans tsa-server serve puis ca-server serve le scellement périodique (log.sealed), le contreseing tiers (log.cross_sealed) et la copie hors site (PR #49, #50 pour le stockage S3), testés de bout en bout sur le binaire ; dégrader /healthz sur échec prolongé.", }, Entry { requirement: Requirement { standard: "ETSI EN 319 401", clause: "§7.12", title: "Plan de cessation d'activité" }, diff --git a/crates/oe-tsa-core/src/lib.rs b/crates/oe-tsa-core/src/lib.rs index af712cd..46e5166 100644 --- a/crates/oe-tsa-core/src/lib.rs +++ b/crates/oe-tsa-core/src/lib.rs @@ -5,13 +5,11 @@ //! (`Clock`, équivalent du moniteur `oe-timesource`, jalon J4) pour produire //! et signer un jeton d'horodatage réel. //! -//! **Écart assumé face à `internal/conformance`** : la vérification du -//! profil du certificat TSU (`conformance.CheckTSUCertificate`, EN 319 421 -//! §7.7.2) n'est pas encore portée (`oe-conformance` la déclare `Gap`, -//! jalon J3) — `Authority::new` ne la reproduit donc pas encore. Ce qui est -//! bien vérifié ici : la correspondance clé publique du token ↔ certificat, -//! et la fenêtre de validité temporelle du certificat, comme dans `tsa.New` -//! (Go). +//! `Authority::new` vérifie la correspondance clé publique du token ↔ +//! certificat, la fenêtre de validité temporelle du certificat, comme dans +//! `tsa.New` (Go), et le profil du certificat TSU +//! (`oe_conformance::check_tsu_certificate`, portage de +//! `conformance.CheckTSUCertificate`). use std::sync::Arc; diff --git a/deploy/helm/open-eidas/values.yaml b/deploy/helm/open-eidas/values.yaml index 1f551b8..6b228df 100644 --- a/deploy/helm/open-eidas/values.yaml +++ b/deploy/helm/open-eidas/values.yaml @@ -53,6 +53,9 @@ tsa: sources: "ntp.obspm.fr,ptbtime1.ptb.de" minSources: 2 maxOffset: 500ms + # Scellement du journal et contreseing par des TSA tierces : valeurs + # transmises au service, qui ne les exploite PAS encore (écart J-1, voir + # docs/ARCHITECTURE.md §7). crossTSA: urls: "https://freetsa.org/tsr,http://timestamp.digicert.com" audit: diff --git a/docker-compose.yml b/docker-compose.yml index a66cdc4..ad909db 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -131,6 +131,8 @@ services: OPENEIDAS_TIME_MIN_SOURCES: "2" OPENEIDAS_TIME_MAX_OFFSET: 500ms OPENEIDAS_AUDIT_FILE: /var/lib/open-eidas/state/audit.log + # Scellement, contreseing tiers et réplication du journal : lus par la + # configuration mais PAS encore exploités (écart J-1, docs/ARCHITECTURE.md §7). OPENEIDAS_AUDIT_SEAL_INTERVAL: 1h OPENEIDAS_CROSS_TSA_URLS: ${OPENEIDAS_CROSS_TSA_URLS:-https://freetsa.org/tsr,http://timestamp.digicert.com} OPENEIDAS_AUDIT_REPLICA_URL: http://audit-replica/ diff --git a/docs/API.md b/docs/API.md index 0f67456..c0d60c1 100644 --- a/docs/API.md +++ b/docs/API.md @@ -198,10 +198,10 @@ Toutes les options sont pilotées par variables d'environnement. | `OPENEIDAS_SIGNING_DIGEST` | `sha256` | Empreinte utilisée pour signer le jeton | | `OPENEIDAS_MAX_REQUEST_BYTES` | `65536` | Taille maximale d'une requête | | `OPENEIDAS_AUDIT_FILE` | `/var/lib/open-eidas/audit.log` | Journal d'audit chaîné par hachage | -| `OPENEIDAS_AUDIT_SEAL_INTERVAL` | `1h` | Période de scellement de la tête de chaîne (`0` désactive) | -| `OPENEIDAS_CROSS_TSA_URLS` | `https://freetsa.org/tsr,http://timestamp.digicert.com` | TSA tierces contresignant chaque scellement, séparées par des virgules | +| `OPENEIDAS_AUDIT_SEAL_INTERVAL` | `1h` | Période de scellement de la tête de chaîne (`0` désactive). **Lue mais pas encore exploitée** : aucun scellement n'a lieu (écart J-1) | +| `OPENEIDAS_CROSS_TSA_URLS` | `https://freetsa.org/tsr,http://timestamp.digicert.com` | TSA tierces contresignant chaque scellement, séparées par des virgules. **Lue mais pas encore exploitée** (écart J-1) | | `OPENEIDAS_CROSS_TSA_TIMEOUT` | `15s` | Délai d'attente par TSA tierce | -| `OPENEIDAS_AUDIT_REPLICA_URL` | — | Base WebDAV où répliquer le journal à chaque scellement (vide = désactivé) | +| `OPENEIDAS_AUDIT_REPLICA_URL` | — | Base WebDAV où répliquer le journal à chaque scellement (vide = désactivé). **Lue mais pas encore exploitée** (écart J-1) | | `OPENEIDAS_AUDIT_REPLICA_USER` | — | Utilisateur WebDAV | | `OPENEIDAS_AUDIT_REPLICA_PASSWORD` | — | Mot de passe WebDAV | | `OPENEIDAS_AUDIT_REPLICA_TIMEOUT` | `30s` | Délai d'attente de la réplication | diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 312aac3..60a222c 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -194,67 +194,49 @@ quiconque relit le fichier — y compris sans accès au service : docker compose exec tsa tsa-server verify-audit ``` -Y sont consignés l'ouverture du journal, chaque jeton émis (numéro de série, -`genTime`, politique, empreinte soumise, présence d'un nonce), chaque refus -avec son `failureInfo`, chaque mesure de temps avec l'écart par source, et -chaque enrôlement de certificat. Le jeton émis est **relu avant d'être -consigné** : le journal enregistre ce que contient réellement le jeton, pas ce -que le service croit y avoir mis. +Y sont consignés l'ouverture du journal, chaque jeton émis +(`timestamp.granted` : `genTime`, politique, présence d'un nonce, et le numéro +de série du **certificat** TSU), chaque refus avec son `failureInfo` +(`timestamp.rejected`) et chaque mesure de temps avec l'écart par source +(`time.measurement`). La CA tient son propre journal chaîné, où figurent les +demandes d'enrôlement, les décisions RA, les émissions et les révocations. + +**Écart en cours de correction** (constat J-3 de l'audit du 2026-09-25, +PR #52) : le journal de la TSA ne consigne pas encore le numéro de série du +**jeton**, ni l'empreinte soumise, et le jeton n'est pas relu avant d'être +consigné. Deux propriétés rendent le dispositif exploitable : -- **Une écriture ratée annule l'émission.** Si le journal ne peut pas être - écrit, la requête échoue. Un jeton non tracé ne sort jamais du service. +- **Une écriture ratée annule l'émission.** L'événement est écrit *avant* la + signature : si le journal ne peut pas être écrit, la requête échoue et rien + n'est signé. Un jeton non tracé ne sort jamais du service. Il en va de même + côté CA pour l'émission, la révocation et la publication de la CRL. - **Un journal altéré empêche le démarrage.** La chaîne est vérifiée intégralement à l'ouverture. -La tête de chaîne est **scellée périodiquement** -(`OPENEIDAS_AUDIT_SEAL_INTERVAL`, une heure par défaut) : la TSU horodate sa -propre empreinte de tête et le jeton obtenu est inscrit au journal, ce qui -date son contenu. - -### Contreseing par des TSA tierces - -Le scellement ci-dessus reste auto-référentiel : il ne prouve l'antériorité à -un tiers que si l'on fait déjà confiance à la TSU elle-même. Le service -soumet donc la même tête de chaîne à une ou plusieurs **TSA publiques -indépendantes** (`OPENEIDAS_CROSS_TSA_URLS`, par défaut FreeTSA.org et -DigiCert), via le protocole RFC 3161 standard, et consigne chaque attestation -obtenue (`log.cross_sealed`) : émetteur, date, numéro de série et jeton -complet en base64. - -Un auditeur n'a besoin de rien d'Open eIDAS pour vérifier une attestation : le -certificat de la TSA tierce est public, et les outils standards suffisent — -```bash -openssl ts -query -digest -sha256 -no_nonce -out head.tsq -openssl ts -verify -in -queryfile head.tsq \ - -CAfile -untrusted -``` -— une réponse `Verification: OK` établit que la tête de chaîne, donc tout le -journal qu'elle couvre par construction, existait à la date attestée par une -autorité qui n'a aucun lien avec Open eIDAS. - -L'indisponibilité d'une TSA tierce est journalisée mais non bloquante : le -scellement propre au service continue, et les autres TSA configurées -prennent le relais. - -### Réplication hors site - -Un journal chaîné et contresigné ne protège que contre l'altération — pas -contre la perte de l'instance elle-même (panne disque, compromission, -suppression accidentelle). À chaque scellement, le service dépose donc une -copie complète et datée du journal (`audit--seq.log`) sur un -serveur **WebDAV** distant (`OPENEIDAS_AUDIT_REPLICA_URL`) : Nextcloud, un -stockage d'objets exposé en WebDAV, ou tout hébergeur souverain qui l'offre — -aucun fournisseur particulier n'est imposé. - -Chaque copie est un journal complet et vérifiable indépendamment : -```bash -tsa-server verify-audit audit-20260906T145600Z-seq000030.log -``` -retrouve exactement la même chaîne de hachage que sur l'instance d'origine, -jusqu'au numéro de séquence capturé. L'échec de la réplication est -journalisé mais non bloquant, comme pour le contreseing tiers. +### Limite : l'intégrité repose sur l'hôte (écart déclaré) + +Le chaînage SHA-256 n'utilise pas de clé : quiconque peut écrire dans le +fichier peut en tronquer la fin ou le réécrire en recalculant la chaîne, sans +que `verify-audit` le voie. Ce qui ancre la tête de chaîne hors du système +**n'est pas encore en service** (constat J-1) : + +- **Scellement périodique** par la TSU elle-même (`log.sealed`) : le format + est prévu (`oe_audit::EVENT_SEALED`, compté par `verify-audit`), mais aucun + service ne scelle. +- **Contreseing par des TSA tierces** indépendantes (`oe-crosstsa`, protocole + RFC 3161 standard, vérifiable par `openssl ts -verify` sans rien d'Open + eIDAS) : bibliothèque écrite et testée contre un vrai serveur, non câblée. +- **Réplication hors site** : `oe-replicate` (WebDAV) est écrit et testé, non + câblé ; une copie vers un stockage objet compatible S3 auto-hébergé est en + cours (PR #49 et #50). + +Les variables `OPENEIDAS_AUDIT_SEAL_INTERVAL`, `OPENEIDAS_CROSS_TSA_URLS` et +`OPENEIDAS_AUDIT_REPLICA_*` du `docker-compose.yml` et du chart Helm sont +lues sans être exploitées. D'ici là, l'intégrité du journal revient au +contrôle d'accès de son volume (voir [CPS.md](CPS.md) B.3 et +[CONFORMITE-ETSI.md](CONFORMITE-ETSI.md)). ## 8. Écarts assumés du prototype vis-à-vis d'une TSA qualifiée @@ -269,10 +251,10 @@ ci-dessous en donne la lecture d'ensemble. | Source de temps | Surveillance NTP de deux sources UTC(k) avec suspension automatique de l'émission | Réception redondante et indépendante, calibration documentée, journal des mesures conservé et audité | | Cérémonie de clé | Scriptée, idempotente, procès-verbal consigné au journal d'audit (empreintes, opérateur, horodatage) — mais sans double contrôle ni témoin | Double contrôle, témoin indépendant, HSM certifié, racine hors ligne après cérémonie (voir [CA.md](CA.md)) | | Approbation RA | Point d'approbation réellement actif : aucun chemin du code ne mène à l'émission sans décision d'un opérateur identifié, consignée en base et au journal. Automatisée sous un compte technique pour que la démonstration/CI s'amorce sans opérateur humain | Revue humaine réelle par un opérateur RA nominatif, à la place de l'approbation automatisée | -| Journalisation | Journal chaîné par hachage, contresigné par des TSA tierces publiques et répliqué hors site à chaque scellement ; durée de conservation contrôlée au démarrage | Politique de conservation formalisée, réplication multi-région | +| Journalisation | Journal chaîné par hachage, écrit avant chaque émission ; durée de conservation contrôlée au démarrage côté CA seulement. Scellement, contreseing tiers et réplication hors site **non câblés** (écart J-1) | Scellement et contreseing périodiques, copie hors site, conservation contrôlée côté TSA, politique de conservation formalisée | | Politique d'horodatage | OID de test `1.3.6.1.4.1.99999.1.1.1` ; brouillon de Policy/Practice Statement dans [CPS.md](CPS.md) | OID sous l'arc PEN de l'association, [CPS.md](CPS.md) adopté formellement et publié | | Profils de certificat | Structures Rust compilées et testées ; le certificat émis est relu depuis son DER et re-contrôlé avant délivrance ; CDP, AIA `ca_issuers` et répondeur OCSP réellement publiés et vérifiés | OID de politique de certification propre | -| Continuité | Instance unique ; registre PostgreSQL sauvegardable, journal répliqué hors site | Redondance active/active, sauvegarde et restauration testées, plan de cessation d'activité engagé (voir [CA.md](CA.md)) | +| Continuité | Instance unique ; registre PostgreSQL sauvegardable ; journal **non** répliqué hors site (écart J-1) | Redondance active/active, sauvegarde et restauration testées, plan de cessation d'activité engagé (voir [CA.md](CA.md)) | | Audit | Aucun | Évaluation par un organisme accrédité (LSTI, Apave), inscription à la liste de confiance | Le prototype refuse de démarrer sur les écarts qui rendraient les jetons ou diff --git a/docs/CA.md b/docs/CA.md index 457325e..817006a 100644 --- a/docs/CA.md +++ b/docs/CA.md @@ -381,12 +381,12 @@ l'URL, le serveur qui publie et le répondeur OCSP qui va chercher la CRL. |---|---|---| | Vol de la clé de la CA émettrice | La clé ne quitte jamais le token PKCS#11 ; seul le conteneur `ca` y accède | SoftHSM2 est logiciel : qui obtient le volume et le PIN obtient la clé. Un HSM certifié lève ce point | | Vol du secret HMAC d'enrôlement | Il authentifie le demandeur, il ne décide pas : toute demande reste soumise à approbation | Une clé volée permet de déposer des demandes, pas d'en faire émettre | -| Compromission d'un opérateur RA | Chaque décision est consignée avec son auteur ; le journal est chaîné, scellé et répliqué hors site | Un opérateur seul peut approuver : il n'y a pas de double validation | +| Compromission d'un opérateur RA | Chaque décision est consignée avec son auteur dans le journal chaîné | Un opérateur seul peut approuver : il n'y a pas de double validation. Le journal n'est ni scellé ni répliqué hors site (écart J-1) : un accès en écriture à son volume permet de le réécrire | | Numéro de série prédit ou rejoué | 128 bits sur `crypto/rand`, unicité portée par la clé primaire du registre | — | | Certificat non conforme émis | Le DER produit est relu et re-contrôlé avant d'être enregistré ; l'émission est annulée sinon | — | | CRL ancienne rejouée pour masquer une révocation | `CRLNumber` strictement croissant, servi par une séquence PostgreSQL ; le répondeur OCSP refuse de répondre plutôt que de servir un statut obsolète | — | -| Altération du journal d'audit | Chaînage par hachage vérifié à l'ouverture ; un journal altéré empêche le démarrage | — | -| Perte de l'instance | Registre PostgreSQL sauvegardable, journal répliqué hors site à chaque scellement | Sauvegarde et restauration non encore testées de bout en bout | +| Altération du journal d'audit | Chaînage par hachage vérifié à l'ouverture ; un journal altéré empêche le démarrage | Chaînage sans clé : une réécriture complète avec recalcul de la chaîne n'est pas détectée tant que la tête n'est pas scellée hors du système (écart J-1) | +| Perte de l'instance | Registre PostgreSQL sauvegardable | Journal non répliqué hors site (écart J-1) ; sauvegarde et restauration non encore testées de bout en bout | ## 8. Continuité et cessation d'activité @@ -398,8 +398,8 @@ Ce qui doit être sauvegardé, et suffit à reconstituer l'autorité : définitivement perdue ; 2. la **base PostgreSQL** — registre des certificats, demandes, historique des CRL ; -3. le **journal d'audit** (volume `castate`), déjà répliqué hors site à chaque - scellement. +3. le **journal d'audit** (volume `castate`) — pas encore répliqué hors site + (écart J-1) : à sauvegarder avec le reste. État actuel : instance unique, sauvegarde non automatisée. C'est un écart documenté ; la cible est une redondance active/active, une sauvegarde diff --git a/docs/CONFORMITE-ETSI.md b/docs/CONFORMITE-ETSI.md index 0a5dcf0..f28e5e9 100644 --- a/docs/CONFORMITE-ETSI.md +++ b/docs/CONFORMITE-ETSI.md @@ -5,7 +5,7 @@ correction se fait dans le code, pour que la matrice publiée reste celle que le système applique réellement. --> -**28 exigences** — 24 couvertes, 2 écarts documentés, 2 hors périmètre logiciel. +**28 exigences** — 22 couvertes, 4 écarts documentés, 2 hors périmètre logiciel. Trois statuts seulement, pour qu'aucune zone grise ne puisse s'y loger : @@ -18,9 +18,9 @@ Trois statuts seulement, pour qu'aucune zone grise ne puisse s'y loger : | Clause | Exigence | Statut | Mécanisme | Vérification / cible | |---|---|---|---|---| | §7.4 | Gestion des clés du prestataire dans un module cryptographique | couvert | Toutes les clés vivent dans un token PKCS#11 et n'en sortent jamais : oe-hsm::Pkcs11Token, validé contre un vrai token SoftHSM2 (crates/oe-hsm/tests/pkcs11_integration.rs). | crates/oe-hsm/tests/pkcs11_integration.rs | -| §7.10 | Journalisation des événements et durée de conservation | couvert | Journal JSON Lines chaîné par SHA-256 (oe-audit) ; durée de conservation contrôlée à la configuration par oe_conformance::check_audit_retention, branché sur bin/ca-server::Config::load. | crates/oe-audit/src/lib.rs (deux_ecrivains_partagent_la_meme_chaine), crates/oe-conformance/src/lib.rs (check_audit_retention_accepts_the_minimum, check_audit_retention_rejects_unconfigured_and_short_durations) | +| §7.10 | Journalisation des événements et durée de conservation | écart documenté | Journal JSON Lines chaîné par SHA-256 (oe-audit), écrit avant chaque émission dans les deux services ; durée de conservation contrôlée à la configuration par oe_conformance::check_audit_retention, mais côté CA seulement (bin/ca-server::Config::load). | **Cible :** Contrôler aussi la durée de conservation au démarrage de tsa-server (docs/CPS.md B.3). | | §7.9 | Intégrité démontrable des enregistrements d'audit | couvert | Chaînage par hachage vérifié intégralement à l'ouverture ; verrou de fichier partagé entre plusieurs écrivains d'un même processus : oe-audit::Log. | crates/oe-audit/src/lib.rs (deux_ecrivains_partagent_la_meme_chaine, verify_detects_modified_record, verify_detects_truncated_and_rewritten_tail) | -| §7.11 | Continuité d'activité et reprise après sinistre | couvert | Contreseing du journal par une TSA tierce (oe-crosstsa) et réplication WebDAV hors site (oe-replicate), validés contre un vrai serveur. | crates/oe-crosstsa/tests/against_local_server.rs (seals_a_digest_against_a_real_rfc3161_server), crates/oe-replicate/tests/against_local_server.rs (replicates_content_via_webdav_put) | +| §7.11 | Continuité d'activité et reprise après sinistre | écart documenté | Contreseing du journal par une TSA tierce (oe-crosstsa) et réplication WebDAV hors site (oe-replicate) écrits et testés contre un vrai serveur, mais appelés par aucun binaire : l'intégrité et la survie du journal reposent sur le contrôle d'accès et la sauvegarde de son volume. | **Cible :** Constat J-1 : câbler dans tsa-server serve puis ca-server serve le scellement périodique (log.sealed), le contreseing tiers (log.cross_sealed) et la copie hors site (PR #49, #50 pour le stockage S3), testés de bout en bout sur le binaire ; dégrader /healthz sur échec prolongé. | | §7.12 | Plan de cessation d'activité | hors périmètre logiciel | Procédure organisationnelle décrite dans docs/CA.md, indépendante du langage d'implémentation. | **Cible :** Engagement juridique de l'association, dépôt auprès de l'organe de contrôle, séquestre des journaux. | | §6.1 | Politique de service et déclaration des pratiques publiées | écart documenté | docs/CPS.md porte un brouillon structuré, déjà indépendant du langage d'implémentation du service. | **Cible :** Adoption formelle de docs/CPS.md par l'association (organisationnel, non affecté par le portage Rust). |