Sessions scellées de bout en bout (sealed sessions)
Android Console ouvre le chiffrement de bout en bout depuis les paramètres, avec choix de l’appareil sur la page. Les paramètres du compte Android et Web permettent de définir ou modifier le mot de passe et d’associer ou remplacer l’e-mail ; l’ancienne adresse reste active jusqu’à vérification. Console sauvegarde par défaut la clé racine dans Exchange avec une phrase secrète vide, sans écraser une sauvegarde protégée existante. La suppression désactive l’enregistrement automatique sur ce client. Une phrase vide permet à Exchange ou au détenteur de la base de déchiffrer la clé. La restauration manuelle vérifie la racine de l’espace avant de la conserver et de synchroniser les autorisations.
Objectif : même si Exchange est compromis ou son exploitant curieux, il ne peut lire commandes, sorties, octets PTY, fichiers, captures d’écran et données ubus, ni décider ou falsifier qui peut consulter ou administrer un appareil. Exchange devient un relais de texte chiffré et de métadonnées de routage.
Définitions : crates/proto/src/seal.rs (types réseau, autorisation locale), crates/proto/src/seal/crypto.rs (cryptographie des sessions, ring uniquement), crates/proto/src/seal/backup.rs (sauvegarde par phrase secrète, Argon2id + ChaCha20-Poly1305, non lié à l’agent), crates/agent/src/seal.rs (appareil), crates/cli/src/seal.rs (CLI), shared/seal-session.js (sessions communes aux consoles Web et Android, @noble/curves · @noble/hashes · @noble/ciphers), shared/seal-backup.js (sauvegarde Argon2id sur les mêmes bibliothèques). Vecteurs interlangages : shared/seal-vectors.json (générés par JS ; Rust javascript_vectors_interoperate vérifie signatures/HKDF/texte chiffré octet par octet), shared/seal-rust-vectors.json (réponse d’appareil Rust ; JS shared/seal-session.test.js termine la négociation et déchiffre).
seal approve publie toujours automatiquement les autorisations. Un appel suivant à seal grant --publish (ou la publication par défaut après connexion) ignore une autorisation publiée non expirée ayant les mêmes clés émettrice et destinataire, appareil et level, indique qu’elle est déjà publiée et n’émet aucun nouveau jeton. Utilisez --renew-before DAYS pour renouveler les autorisations proches de l’expiration ; --no-publish conserve la signature hors ligne. Ce contrôle évite les doublons lors de tentatives successives, sans transaction entre clients concurrents ni suppression des anciens doublons. seal show crée une identité locale et enregistre sa clé publique si vous êtes connecté ; utilisez un profile séparé pour les essais.
Modèle de menace
| Élément | Protection et limites des sessions scellées |
|---|---|
| Commande, stdout/stderr, code de sortie | Invisibles ; audit limité à sealed:true, clé publique opérateur, niveau d’autorisation, expiration et résultat final |
| Saisie PTY et sortie à l’écran | Invisibles, les trames chiffrées ne sont pas auditées |
| Fichiers, captures, arbre d’interface, paramètres et résultats ubus | Invisibles |
| Permissions (superadministrateur/administrateur/opérateur/lecture seule, consultation/gestion) | Décidées par l’appareil : seules les autorisations signées par sa racine locale épinglée sont reconnues ; Exchange ne peut les délivrer, étendre ou falsifier |
| Usurpation d’appareil | Impossible : signature d’identité native et clé publique de l’appareil épinglée par la CLI ; l’extension vérifie la preuve de chaîne racine et la signature de réponse |
| Injection de touches ou de commandes | Impossible : authentification AEAD et compteurs strictement croissants ; l’agent rejette tout contenu en clair adressé à un identifiant de session scellée |
| Rejeu, réordonnancement, perte, troncature | Détectés, avec fermeture de la session en cas d’échec |
| Métadonnées encore visibles | Identifiant d’appareil, compte opérateur, identifiant de session, horaires, taille et nombre de paquets ; la distinction stdout/stderr reste invisible (type dans le contenu chiffré) |
| Actions encore possibles | Déni de service : ne pas transmettre, déconnecter ou fermer une session (sealed_close). Cela concerne la disponibilité, pas les permissions |
Hors protection : appareil ou ordinateur de l’opérateur compromis ; usurpation par Exchange dès la première connexion TOFU, évitable par comparaison d’empreintes sur un canal indépendant. Toutes les opérations sur les appareils exigent des sessions scellées ; les limites du catalogue de commandes et de l’autorisation par chaîne racine du navigateur figurent ci-dessous.
Couverture des chemins et conception
| Chemin | Comportement des sessions scellées | Autorisation sur l’appareil et limites |
|---|---|---|
HTTP POST /api/devices/{id}/exec |
Refus explicite, HTTP 409, indication d’utiliser une session WebSocket open_sealed |
L’appareil refuse toujours exec_req en clair même si Exchange ignore la politique |
HTTP de commande prédéfinie /commands/{cmd}/run |
Refus explicite, même indication d’utiliser un WS scellé | Une commande de la base serveur ne constitue pas une autorisation sur l’appareil |
| Commandes prédéfinies Web Console / CLI | Modèle extrait du catalogue et envoyé par l’opérateur comme exec_req dans un WS scellé ; sortie également scellée |
L’appareil vérifie hello, grant signé par la racine, révocation, validité et niveau manage ; view ne permet pas d’exécuter un modèle |
LAN /ws/operator |
Sessions scellées uniquement, grant et chaîne de certificats inclus dans hello ; aucun jeton Bearer | Même chaîne racine et mêmes règles view/manage qu’Exchange. La couche externe ne vérifie que l’adresse privée et Origin. /api/devices expose seulement l’identifiant de l’appareil et la clé/le mode de scellement |
| Agent navigateur / routes scellées natives | open_sealed / sealed_input / sealed_event, ancien canal navigateur refusé |
L’extension vérifie le grant et la chaîne opérateur signés par la racine ; CLI/Console vérifient la chaîne de SealAccept.peer et exigent que sa clé publique soit celle qui signe la réponse |
CLI exec-attach / exec-hold-tail / exec-hold-cat |
Requêtes natives, trames de sortie et événements de fin scellés | manage requis ; les tâches scellées ne peuvent être lues ni rejointes en clair |
CLI sensors --live |
Une session scellée indépendante par échantillon (une opération par session), Ctrl-C arrête le rafraîchissement | view suffit ; plusieurs échantillons ne sont pas regroupés dans un tunnel à opération unique |
CLI net |
net_req et net_resp scellés, y compris URL, en-têtes, corps et résultats |
manage requis |
CLI device update |
update_req, toute la progression et l’état final sont scellés |
manage requis ; la commande CLI update au premier niveau met à jour le client local, ce n’est pas une session distante |
| exec / PTY / file / ubus / Android / sensors ponctuel / hold list, kill | Implémentation scellée native existante conservée | Même required_level et même répartiteur côté appareil |
| Liste des appareils, métriques, état en ligne, liste Exchange sessions | Métadonnées de routage et de supervision, pas des opérations scellées sur l’appareil | Visibles par Exchange ; ne constituent pas une autorisation sur l’appareil |
HTTP et commandes prédéfinies
Les opérations chiffrées natives acceptent désormais des clients utilisant uniquement HTTP. Utilisez remote-cli --transport http ou REMOTE_TRANSPORT=http. POST /api/sealed/connections crée une connexion ; POST /input transmet la négociation signée ou le texte chiffré, GET /events récupère les résultats et DELETE ferme la connexion. Le client conserve la cryptographie et la vérification des autorisations. Les anciens points d’action renvoient toujours 409 ; la connexion de l’appareil reste identique.
https://remote.ivjn.us/sealed-http.md
Création, édition et liste des prédéfinis restent l’administration de la base Exchange. Modèles, noms et descriptions du catalogue ne sont ni scellés ni une liste de commandes autorisées signée par la racine. Console et CLI détenteurs de clés exécutent le modèle comme exec scellé ordinaire (sans hold). L’appareil fait confiance à sa politique et au grant, pas au catalogue, au rôle serveur ou à une étiquette « prédéfini ». view ne suffit pas, la racine doit accorder manage. Altérer le catalogue n’étend pas le grant, mais manage permet déjà tout exec : vérifiez les modèles, sans mots de passe/jetons dans le catalogue. Sorties déchiffrées et affichées localement seulement ; audit limité aux métadonnées sealed, sans commande ni sortie.
LAN et sélection côté client
Les sessions d’appareil de la CLI utilisent exclusivement le scellement de bout en bout, sans dépendre de seal_key, des capacités ou des indications de politique annoncés par Exchange. Des informations de capacité absentes ou supprimées ne provoquent aucun passage en clair. Une erreur de lecture de la base locale d’épinglage est signalée immédiatement, sans ignorer les enregistrements de confiance endommagés.
Exec scellés avec/sans hold et tâches fichiers sont marqués avant publication dans le répertoire global, empêchant une fermeture avant le premier événement sortant de révéler un aperçu en clair. Les journaux appareil ne conservent ni cible/méthode de sonde HTTP, ni objet/méthode ubus.
LAN réutilise enveloppes natives, transcript signé, compteurs, AEAD, épinglage et révocation, sans nouvelle cryptographie. Les sorties passent par Tunnels::seal_outbound avant traduction OperatorDown ; le dispatcher rejette l’injection en clair vers un ID scellé. La fermeture détache les hold exec, annule le reste, et les ID isolés rejettent les sorties tardives. Aucun jeton LAN : grant et chaîne hors ligne sont joints à hello ; l’appareil utilise le même authorize qu’Exchange pour view/manage. Seules adresse privée et Origin sont vérifiées extérieurement. Le clair LAN est toujours refusé, quelle que soit l’ancienne valeur de politique. La console Web hébergée reste connectée à Exchange.
Les informations facultatives d’appareil info.seal_mode (allow / require), seal_key et la capacité sealed restent compatibles. Elles ne constituent ni une autorisation ni une clé publique d’appareil fiable ; l’appareil applique la politique en dernier ressort. CLI, Web Console et Android Console utilisent des sessions scellées natives pour toutes les commandes, terminaux, fichiers, ubus, capteurs, tâches, sondes réseau, opérations de bureau, mises à jour d’appareil, commandes prédéfinies et opérations du navigateur, sans chemin en clair. La valeur de politique allow refuse également les opérations en clair. Tout échec de négociation, d’épinglage d’appareil, d’autorisation ou de déchiffrement arrête l’opération, sans nouvelle tentative en clair. Après une modification de politique en ligne, les informations LAN sont actualisées immédiatement, celles du cloud lors de l’enregistrement après reconnexion.
Authentification racine de l’agent navigateur
L’extension annonce sealed, sa clé Ed25519 seal_key et seal_mode: require. Après appairage ou configuration, elle appelle POST /api/seal/device avec les identifiants de l’appareil et une preuve de possession signée. Exchange déduit l’espace de l’appareil et lie sa clé publique. Un client détenant la clé privée racine de confiance signe un grant view limité à cet appareil navigateur. Enregistrement et bearer permettent l’accès au relais et la récupération des autorisations signées ; les sessions exigent une authentification par certificats.
L’extension vérifie signature, chaîne racine, appareil, niveau, validité et révocations locales, puis signe SealAccept avec son propre SealHello signé dans peer. CLI finish_workspace_operator et finishBrowser vérifient la preuve, imposent peer.operator_key == accept.device_key et vérifient la signature de réponse. Une preuve absente ou invalide ferme la session. Les sessions valides démarrent automatiquement. Raw CDP exige aussi un consentement local par requête ou une autorisation sans surveillance limitée à la clé opérateur, au serveur et à l’appareil ; view ne permet pas CDP.
Les autorisations sont isolées par origine du serveur, espace et appareil. La graine d’identité locale est accessible uniquement aux contextes d’extension de confiance. La première racine est épinglée localement et tout changement est refusé. CLI et Console utilisent leur identité seal et chaîne racine habituelles, sans éditeur de pin propre au navigateur. Signature racine, droits du certificat et identifiants d’accès de l’appareil sont des contrôles distincts ; le TOFU des appareils natifs reste séparé.
Limite de sécurité : le premier TOFU ne détecte pas la fausse racine d’un relais malveillant ; vérification hors bande nécessaire. PoP prouve la possession, pas l’approbation. L’approbation automatique dépend de l’annuaire ; examiner les nouvelles clés en déploiement sensible. Protégez profil/graine et chaîne de publication Web ; endpoint compromis ou full CDP offert à un opérateur malveillant hors protection. Supprimer l’annuaire ne révoque pas les justificatifs signés en cache ; révocations et expiration locales restent importantes. Exchange voit les métadonnées et peut retenir les justificatifs ou refuser le service.
Identité indisponible : si l’identité de l’appareil ne peut être chargée ou écrite, les sessions scellées sont indisponibles et toutes les opérations en clair restent refusées. L’absence de fichier de politique, une politique endommagée ou une ancienne valeur n’ouvre aucun chemin en clair ; le stockage des clés doit être réparé localement sur l’appareil.
Les requêtes HTTP doivent atteindre l’instance Exchange qui a créé la connexion. Celle-ci expire après 90 secondes sans activité. Sérialisez les entrées et réessayez avec le même numéro et le même texte chiffré. Confirmez les événements traités avec after. Web Console, mobile et LAN conservent leur transport. Les commandes prédéfinies exigent toujours manage.
Vérification des régressions
Rust couvre négociation réelle des grants, résultats scellés exec/attach/tail/cat/sensors/net/update, refus du clair pour toutes les valeurs de politique, négociation/trames/politique LAN WebSocket, indication HTTP 409, fermeture browser require et invariants imposant le scellement côté clients et informations d’appareil. JavaScript couvre vecteurs Rust/JS bidirectionnels, enveloppes, sortie scellée des prédéfinis sans repli après refus, configuration/enregistrement/refus navigateur, changement de politique et erreurs d’épinglage. Les singletons seal des tests sont initialisés dans des sous-processus isolés pour éviter les interférences concurrentes.
CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 cargo test --workspace
cargo fmt --all --check
CARGO_PROFILE_DEV_DEBUG=0 CARGO_PROFILE_TEST_DEBUG=0 cargo clippy --workspace --all-targets -- -D warnings
npm test --prefix shared
npm test --prefix website/console
npm test --prefix browser-agent
node --test scripts/test-pages-worker.mjs
JavaScript nécessite Node 24+. Les régressions LAN utilisent des flux duplex mémoire pour réaliser un véritable HTTP Upgrade / WebSocket et vérifier la négociation, les trames et l’obligation de scellement pour toutes les valeurs de politique même sans sockets en sandbox. Les tests loopback WS/TLS/HTTP existants nécessitent la liaison de ports ; sinon Operation not permitted ne prouve pas la réussite de la suite. Les tests réseau/mise à jour vérifient les résultats scellés de succès/erreur, sans télécharger ni installer.
Risque Web Console : le code vient du site Pages ; un site/CDN compromis peut fournir un script lisant le clair ou signant avec les clés du navigateur (sans extraire la clé privée non exportable). La confidentialité vis-à-vis d’Exchange suppose une chaîne de publication statique fiable ; sinon utilisez CLI ou Android Console.
Identités et clés
| Clé | Type | Stockage | Visibilité publique |
|---|---|---|---|
| Appareil | Ed25519 | agent : agent.seal.key (0600). État en tmpfs (/var/run, /run, /tmp) : stockage /etc/remote-agent/, remplaçable par REMOTE_SEAL_DIR ; conffiles OpenWrt |
info.seal_key à l’enregistrement (affichage seulement, copie non fiable pour le CLI) |
| Opérateur | Ed25519 | CLI : seal-identity.pk8 (0600) près de config.toml ; Web : CryptoKeyPair WebCrypto Ed25519 non exportable dans IndexedDB remote-seal (sinon graine aléatoire IndexedDB si non pris en charge) ; Android : graine expo-secure-store (chiffrement Android Keystore) |
Négociation |
| Racine d’espace | Ed25519 | Clé opérateur du superadministrateur. Web exige une graine exportable (remplace la clé WebCrypto non exportable à la création de racine) ; export rsk1., sauvegarde Exchange automatique par Console avec une phrase vide |
rsr1. publié par Exchange, épinglé sur appareil |
| Session | X25519 éphémère → HKDF-SHA256 → une ChaCha20-Poly1305 par direction | Mémoire, détruite à la fin (confidentialité persistante) | Aucune |
Les clés privées opérateur restent locales. Console envoie par défaut une sauvegarde de la racine avec une phrase secrète vide ; Exchange ou les détenteurs de la base peuvent la déchiffrer. Une phrase peut être définie manuellement. Supprimer la sauvegarde désactive son enregistrement automatique sur ce client.
Autorisations (Exchange n’est pas l’autorité)
L’appareil fait confiance à une seule clé publique racine. Politique agent.seal.json :
{"mode":"require","root_key":"<base64 Ed25519>","revoked_keys":[]}
L’agent décide du niveau de chaque sealed_open :
- Aucune racine épinglée → refus (
no workspace root pinned on this device). - Clé opérateur dans
revoked_keys→ refus. - Clé opérateur égale à
root_key→owner(gestion et politique locale). grantjoint : signature valide,operator_keyidentique à la négociation,device_idlocal ou*, validité avec ±5 minutes d’horloge. Signataire racine épinglée, ou chaînersc1.jointe à hello (hello.chain, hors signature de négociation), du certificat racine au signataire du grant ; chaque certificat autorise la délivrance, sans dépassement de profondeur, niveau, portée ou expiration du précédent. Niveauviewoumanage, jamaisowner.- Sinon → refus.
Contraintes des chaînes de certificats
L’appareil reconnaît une chaîne rsc1. remontant à la racine épinglée, pas un intermédiaire isolé (hello.chain, certificat racine en premier). Aucune limite métier de profondeur (plus de 10 niveaux possibles), seulement une protection DoS généreuse : MAX_CERT_CHAIN = 64 certificats, somme des longueurs canoniques des jetons rsc1. MAX_CERT_CHAIN_BYTES ≤ 64 KiB (check_chain_limits / checkChainLimits dans crates/proto/src/seal.rs / shared/seal-cert.js, mêmes règles appareil/Exchange/CLI/Web/Android). Dépassement refusé.
Chaque certificat a max_path_len (0–255), contrainte facultative de profondeur descendante de l’émetteur. Sans --max-path, seal cert issue conserve toute la profondeur restante (255 pratiquement illimité). Les certificats suivants ne doivent pas dépasser max_path_len. L’intermédiaire administrateur racine a max_path_len de 0 : il signe des grants, pas d’autres certificats. can_issue faux interdit aussi toute poursuite.
Les enfants doivent rester dans les limites du parent (cert_within_parent / verify_chain) :
- Niveau au plus égal au parent. Racine
owner; attributionviewoumanageseulement, sans dépasser le parent. Aucunownerdans certificat/grant. - Portée incluse dans le parent. Tous les appareils seulement si parent
*; sinon chaque appareil figure dans la liste parent. Enfant*exige parent*. - Expiration au plus tard celle du parent ; racine sans borne. Le grant respecte aussi expiration et portée de son certificat signataire.
Révoquer un intermédiaire invalide tous ses descendants. L’appareil compare chaque subject_key et serial aux revoked_keys de la politique. Les entrées revoked_keys sont vérifiées sur toute la chaîne. Toute chaîne contenant l’intermédiaire révoqué échoue, donc certificats et grants descendants aussi. Supprimer sur Exchange arrête seulement la diffusion ; les copies restent valides jusqu’à expiration. Pour invalider immédiatement la sous-chaîne, ajouter clé du sujet (ou numéro de série) aux revoked_keys de l’appareil.
Niveaux/opérations (proto::seal::required_level) : view autorise capteurs, liste hold et actions Android en lecture seule (status/tree/screenshot/find_text) ; tout le reste (exec, PTY, fichiers, ubus, sonde réseau, automise à jour, saisie Android) exige manage.
Le grant est du JSON signé par la racine ; modifier un champ (view→manage, appareil, durée, bénéficiaire) invalide la signature ; celle du hello le lie à la session. Les rôles Exchange décident seulement du routage vers l’appareil (visibilité locataire), sans modifier les permissions scellées. Aucun propriétaire/copropriétaire ou revendication d’appareil : l’unique owner est la racine.
Épinglage de la racine :
- TOFU : Exchange envoie
seal_root {token, rotation?}(rsr1.autosigné) à l’appareil compatible connecté. L’appareil vérifie l’autosignature et, sans racine locale, l’épingle dansagent.seal.json. - Rotation : après épinglage, nouvelle racine acceptée seulement avec
rsx1.signé par la racine épinglée (old_rootancien,new_rootnouveau, même espace) ; sinon ignorée et journalisée. La racine peut aussi changer viaseal_policy_set {root_key}scellé (seule la racine actuelle estowner). - Local :
remote-agent seal root <key>écrit la racine ;remote-agent seal forget-rootl’efface, nouveau TOFU à la connexion suivante (redémarrage nécessaire).
Risque : Exchange compromis dès le premier TOFU peut fournir une fausse racine. Avant mise en ligne, remote-agent seal root <key> fixe la clé (empreinte via remote-cli seal root). Ensuite Exchange ne peut plus la remplacer.
Révocation : racine remote-cli seal policy <设备> --revoke <key>, ou local remote-agent seal revoke <key>. Racine non révocable. Grants expirants (90 jours par défaut). Supprimer un grant Exchange arrête sa diffusion seulement ; les copies importées nécessitent la politique appareil pour révocation avant expiration.
Exchange comme relais d’autorisations (non fiable)
Plus besoin de copier rsg1. ou clés publiques dans une discussion : Exchange fournit annuaire de clés opérateur et relais de grants par espace. Modèle inchangé : pas de clair, ni délivrance, extension ou falsification. Il stocke seulement :
- Clés Ed25519 dont l’opérateur prouve la possession (
POST /api/seal/keys). - Texte original des
rsg1.signés (POST /api/seal/grants). - Certificats
rsc1.signés (POST /api/seal/certs). Exchange vérifie la signature Ed25519 sans pouvoir falsifier.GET /api/seal/certs/chain?subject_key=récupère la filiation, racine en premier, sous les mêmes limites 64 / 64 KiB.
L’appareil n’autorise que par racine épinglée ou chaîne rsc1. y remontant. Exchange analyse pour indexer owner_key / operator_key / expiration, sans jamais assimiler téléversement et autorisation. Au pire il retient un jeton (no grant), sans transformer view en manage ni usurper la racine.
Tout membre peut publier un grant autosuffisant signé ; publier n’autorise rien, seuls les grants signés racine sont valides. CLI connecté : --publish par défaut ; Web/Android : même API pour « Délivrer et publier ».
La preuve de possession lie :
remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>
Signature Ed25519 par cette clé, issued_at_ms à ±10 minutes du serveur. CLI tente l’enregistrement à login et seal show ; consoles après connexion.
Sans grant local correspondant avant une opération, tentative seal sync ; après refus no grant, une synchronisation et un nouvel essai uniques.
Le client émetteur renouvelle automatiquement ses grants publiés proches d’expiration à sa connexion. Signature locale ; Exchange conserve le nouveau rsg1. et supprime l’ancien, sans modifier niveau ou bénéficiaire.
- Quand : CLI après connexion et au début de toute commande authentifiée (
seal renewreste manuel sans couche automatique). Web à la connexion/entrée d’espace, y compris session préexistante au chargement. Android après connexion et retour au premier plan. Rien sans connexion ou sans grants publiés par cette clé. - Règles :
GET /api/seal/grants?owner_key=<本机公钥>. Expiration sous 30 jours : mêmesdevice_id,operator_key,level, nouvelle durée 90 jours ;DELETEancien après publication réussie. SiGET /api/seal/keysréussit, ignorer lesoperator_keydisparus ; sinon aucun filtrage d’annuaire. - Limitation : Web/Android: une tentative par 12 heures et clé. CLI :
seal-renew-state.jsondu répertoire de configuration ; WeblocalStorage, Android SecureStore. Échec sans effet sur l’opération : une ligne stderr CLI,console.warnconsole ; succès CLI brièvement signalé sur stderr. Web racine délivre d’abord à l’entrée d’espace, Android à l’ouverture « Chiffrement de bout en bout », sans limitation ; seuls les rafraîchissements suivants de la visite sont limités. - Renouvellement CLI : les succès sont enregistrés par serveur et clé émettrice. Après un succès, les requêtes de renouvellement sont évitées pendant 12 heures ; un échec ou une interruption impose seulement 5 minutes avant une nouvelle tentative. Le processus complet dispose de 30 secondes. Les erreurs indiquent l’étape et la durée ; un échec de la commande principale annule immédiatement le renouvellement. Les révocations en attente contournent cette limitation et conservent un budget distinct de 4 secondes. Renouvellement explicite :
remote-cli seal renew. Une connexion réussie force une actualisation. - Racine : le superadministrateur utilise la délivrance par rôle ci-dessous au lieu du renouvellement générique pour ne pas recréer un grant supprimé.
- Manuel :
remote-cli seal renew, défaut--before 30 --days 90, mêmes règles.
# 按工作区用户名授权对方每一把已登记的钥匙,并发布到 Exchange(默认 90 天)
remote-cli seal grant --device lab-router --to alice --level view
# 只打印 token,不上传
remote-cli seal grant --device lab-router --operator-key <KEY> --no-publish
remote-cli seal keys # 工作区公钥目录
remote-cli seal sync # 拉取签给自己钥匙的 grant 并导入本地
remote-cli seal renew # 手动续签,默认 --before 30 --days 90
Le superadministrateur est la racine de confiance
La racine est la clé Ed25519 du client superadministrateur. Seules cette clé et les autorisations rsg1. remontant par rsc1. sont reconnues ; le client délivre/renouvelle selon les rôles. Exchange conserve rsr1. autosignés, rsx1. et invitations rsi1., vérifie format/signature mais ne décide pas des approbations. Il peut retenir ou ajouter de faux membres, pas falsifier la racine. CLI, Web et Android approuvent automatiquement par défaut : ces racines signent directement les nouvelles clés de l’annuaire. Seule une désactivation explicite laisse en attente les clés jamais approuvées/publiées par cette racine.
Modèle de confiance et compromis
- Épinglage TOFU de racine. Premier enregistrement vérifié épinglé localement. CLI :
seal-root.json(0600) près deconfig.toml, index服务器|工作区 id. WeblocalStorage(remote.seal.root.<工作区 id>). Android SecureStore (remote.seal.rootstate, JSON par serveur/espace). Autosignature/empreinte ensuite contrôlées. Changement : alerte forte, aucune confiance automatique côté client (l’appareil suitrsx1., voir plus haut). Après contrôle,remote-cli seal root --trust <key>ou « Faire confiance à la nouvelle racine ». - Invitation = enregistrement seulement. Racine signe
rsi1.(ID espace, clé racine, nonce 16 octets, expiration). Clients administrateur/opérateur/lecture seule récupèrent aux connexions, commandes, chargements ou retours au premier plan, vérifient par racine épinglée, puisPOST /api/seal/keys. Invitation non autorisante. - Délivrance par rôle et approbation locale. Client racine lit
GET /api/seal/roster(superadministrateur seulement, membres désactivés, rôles,operator_device_grants, clés) et ses grants publiés. Chaque clé enregistrée suit :- Superadministrateur / administrateur non restreint :
manage*. - Administrateur restreint :
managesican_manage, sinonview, selon délégations. - Opérateur non restreint :
view*, plusmanagepour chaque appareilcan_manage. - Opérateur restreint :
manageouviewpar appareil délégué. - Lecture seule non restreinte :
view*. - Lecture seule restreinte :
viewpar appareil ; jamaismanage, mêmecan_manage, car ce compte ne gère pas l’appareil. - Désactivé : aucun grant.
- 90 jours par défaut, renouvellement sous 30 jours restants.
- Superadministrateur / administrateur non restreint :
- Intermédiaire administrateur. Racine délivre aux administrateurs actifs
rsc1.(manage,can_issue,max_path_len0, 90 jours). Portée*sans restriction, sinon appareils déléguéscan_manage. Renouvelé sous 30 jours, jamais pour clé non approuvée. L’administrateur avec certificat valide signe pour ses membres avec sa clé et chaîne, intersection certificat/délégations. Pas de nouvelle émission si grant racine frais couvre déjà opérateur/appareil/niveau (*couvre appareil,managecouvreview). Supprimer Exchange arrête seulement la diffusion ; copies valides jusqu’à expiration, révocation ferme viarevoked_keysappareil. - Approbation automatique activée par défaut. Web
localStorageet Android SecureStore interprètent absent comme activé (absent = on), seulfalseexplicite garde la désactivation, jamais annulée par défaut. Signature directe de l’annuaire moins sûre : Exchange compromis peut obtenir immédiatement la signature de faux membres. Libellé « Approuver automatiquement les nouvelles clés publiques ». CLI est activé par défaut, comme Web/Android Console : si le fichierseal-approved.jsonou le champauto_approveest absent, l’approbation est activée ;"auto_approve": falseexplicite reste désactivé. Utilisezremote-cli seal approve --no-auto-approvepour désactiver ouremote-cli seal approve --auto-approvepour réactiver ; le choix est enregistré. Désactivé : clés jamais approuvées/publiées par cette racine en file locale (CLIseal-approved.json, WeblocalStorage, Android SecureStore).remote-cli seal pendingaffiche nom/empreinte ;seal approve --allouseal approve <key>…délivre immédiatement. Clés déjà approuvées/publiées suivent renouvellements/rôles automatiquement ; clé du client émetteur sans nouvelle approbation. - Émission à l’ouverture. Web racine à l’entrée d’espace, Android à l’ouverture « Chiffrement de bout en bout », sans limite 12 heures ; rafraîchissements suivants limités. Modifier le commutateur ou « Approuver » force un passage.
- Dialogue Compte, badge, échecs. Web : commutateur et attente dans « Compte » de la page appareil (barre « Compte »). Badge « N clés publiques en attente » ouvre le dialogue. Erreur « Échec de délivrance par la racine : … » dans la barre, sans jetons ni longues données de clés. Android : mêmes éléments sur « Chiffrement de bout en bout ».
- Rétrograder ne révoque pas les anciens grants appareil. Membre retiré/désactivé ou clé disparue : supprimer grants Exchange, arrêter renouvellement, tenter
revoked_keyssur appareils connectés compatibles. Rétrogradation : nouveau grant seulement, sans révoquer la clé. Anciens grants importés valides jusqu’à expiration. Racine jamais révoquée. Révocations au-delà du budget 4 secondes conservées localement pour la commande suivante, sans limite 12 heures. Une clé redevenue souhaitée n’est rétablie que sur les appareils où la révocation avait réussi. - Création automatique. À l’inscription superadministrateur (
register/premier compte) ou toute connexion ultérieure, sans racine Exchange, son client CLI/Web/Android publie sa clé locale et une invitation de 90 jours. Invitation absente ou expirant sous 30 jours rafraîchie selon la limitation.remote-cli seal root --publishreste manuel.
Configuration initiale
- Inscription/connexion du superadministrateur sur un client : racine créée/publiée automatiquement (stderr CLI, notification console).
- Conservez la racine (une ou les deux options) :
- Sans téléversement :
remote-cli seal root --export root.rsk(0600 ;-vers stdout), ou « Exporter la clé privée racine ». - Configuration manuelle du cloud :
remote-cli seal backupou « Sauvegarde cloud racine ». Phrase vide permise, mais vide/faible elle permet à Exchange de déchiffrer la racine et contrôler tous les appareils.
- Sans téléversement :
- Appareil compatible connecté : épinglage automatique TOFU ; plus sûr avant mise en ligne :
remote-agent seal root <根公钥>. - Nouveau membre enregistre sa clé à la connexion. CLI/Web/Android approuvent automatiquement par défaut ; si cette option est désactivée
remote-cli seal pending, puisremote-cli seal approve --allou « Approuver » pour délivrer.
remote-cli seal root # 显示已固定的根公钥和指纹
remote-cli seal root --export root.rsk # 导出根私钥(rsk1. 文本)
remote-cli seal root --import root.rsk # 在另一台机器装上根私钥(或 seal restore)
remote-cli seal root --rotate # 生成新根,旧根签 rsx1. 轮换,自动按新根重签成员授权
remote-cli seal invite # 单独刷新邀请(默认 90 天,--days N)
remote-cli seal pending
remote-cli seal approve --all # 或 seal approve <key>…;--no-auto-approve 关闭自动批准(保持开启较弱)
Autre machine / changement de racine
- Racine ailleurs : après connexion du superadministrateur, sans racine publiée locale et avec sauvegarde cloud
rsb1.Exchange, CLI/Web/Android tentent automatiquement la restauration (voir ci-dessous). Sinon import manuel proposé :seal root --import <文件|->, collerrsk1.puis « Importer la clé privée racine », ouseal restore/ « Restaurer depuis Exchange ». Importer la racine actuelle installe localement sans modifier les enregistrements. - Rotation :
seal root --rotate(« Générer et effectuer la rotation »), ou importrsk1.différent de l’actuel. Avec racine actuelle locale, signerrsx1.par l’ancienne, publier avec nouvellersr1.viaPUT /api/seal/root. Exchange pousse aux appareils connectés, qui vérifient avant adoption ; hors ligne à la reconnexion. Membres approuvés conservés localement et tous grants resignés 90 jours. Ancienne clé :seal-identity.<毫秒>.previous.pk8. Réexporter/sauvegarder ensuite. - Ancienne racine perdue : impossible de signer la rotation. Nouvelle racine publiée mais refusée par les appareils épinglés ; exécuter
remote-agent seal root <新根公钥>sur chacun et redémarrer. - Transfert : voir « Transfert du superadministrateur = transfert de la racine ». Le rôle seul ne suffit pas, l’appareil reconnaît la clé racine.
Restauration automatique à la connexion
Après connexion du superadministrateur, avant création d’identité locale, CLI/Web/Android suivent la même décision pure (Rust decide_auto_restore, JS decideAutoRestore) pour restaurer la racine publiée depuis rsb1. Exchange :
- Ignorer : non-superadministrateur, clé déjà identique, cloud désactivé (CLI
REMOTE_SEAL_CLOUD_BACKUP=0, WeblocalStorage.seal.cloudBackup=off, Android SecureStoreremote.seal.cloudBackup=off), ou racine non publiée. - Récupération unique :
GET /api/seal/backup, limitée par heure. Absence signalée comme racine ailleurs, sans bloquer. - Essai silencieux vide : déchiffrer avec chaîne vide ; si clé publique identique à la racine et aucune autre clé locale différente, installer/épingler.
- Sinon phrase : CLI essaie
REMOTE_SEAL_BACKUP_PASSPHRASE, puis saisie masquée TTY ; Web/Android avis non bloquant invitant à saisir sur page seal et « Restaurer depuis Exchange ». Aucun dialogue bloquant, connexion normale. - Sécurité : jamais écraser silencieusement une autre clé (
seal restore --forceou confirmation console) ; clé publique obligatoirement identique à la racine ; échec averti sans bloquer ; jamais afficher phrase, graine ou ciphertextrsb1.. - Avis non bloquant : plusieurs copies de racine augmentent le risque ; si phrase vide réussie, Exchange peut aussi la déchiffrer.
Transfert du superadministrateur = transfert de la racine
Rôle de compte et racine de confiance sont distincts. Transférer le rôle sans clé ne change pas la confiance appareil. Remettre la clé privée racine, puis tourner immédiatement pour invalider la copie remise.
- Titulaire détenant la racine publiée :
POST /api/superadmin/transfer, corps{operator_id, root_handover?}.root_handover:rsb1.chiffré, même sauvegarde (Argon2id 64 MiB, t=3, p=1, ChaCha20-Poly1305), phrase vide permise. CLIremote-cli seal handover <账号>. Web/Android demandent une phrase facultative et joignent le paquet si la clé est locale. - Exchange stocke sans déchiffrer ; vérifie préfixe
rsb1.oursk1., maximum 16384, ASCII URL sûr seulement. Sansroot_handover, supprime le paquet résiduel de l’espace. - Successeur :
GET /api/superadmin/handover→{blob, from_operator, created_at}(404 si absent), déchiffre/installe, puis rotation normale : ancienne racine signersx1., nouvelle publiée ; enfinDELETE /api/superadmin/handover. CLIremote-cli seal accept-handover. Console : « Accepter le transfert de clé privée racine » si paquet présent. - Rotation invalide les copies. Appareils changent seulement sur signature de l’ancienne racine fiable. L’ancien titulaire ne peut autoriser au nom de la nouvelle ni changer la racine adoptée. Avant rotation,
rsb1.à phrase faible/vide déchiffrable ; transmettre la phrase séparément.
Hors bande : remote-cli seal handover <账号> --out-of-band évite Exchange ; stdout contient une ligne rsb1., requête sans root_handover. Transmettre ligne/phrase autrement, destinataire remote-cli seal accept-handover <那一行>. Annuler la phrase console (« Transférer seulement le compte » Android) transfère le rôle sans clé téléversée ; remettre la clé séparément (rsk1. exporté ou rsb1. hors bande).
Négociation et format réseau
CLI Exchange agent
│ open_sealed{session_id, │ │
│ device_id, hello} │──sealed_open{session_id,hello}▶│ 验证 hello 签名、授权 → 级别
│ │◀─sealed_accept{session_id, │
│◀─sealed_accept (原样转发) ──│ accept} │
│ 验证设备签名 + pin │ │
│ sealed_input{packet} ─────▶│──sealed_input────────────────▶│ 解密、校验级别、执行
│ 二进制帧 kind=4 (PTY 键入)─▶│── 只改写帧头编号 ─────────────▶│
│◀── sealed_event{packet,last}│◀── sealed_event ──────────────│ 所有输出在出站队列加密
│◀── 二进制帧 kind=4 ─────────│◀── 二进制帧 kind=4 ───────────│
hello={v, operator_key, operator_ephemeral, nonce, grant?, sig}, signe["remote-seal-v1/hello", v, device_id, operator_key, operator_ephemeral, nonce, grant.sig|""](tableau JSON, octets canoniques).- Racine
rsr1.signe["remote-seal-v1/workspace-root", v, workspace_id, root_key, issued_at_ms]; rotationrsx1.={v, workspace_id, old_root, new_root, issued_at_ms, sig}, signée parold_rootsur["remote-seal-v1/root-rotation", v, workspace_id, old_root, new_root, issued_at_ms]. Exchange → agent :{"type":"seal_root","token":"rsr1.…","rotation":"rsx1.…"?}(contrôle en clair uniquement depuis Exchange, invalide en tunnel). accept={v, device_key, device_ephemeral, nonce, level, sig}, signe["remote-seal-v1/accept", v, device_id, operator_key, operator_ephemeral, hello.nonce, hello.sig, device_key, device_ephemeral, nonce, level]. Modifierlevelinvalide la signature.- Clés :
salt = SHA256(accept 签名字节),PRK = HKDF-Extract(salt, X25519(o, b)),K_od = Expand("remote-seal-v1/operator_to_device"),K_do = Expand("remote-seal-v1/device_to_operator"). - AEAD ChaCha20-Poly1305, AAD
remote-seal-v1, nonce = octet canal (0 texte / 1 binaire) ‖ 3 zéros ‖ compteuru64big-endian. Compteurs par direction/canal, de 0 par incréments stricts de 1 ; divergence ferme immédiatement. - Texte
{"counter":n,"ciphertext":"<base64>"}. Clair JSON : opérateur → appareilSealedRequest(DeviceDownexistant ou administrationseal_policy_get/seal_policy_set {mode?, root_key?, revoke?, unrevoke?}) ; appareil → opérateurSealedResponse(DeviceUpouseal_policy).session_idinterne réécrit par l’agent avec l’ID tunnel. - Binaire
StreamKind::Sealed = 4, chargecounter(u64 BE) ‖ AEAD(inner_kind ‖ bytes),inner_kind1/2/3 (stdout/stderr/PTY). - Une opération par session (exec, PTY, transfert…), modèle 1:1 conservé. Fin chiffrée par agent, enveloppe
last:true; Exchange libère route/complète audit sans lire le contenu. close_session→sealed_close{detach:false}(terminer) ; déconnexion opérateur →sealed_close{detach:true}(agent : hold exec détaché, reste terminé).- Limite : base64 chiffré environ 2,8 MiB (capture Android 2 MiB), enveloppe
MAX_SEALED_WIRE.
Défense agent : clair sortant d’un ID scellé seulement chiffré ou rejeté ; ID définitivement isolé après fermeture (sorties hold tardives rejetées, jamais en clair). Hold démarré/joint scellé absent des anciennes listes, inaccessible à attach/tail/cat/kill anciens.
Export et sauvegarde de la clé privée racine
La racine contrôle tous les appareils. Conservation au choix du superadministrateur ; Console sauvegarde par défaut avec une phrase vide ; supprimer la copie désactive cet enregistrement :
- Export sans téléversement :
rsk1.+ graine 32 octets base64url sans remplissage. CLIseal root --export FILE|-(0600), console « Exporter la clé privée racine » (champ Web, presse-papiers Android). Son détenteur contrôle tout. - Sauvegarde chiffrée (automatique dans Console, manuelle dans CLI) : superadministrateur avec clé locale égale à la racine publiée uniquement.
rsb1.versionné (JSON base64url, maximum 4096 caractères). Argon2id : production 64 MiB, 3 itérations, parallélisme 1, paramètres inclus. Sel 16 octets et nonce ChaCha20-Poly1305 12 octets aléatoires par sauvegarde. AADremote-seal-v1/key-backup:{version}:{public_key}; changer version/clé/ciphertext invalide. Vérifier aussi la clé publique dérivée de la graine. Avant Argon2, refus des paramètres faibles/excessifs (mémoire 8 KiB–256 MiB, production minimum 64 MiB ; itérations 1–8, production minimum 3 ; parallélisme 1 seulement). - Phrase facultative, force non imposée. Vide utilise aussi Argon2id avec chaîne vide et même format. Avertissement seulement si vide/faible (moins de 12 caractères, catégories insuffisantes, répétitions, phrase courante) : Exchange ou un lecteur de base peut déchiffrer la racine et contrôler tous les appareils. Rust/JS partagent règles et vecteurs, vide compris.
PUT / GET / DELETE /api/seal/backup pour compte connecté seulement ; PUT/GET superadministrateur uniquement. Corps {blob}. Exchange vérifie préfixe rsb1./longueur, jamais de déchiffrement. GET maximum 5 par compte/processus/heure, puis 429 ; ralentit seulement essais en ligne, pas l’attaque hors ligne d’une base copiée. Lecture/écriture/suppression auditées avec nombre d’octets seulement.
- CLI
remote-cli seal backup(phrase masquée deux fois, Entrée vide, ou--passphrase-stdin) ;remote-cli seal restore(--forcesi autre clé, ancienneseal-identity.<毫秒>.previous.pk8), puis épinglage comme racine locale. Essai automatique aussi à la connexion. Aucun affichage de clé, phrase ou ciphertext. - Web : racine exportable obligatoire ; création remplace WebCrypto non exportable par graine IndexedDB. Bootstrap restaure avant création d’identité ;
localStorage.seal.cloudBackup=offdésactive. - Android : graine expo-secure-store ;
bootstrapSealrestaure aussi d’abord ;remote.seal.cloudBackup=offdésactive.
Utilisation
remote-cli seal show # 生成/显示本机操作员公钥与指纹
remote-cli exec lab-router -- uname -a
remote-cli shell lab-router
remote-cli seal policy lab-router # 根:查看设备授权与吊销策略
# 根给同事手动授权(通常不需要:根客户端按角色自动签发)(默认 90 天;对方已登录的客户端会自动登记公钥)
remote-cli seal grant --device lab-router --to alice --level view
# 或仍用公钥;--no-publish 只打印 token
remote-cli seal grant --device lab-router --operator-key <KEY> --level view --no-publish
# 手动续签即将到期的已发布授权(默认 --before 30 --days 90;上线时也会自动续签)
remote-cli seal renew
# 同事:拉取 Exchange 上签给自己的授权(登录后也会自动 sync)
remote-cli seal sync
# 仍可手动导入
remote-cli seal import-grant rsg1.xxxxx
# 根私钥:导出(不上传)或可选的加密云端备份
remote-cli seal root --export root.rsk
remote-cli seal backup
remote-cli seal restore # 已有不同钥匙时加 --force
# 带外固定设备指纹(在设备上 `remote-agent seal show`)
remote-cli seal trust-device lab-router <DEVICE_KEY>
Web Console / Android Console: toutes les opérations sur les appareils utilisent exclusivement des sessions scellées natives. Commandes, hold list/kill/tail/cat, terminal, fichiers, captures et saisie Android/bureau, arbre d’interface, capteurs, ubus, journaux système et mises à jour automatiques passent dans le tunnel scellé ; la page affiche « 🔒 Chiffré de bout en bout ». Dans les détails d’appareil Web ou les paramètres Android, le panneau « Chiffrement de bout en bout » équivaut à la CLI : clé et empreinte locales, racine d’espace de travail (superadministrateur : exportation, importation, rotation et transfert de clé privée racine, approbation des nouvelles clés publiques), consultation et modification des clés d’appareil et de la politique de révocation (racine requise), délivrance d’autorisations (jeton rsg1., importable dans la CLI avec seal import-grant), importation d’autorisation, approbation de la clé publique d’appareil et sauvegarde cloud automatique de la clé privée racine (phrase vide permise, avertissement explicite qu’Exchange peut déchiffrer une sauvegarde à phrase faible). La première connexion épingle l’empreinte de l’appareil (TOFU) ; tout changement ultérieur est refusé avec demande de vérification. Un membre ordinaire utilise des clés distinctes sur CLI, navigateur et téléphone, chacune enregistrée puis autorisée par la racine selon le rôle. Pour utiliser la racine sur plusieurs appareils, le superadministrateur peut téléverser une sauvegarde à phrase secrète, restaurée automatiquement à la connexion sur le nouvel appareil, ou importer la clé privée sur chaque appareil.
Localement sur l’appareil : remote-agent seal show | root <key> | forget-root | revoke <key> (redémarrage de l’agent nécessaire ; les modifications via seal policy dans une session scellée sont immédiates).
Comportement par défaut
| Portée | Comportement actuel |
|---|---|
| CLI, Web Console, Android Console | Toutes les opérations sur les appareils utilisent des sessions scellées natives, sans interrupteur de désactivation ni repli en clair |
| Politique locale de l’appareil | Toutes les valeurs, y compris allow, refusent les opérations en clair |
- Les nouveaux appareils épinglent la racine d’espace de travail à leur connexion à Exchange (TOFU) ; la racine et ses autorisations signées permettent les opérations. En cas de doute sur Exchange à la première connexion, exécutez d’abord localement
remote-agent seal root <key>. - Une clé d’identité indisponible ou une politique endommagée entraîne un refus. Réparez les clés et la politique localement sur l’appareil avant de le reconnecter.