Scope messagerie E2EE en M5 (ECIES per-message)
source : docs/adr/0006-messaging-e2ee-scope-m5.md · versionné · MADR-lite
ADR-0006 — Scope messagerie E2EE en M5 (ECIES per-message)
- Statut : Accepted
- Date : 2026-04-24
- Décideurs : Core team OmbrysWeb
- Issues GitHub : #62 (double ratchet), #66 (wrapping clés), #67 (fichiers), #68 (push)
- Références : ADR-0002 (stratégie crypto), cahier des charges § 4.2, § 5.2
Contexte
Le cahier des charges (§ 4.2 et issue #62) prévoit un double ratchet de type Signal pour la messagerie E2EE. Implémenter correctement le double ratchet — X3DH + Diffie-Hellman ratchet + chain keys + out-of-order handling — nécessite :
- plusieurs milliers de lignes de crypto code,
- une revue indépendante par un cryptographe compétent en protocoles avancés,
- des tests de vecteurs d'interop et des scénarios adversariaux.
Le planning M5 (5 semaines) ne permet pas de livrer cela et le reste du scope (inbox backend, UI chat, contact management, wrapping clés) avec le niveau de rigueur attendu.
Décision
M5 livre un chiffrement ECIES per-message qui fournit les propriétés essentielles attendues d'une messagerie E2EE :
- Confidentialité de bout en bout : le serveur
messaging-svcne voit
jamais le plaintext. Il n'a accès qu'à des ciphertexts opaques. - Forward secrecy côté expéditeur : chaque message utilise une paire ECDH P-256 éphémère, jetée immédiatement après envoi. - Authentification : l'AAD inclut from/to/ts/id et est intégrée au tag AEAD. Tampering détectable.
Le double ratchet complet (DH-ratchet + skipped keys + Kyber hybride) est différé en M6, traité comme un hardening audité.
Schéma M5 (ECIES)
Alice → Bob :
1. (eph_sk, eph_pk) = ECDH.keygen(P-256) # paire éphémère
2. shared = ECDH(eph_sk, bob_long_pk)
3. K = HKDF-SHA-384(shared,
salt = eph_pk_spki,
info = "ombrys.msg/v1",
L = 32)
4. nonce = csprng(12)
5. aad = JSON { v=1, id, from, to, ts }
6. ciphertext, tag = AES-256-GCM(K, nonce, plaintext, aad)
7. envelope = [eph_pk_spki, nonce, aad, ciphertext+tag]
8. send(envelope) ; drop eph_skBob :
1. shared = ECDH(bob_long_sk, eph_pk)
2. K = HKDF-SHA-384(shared, ... same parameters ...)
3. plaintext = AES-256-GCM.decrypt(K, nonce, ciphertext+tag, aad)Identité long-terme
- Paire ECDH P-256 générée au premier login par Web Crypto API.
- Clé privée exportée en PKCS#8, chiffrée par K_master (AES-256-GCM,
clé dérivée via HKDF-SHA-384(K_master, "ombrys.msg/wrap/v1")), stockée dans IndexedDB. - Clé privée ré-importée non-extractable en début de session (crypto.subtle.importKey("pkcs8", …, extractable=false, ["deriveBits"])). - Fingerprint : SHA-384(SPKI) tronqué 12 octets, affiché en hex avec séparateurs :. Partagé hors-bande pour vérification manuelle.
Contacts
- Ajout manuel par l'utilisateur : username + clé publique SPKI (base64url).
- Stockés dans IndexedDB (object store
contacts, keyPathusername). - Flag
verified: boolean— vérifié hors-bande (fingerprint matching). - Pas de découverte automatique (matches ADR-0003 § 4 minimisation metadata).
Conséquences
Positives
- E2EE réel livré en M5, utilisable end-to-end.
- Forward secrecy côté expéditeur, meilleur qu'un simple "encrypt-to-long-term-key".
- Base de code auditable en quelques heures par un reviewer crypto.
- Évite d'introduire un protocole complexe bogué — la plupart des failles
crypto viennent d'implémentations custom d'algorithmes complexes.
Négatives
- Pas de forward secrecy côté destinataire : si
bob_long_skfuit
plus tard, tous ses messages passés deviennent décryptables. Menace réelle mais bornée par le warrant canary + le soin utilisateur. - Pas de post-compromise recovery : un attaquant qui obtient bob_long_sk peut déchiffrer les messages futurs jusqu'à rotation manuelle. - Pas d'out-of-order handling natif : les messages sont indépendants, donc tout arrivé arrive, mais les states ratchet attendus par un utilisateur venant de Signal ne sont pas là. - Pas de hybride PQ : harvest-now-decrypt-later reste théoriquement possible sur horizon quantique. Messages courts → faible intérêt pratique pour un attaquant patient.
Neutres
- Format de ciphertext versionné (
info = "ombrys.msg/v1") : migration
vers M6 ratchet se fera via un nouveau tag v2, rétro-compat lisible pour anciens messages archivés.
Plan M6 (hardening issue #62)
- Implémenter X3DH avec hybride X25519+Kyber-768 (pre-keys publiées via
auth-svc ou distribuées hors-bande via QR signés). 2. Double ratchet : DH-ratchet (paires éphémères qui tournent à chaque round-trip), chain keys symétriques, skipped message keys pour out-of-order. 3. Session state persisté dans IndexedDB, wrappé par K_master. 4. Tests d'interop avec vecteurs Signal / libsignal-protocol quand applicable. 5. Audit crypto externe obligatoire avant la bascule prod.
Changelog
- 2026-04-24 : rédaction initiale, statut Accepted pour M5.