Sesiones selladas de extremo a extremo (sealed sessions)
Android Console abre el cifrado de extremo a extremo desde Ajustes y permite elegir el dispositivo en la página. Los ajustes de cuenta de Android y Web permiten configurar o cambiar la contraseña y vincular o sustituir el correo; el anterior sigue activo hasta verificar el nuevo. Console guarda por defecto la clave raíz en Exchange con frase vacía y conserva las copias existentes protegidas con contraseña. Eliminar la copia desactiva el guardado automático en ese cliente. Una frase vacía permite a Exchange o al poseedor de la base descifrar la clave. La restauración manual verifica la raíz del espacio, la guarda de forma persistente y sincroniza permisos.
Objetivo: aunque el host de Exchange esté comprometido o su operador tenga curiosidad, no puede leer comandos, salida, bytes PTY, archivos, capturas ni datos ubus, ni decidir o falsificar quién puede ver o administrar dispositivos. Exchange pasa a ser un relé de texto cifrado y metadatos de enrutamiento.
Definiciones: crates/proto/src/seal.rs (tipos de protocolo y autorización local), crates/proto/src/seal/crypto.rs (criptografía de sesión, solo ring), crates/proto/src/seal/backup.rs (copias con contraseña: Argon2id + ChaCha20-Poly1305, no enlazado por agent), crates/agent/src/seal.rs (dispositivo), crates/cli/src/seal.rs (CLI), shared/seal-session.js (sesiones compartidas de Web Console y Android Console, @noble/curves · @noble/hashes · @noble/ciphers), shared/seal-backup.js (copias Argon2id sobre las mismas bibliotecas). Vectores entre lenguajes: shared/seal-vectors.json (generados por JS; Rust javascript_vectors_interoperate comprueba firmas/HKDF/cifrado byte a byte), shared/seal-rust-vectors.json (respuesta Rust; JS shared/seal-session.test.js negocia y descifra).
seal approve sigue publicando permisos automáticamente. Un seal grant --publish posterior (o la publicación predeterminada al iniciar sesión) omite un permiso publicado vigente con las mismas claves de emisor y destinatario, dispositivo y level, informa que ya está publicado y no genera otro token. Use --renew-before DAYS para renovar permisos próximos a caducar; --no-publish sigue firmando tokens sin conexión. Esta comprobación evita duplicados por reintentos secuenciales, pero no ofrece transacciones entre clientes concurrentes ni elimina duplicados históricos. seal show crea una identidad local y registra su clave pública si hay sesión iniciada; use un profile separado para pruebas.
Modelo de amenazas
| Ruta anterior (en claro) | Sesiones selladas | |
|---|---|---|
| Comandos, stdout/stderr, código de salida | Exchange los ve; auditoría completa cifrada con una clave de Exchange | Invisibles; auditoría solo de sealed:true, clave pública, nivel, caducidad y resultado final |
| Entrada PTY y salida de pantalla | Visibles, auditadas trama a trama | Invisibles, tramas cifradas excluidas de auditoría |
| Archivos, capturas, árbol de interfaz, parámetros/resultados ubus | Visibles | Invisibles |
| Quién puede hacer qué (superadministrador/administrador/operador/lectura, ver/administrar) | Decide Exchange | Decide el dispositivo: solo acepta autorizaciones firmadas por la raíz fijada; Exchange no puede emitirlas, ampliarlas ni falsificarlas |
| Suplantar dispositivo | Posible (Exchange puede responder) | Imposible: identidad nativa firmada y clave fijada en CLI; extensión verifica cadena raíz y firma de respuesta |
| Inyectar pulsaciones/comandos | Posible | Imposible: AEAD y contadores estrictamente crecientes; agent descarta texto en claro dirigido a IDs sellados |
| Repetición, reordenación, pérdida, truncamiento | — | Detectados; cierran sesión (fail closed) |
| Metadatos visibles | — | ID dispositivo, cuenta, ID sesión, tiempos, tamaños y cantidades; stdout/stderr indistinguibles porque el tipo va cifrado |
| Acciones posibles | — | Denegación de servicio: retener, desconectar, cerrar (sealed_close); afecta disponibilidad, no permisos |
Fuera de alcance: dispositivo o equipo del operador comprometido; suplantación de Exchange en la primera conexión TOFU, evitable comparando huellas por otro canal. Todas las operaciones de dispositivo solo aceptan sesiones selladas; los límites del catálogo de comandos y de la autorización raíz de navegador figuran debajo.
Cobertura de rutas y diseño
| Ruta | Comportamiento de sesiones selladas | Autorización y límites del dispositivo |
|---|---|---|
HTTP POST /api/devices/{id}/exec |
Rechazo explícito, HTTP 409, indica utilizar una sesión WebSocket open_sealed |
Aunque Exchange ignore la política, el dispositivo rechaza exec_req en claro |
HTTP comandos predefinidos /commands/{cmd}/run |
Rechazo explícito, con la misma indicación de WS sellado | El catálogo de servidor no autoriza al dispositivo |
| Comandos predefinidos Web Console / CLI | Obtiene plantilla; el operador envía exec_req y recibe salida en WS sellado |
Verifica hello, grant raíz, revocación, vigencia y manage; view no ejecuta plantillas |
LAN /ws/operator |
Solo sesiones selladas; hello incluye grant y cadena; sin token Bearer | Misma cadena raíz y view/manage; exterior solo dirección privada/Origin. /api/devices devuelve ID y clave/modo seal |
| Agent navegador / rutas nativas selladas | open_sealed / sealed_input / sealed_event; rechaza canal browser antiguo |
Extensión verifica grant/cadena raíz; CLI/Console verifican cadena de SealAccept.peer y coincidencia con clave firmante |
CLI exec-attach / exec-hold-tail / exec-hold-cat |
Peticiones, salida y final sellados nativos | Requiere manage; los trabajos sellados no pueden leerse ni adjuntarse en claro |
CLI sensors --live |
Una sesión sellada por muestra (una operación/sesión); Ctrl-C termina la actualización | Basta view; no agrupa muestras en un túnel de una sola operación |
CLI net |
Sella net_req / net_resp, URL, cabeceras, cuerpo y resultados |
Requiere manage |
CLI device update |
Sella update_req, todo el progreso y el estado final |
Requiere manage; update superior actualiza el cliente local, no una sesión remota |
| exec / PTY / file / ubus / Android / sensors individual / hold list, kill | Implementación sellada nativa existente, sin cambios | Mismo required_level y dispatcher del dispositivo |
| Dispositivos, métricas, conexión, sessions Exchange | Metadatos de rutas/monitorización, no operaciones selladas de dispositivo | Visibles a Exchange; no autorizan operaciones |
HTTP y comandos predefinidos
Las operaciones selladas nativas ya admiten clientes que usan únicamente HTTP. Use remote-cli --transport http o REMOTE_TRANSPORT=http. POST /api/sealed/connections crea una conexión; POST /input envía la negociación firmada o el texto cifrado, GET /events obtiene resultados y DELETE cierra la conexión. El cliente mantiene la criptografía y la verificación de autorizaciones. Los antiguos endpoints de acciones siguen devolviendo 409; la conexión del dispositivo no cambia.
https://remote.ivjn.us/sealed-http.md
Crear, editar y listar predefinidos sigue siendo administración de Exchange. Plantillas, nombres y descripciones no son datos sellados ni una lista permitida firmada por raíz. Console/CLI con claves ejecutan plantillas como exec sellado normal sin hold. Dispositivo solo confía en política local y grant, no en catálogo, rol o etiqueta «predefinido». view no ejecuta: necesita manage de raíz. Alterar catálogo no amplía grant, pero manage permite exec arbitrario: revise plantillas y no almacene secretos allí. Salida se descifra localmente; auditoría solo contiene metadatos sellados.
LAN y selección del cliente
Las sesiones de dispositivo de la CLI utilizan exclusivamente sellado de extremo a extremo, sin depender de que Exchange anuncie seal_key, capacidades o indicaciones de política. La información de capacidades ausente o eliminada no degrada a texto en claro. Si falla la lectura de la base de datos local de claves fijadas, se devuelve un error sin ignorar registros de confianza dañados.
Exec sellado con/sin hold y tareas de archivos se marcan antes de publicar globalmente: cerrar antes del primer evento saliente no expone vistas previas mediante carreras. Registros omiten destino/método HTTP y objeto/método ubus.
LAN reutiliza envelope, transcript, contadores, AEAD, fijación y revocación. Salida pasa por Tunnels::seal_outbound antes de OperatorDown; dispatcher descarta inyección en claro a IDs sellados. Cerrar separa held exec, cancela otros; IDs aislados descartan salida tardía. Sin token LAN: hello lleva grant/cadena en caché y el mismo authorize decide view/manage. Exterior comprueba dirección privada y Origin. Siempre rechaza texto en claro, independientemente del valor histórico de la política. Web alojada sigue usando Exchange.
La información opcional del dispositivo info.seal_mode (allow / require), seal_key y la capacidad sealed conservan la compatibilidad. No constituyen autorización ni una clave pública de dispositivo de confianza; la política la aplica finalmente el dispositivo. Los comandos, terminales, archivos, ubus, sensores, trabajos, sondeos de red, escritorio, actualizaciones de dispositivos, comandos predefinidos y operaciones de navegador de CLI, Web Console y Android Console utilizan exclusivamente sesiones selladas nativas, sin ruta en claro. El valor allow también rechaza operaciones sin cifrar. Los fallos de negociación, fijación del dispositivo, grant o descifrado detienen la operación sin reintentar en claro. Tras cambiar la política en ejecución, la información LAN se actualiza inmediatamente y la información en la nube al reconectar y registrar.
Autenticación del Agent de navegador con cadena raíz del espacio de trabajo
La extensión anuncia sealed, su seal_key Ed25519 y seal_mode: require. Tras emparejar o configurar el dispositivo, llama a POST /api/seal/device con credenciales de dispositivo y una prueba de posesión firmada. Exchange determina el espacio desde el dispositivo y vincula su clave pública. Un cliente con la clave privada raíz de confianza firma un grant view para ese dispositivo exacto. El registro y el bearer de dispositivo permiten acceder al relay y obtener credenciales firmadas; las sesiones requieren autenticación por certificados.
La extensión verifica firma, cadena raíz, dispositivo, nivel, vigencia y revocaciones locales; después firma SealAccept con su propio SealHello firmado en peer. CLI finish_workspace_operator y finishBrowser verifican la prueba, exigen peer.operator_key == accept.device_key y comprueban la firma de respuesta. Una prueba ausente o inválida cierra la sesión. Las sesiones válidas comienzan automáticamente. Raw CDP también exige consentimiento local por solicitud o permiso desatendido limitado a clave del operador, servidor y dispositivo; view no permite CDP.
Los permisos se separan por origen del servidor, espacio y dispositivo. La semilla de identidad local solo es accesible desde contextos de extensión de confianza. La primera raíz se fija localmente y sus cambios se rechazan. CLI y Console usan su identidad seal y cadena raíz normales, sin editor de pin exclusivo del navegador. Firma raíz, permisos de certificado y credenciales de acceso del dispositivo son comprobaciones distintas; el TOFU de dispositivos nativos sigue separado.
Límite de seguridad: primer TOFU no detecta raíz falsa de relé malicioso: compare por otro canal. PoP prueba posesión, no aprobación. Aprobación automática depende del listado: instalaciones sensibles deben revisar claves. Proteja profile/semilla y publicación de Console; endpoints comprometidos o full CDP para operadores maliciosos quedan fuera. Borrar catálogo no revoca credenciales cacheadas; importan revocación local/caducidad. Exchange ve metadatos, puede retener credenciales y denegar servicio.
Identidad no disponible: si la identidad del dispositivo no puede cargarse o escribirse, las sesiones selladas no están disponibles y se siguen rechazando todas las operaciones en claro (fallo cerrado). La ausencia de archivo de política, una política dañada o sus valores antiguos no habilitan una ruta sin cifrar; debe repararse el almacenamiento de claves en el propio dispositivo.
Las peticiones HTTP deben llegar a la instancia Exchange que creó la conexión. Esta caduca tras 90 segundos sin actividad. Envíe las entradas en serie y reintente con el mismo número y texto cifrado. Confirme los eventos procesados con after. Web Console, móvil y LAN conservan su transporte. Las órdenes predefinidas siguen requiriendo manage.
Verificación de regresiones
Rust cubre negociación real de grants, resultados sellados exec/attach/tail/cat/sensors/net/update, rechazo del texto en claro con cualquier valor de política, negociación/tramas/política LAN WS, HTTP 409, cierre browser require y flags/información compatibles. JavaScript cubre vectores Rust/JS, envelopes, salida sellada predefinida y ausencia de degradación, configuración/registro/rechazo/política navegador y errores de fijación. Singleton seal se inicializa en subprocesos aislados para evitar interferencia concurrente.
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 usa Node 24+. LAN nueva realiza HTTP Upgrade / WebSocket real sobre duplex en memoria: verifica negociación/tramas/require/allow sin sockets de red. Pruebas loopback WS/TLS/HTTP existentes requieren enlazar puertos; sin permiso fallan Operation not permitted y no prueban éxito completo. Red/actualización comprueban éxito/error sellados sin descargar/instalar actualizaciones.
Riesgo Web Console: Pages entrega código; sitio/CDN comprometido puede leer texto en claro o firmar con claves del navegador (sin extraer privadas no exportables). Confidencialidad frente a Exchange requiere confiar en publicación estática; si no, use CLI o Android Console.
Identidades y claves
| Clave | Tipo | Almacenamiento | Visibilidad pública |
|---|---|---|---|
| Dispositivo | Ed25519 | agent agent.seal.key (0600); estado tmpfs (/var/run, /run, /tmp) usa /etc/remote-agent/, configurable REMOTE_SEAL_DIR; incluido en conffiles OpenWrt |
Registro info.seal_key, solo visualización; CLI no confía en copia |
| Operador | Ed25519 | CLI seal-identity.pk8 junto a config.toml (0600); Web IndexedDB remote-seal, WebCrypto Ed25519 CryptoKeyPair no exportable (semilla aleatoria IndexedDB si no compatible); Android semilla expo-secure-store cifrada Android Keystore |
Negociación |
| Raíz | Ed25519 | Clave del superadministrador; Web requiere semilla exportable y sustituye WebCrypto no exportable al generar raíz; exportable rsk1., copia Exchange automática de Console con contraseña vacía |
Registro rsr1. publicado/fijado |
| Sesión | X25519 efímero → HKDF-SHA256 → ChaCha20-Poly1305 por dirección | Solo memoria, descartada al terminar (secreto hacia adelante) | Nadie |
Las claves privadas del operador se guardan localmente. Console sube por defecto una copia de la raíz con contraseña vacía; Exchange o quien posea la base puede descifrarla. Puede establecerse una contraseña manualmente. Borrar la copia desactiva el guardado automático en este cliente.
Autorización (Exchange no es autoridad)
Dispositivo confía en una raíz pública del espacio. Política agent.seal.json:
{"mode":"require","root_key":"<base64 Ed25519>","revoked_keys":[]}
Agent decide nivel por sealed_open:
- Sin raíz fijada → rechazo (
no workspace root pinned on this device). - Operador en
revoked_keys→ rechazo. - Operador igual a
root_key→owner(administrar y cambiar política). - Con
grant: firma válida,operator_keycoincide,device_idlocal o*, vigente (reloj ±5 minutos). Firma raíz fijada o cadenarsc1.enhello.chain(fuera de firma hello), orden raíz→firmante; cada certificado permite emitir y no supera profundidad/nivel/ámbito/caducidad del anterior. Soloviewomanage, nuncaowner. - Resto → rechazo.
Restricciones de cadena de certificados
Se exige cadena rsc1. hasta raíz fijada (hello.chain, raíz→firmante), no intermedio aislado. Sin máximo funcional de niveles (pueden superar 10); barrera DoS: MAX_CERT_CHAIN 64, tamaño canónico total rsc1. MAX_CERT_CHAIN_BYTES ≤ 64 KiB (check_chain_limits / checkChainLimits en crates/proto/src/seal.rs / shared/seal-cert.js, iguales en dispositivo/Exchange/CLI/Web/Android). Excesos se rechazan.
max_path_len (0–255) es restricción opcional del emisor sobre certificados inferiores. seal cert issue sin --max-path usa toda profundidad restante (255 prácticamente ilimitado). La cantidad restante no puede superar max_path_len. El max_path_len del intermedio de administrador es 0: firma grants, no certificados. can_issue falso también impide emitir.
Hijos deben quedar dentro del padre (cert_within_parent / verify_chain):
- Nivel: raíz
owner; certificados/grants soloviewomanage, nunca superiores al padre niowner. - Ámbito: subconjunto del padre; solo padre
*permite todos; en otro caso cada dispositivo hijo debe figurar en padre. Hijo*exige padre*. - Caducidad: no posterior al padre; raíz sin máximo. Grant tampoco supera vigencia ni ámbito del emisor.
Revocar un intermedio invalida toda su rama. Se compara subject_key y serial de cada certificado con revoked_keys; cadenas con intermedio revocado fallan, incluidos certificados descendientes y grants. Borrar en Exchange solo detiene distribución: copias siguen válidas hasta caducar. Para invalidar de inmediato añada clave sujeto o serie a revoked_keys local.
proto::seal::required_level: view permite sensores, lista hold, Android lectura (status/tree/screenshot/find_text); exec, PTY, archivos, ubus, red, autoactualización y entrada Android requieren manage.
Grant es JSON firmado por raíz: alterar nivel, dispositivo, caducidad o destinatario invalida firma; hello lo vincula a sesión. Rol Exchange solo decide enrutamiento (visibilidad tenant), sin cambiar permisos sellados. Sin reclamar/propietario/copropietario por dispositivo: único owner es raíz.
Cómo fija raíz:
- TOFU: Exchange envía
seal_root {token, rotation?}con registro autofirmadorsr1.; dispositivo verifica y fija enagent.seal.jsonsi falta raíz. - Rotación: acepta cambio solo con
rsx1.firmado por raíz fijada,old_rootactual,new_rootnueva, mismo espacio; si no ignora y registra. Tambiénseal_policy_set {root_key}sellado (solo raíz actualowner). - Local:
remote-agent seal root <key>escribe;remote-agent seal forget-rootelimina para nuevo TOFU al reconectar (reiniciar agent).
Riesgo: Exchange comprometido en primer TOFU puede imponer raíz falsa. Fije antes con remote-agent seal root <key> (huella remote-cli seal root); después Exchange no puede sustituirla.
Revocación: raíz remote-cli seal policy <设备> --revoke <key> o local remote-agent seal revoke <key>. Raíz no revocable. Grant dura 90 días por defecto. Borrarlo de Exchange solo detiene distribución; copias importadas requieren revocación local antes de caducar.
Exchange como relé de autorizaciones (no fiable)
No hace falta copiar rsg1. ni claves en chats: directorio de claves y relé de grants por espacio. No cambia amenazas: Exchange no lee ni emite/amplía/falsifica. Guarda:
- Clave Ed25519 con posesión demostrada localmente (
POST /api/seal/keys). rsg1.firmado original (POST /api/seal/grants).- Intermedio
rsc1.original (POST /api/seal/certs); verifica firma Ed25519 sin poder falsificar.GET /api/seal/certs/chain?subject_key=obtiene cadena raíz primero, limitada a 64/64 KiB.
Dispositivo sigue exigiendo raíz fijada o cadena rsc1. hasta ella. Exchange analiza para indexar owner_key / operator_key / caducidad; subir no autoriza. Puede retener (no grant), no cambiar view a manage ni suplantar raíz.
Cualquier miembro sube grants: firma determina validez; carga no autoriza. CLI autenticado usa --publish; Web/Android «Emitir y publicar» usa mismo endpoint.
Prueba de posesión vincula:
remote-seal-key-register:v1:<workspace_id>:<operator_id>:<public_key>:<issued_at_ms>
Firma Ed25519 con esa clave; issued_at_ms dentro de ±10 minutos. CLI intenta registro en login/seal show; Console tras login.
Sin grant local coincidente intenta seal sync; rechazo no grant permite otra sincronización y un reintento.
Emisor online renueva grants publicados próximos a caducar, firma local; Exchange guarda nuevos rsg1. y elimina antiguos, sin cambiar nivel/destinatario.
- Cuándo: CLI tras login y al iniciar comandos autenticados (
seal renewmanual sin doble renovación); Web al entrar al espacio incluso sesión ya existente; Android tras login y primer plano. Sin sesión/grants propios no renueva. - Reglas:
GET /api/seal/grants?owner_key=<本机公钥>; vencimiento en 30 días reemite 90 con mismosdevice_id,operator_key,level; publica antes deDELETEantiguo. SiGET /api/seal/keysfunciona omiteoperator_keyausentes; si falla no filtra. - Frecuencia: Web/Android: una vez por emisor/12 horas; CLI
seal-renew-state.json, WeblocalStorage, Android SecureStore. Fallo no bloquea: stderr/console.warn; éxito CLI avisa brevemente. Web raíz al entrar y Android al abrir «Cifrado de extremo a extremo» emiten inicialmente sin límite; siguientes refrescos de visita sí limitados. - Renovación CLI: el éxito se registra por servidor y clave emisora. Tras el éxito se omiten las solicitudes de renovación durante 12 horas; un fallo o interrupción solo espera 5 minutos antes de reintentar. El proceso completo tiene un límite de 30 segundos. Los errores indican etapa y duración; si falla el comando principal, la renovación se cancela inmediatamente. Las revocaciones pendientes omiten ese límite de frecuencia y conservan un presupuesto independiente de 4 segundos. Renovación explícita:
remote-cli seal renew. Un inicio de sesión correcto fuerza una actualización. - Raíz: superadministrador usa emisión por roles siguiente para no recrear grants eliminados.
- Manual:
remote-cli seal renew, defecto--before 30 --days 90, mismas reglas.
# 按工作区用户名授权对方每一把已登记的钥匙,并发布到 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
Superadministrador del espacio como raíz de confianza
Raíz es Ed25519 del cliente superadministrador. Dispositivos aceptan esa raíz y rsg1. verificable mediante rsc1.. Cliente emite/renueva según rol. Exchange solo guarda rsr1. autofirmado, rsx1. y rsi1., verifica formato/firmas, no decide aprobación. Puede retener o insertar miembros falsos, no falsificar raíz. CLI/Web/Android aprueban automáticamente por defecto; nuevas claves del listado reciben firma. Solo desactivar deja pendientes claves nunca aprobadas/publicadas por raíz.
Modelo de confianza y compromisos
- TOFU raíz. Primera raíz válida se fija. CLI
seal-root.jsonjunto aconfig.toml(0600), clave服务器|工作区 id; WeblocalStorage(remote.seal.root.<工作区 id>); Android SecureStoreremote.seal.rootstate, JSON por servidor/espacio. Después verifica firma/huella. Cambios advierten: cliente no confía automáticamente (dispositivo sí conrsx1.). Compare y useremote-cli seal root --trust <key>o «Confiar en nueva raíz». - Invitación solo abre registro. Raíz firma
rsi1.(espacio, raíz, nonce 16 bytes, caducidad). Administrador/operador/lectura obtienen al login, comandos, carga o primer plano, verifican con raíz fijada y registran víaPOST /api/seal/keys. No autoriza. - Emisión por rol, aprobación local. Raíz lee
GET /api/seal/roster(solo superadministrador; desactivados, roles,operator_device_grants, claves) y grants propios, calcula:- Superadministrador/administrador sin restricciones:
manage*. - Administrador restringido: dispositivo delegado,
managesican_manage, si noview. - Operador no restringido:
view*másmanageen dispositivoscan_manage. - Operador restringido:
manage/viewpor delegación. - Lectura no restringida:
view*. - Lectura restringida:
viewpor dispositivo; nican_manageconcedemanage. - Desactivados: nada.
- Defecto 90 días; renueva con menos de 30.
- Superadministrador/administrador sin restricciones:
- Intermedios de administrador. Raíz emite
rsc1.a activos (manage,can_issue,max_path_len0, 90 días); ámbito*sin restricciones, si no dispositivos delegadoscan_manage. Renueva a 30 días; pendientes no reciben. Administrador con intermedio vigente firma grants a miembros gestionados en intersección certificado/delegación; omite si grant raíz reciente cubre operador/dispositivo/nivel (*cubre específico,managecubreview). Retirar de Exchange solo frena distribución; copias válidas hasta caducar. Revocación efectiva medianterevoked_keyslocal. - Aprobación automática predeterminada. Web
localStoragey Android SecureStore interpretan ausente como activado;falseexplícito permanece desactivado. Es más débil: Exchange comprometido inserta miembros y obtiene firma inmediata. Interruptor «Aprobar automáticamente nuevas claves públicas». CLI está activado por defecto, igual que Web/Android Console: si falta el archivoseal-approved.jsono el campoauto_approve, se considera activado;"auto_approve": falseexplícito permanece desactivado. Useremote-cli seal approve --no-auto-approvepara desactivar oremote-cli seal approve --auto-approvepara reactivar; la elección se guarda. Desactivado, claves nunca aprobadas/publicadas van a pendientes locales (CLIseal-approved.json, WeblocalStorage, Android SecureStore).remote-cli seal pendingmuestra usuario/huella;seal approve --alloseal approve <key>…aprueban/emiten inmediatamente. Ya aprobadas/publicadas renuevan/adaptan roles automáticamente. Clave propia no requiere aprobación. - Emitir al abrir. Web raíz al entrar y Android al abrir panel emiten sin límite 12 horas inicial; refrescos posteriores sí limitados. Cambiar interruptor o «Aprobar» fuerza emisión.
- Cuenta, insignia y errores. Web en diálogo «Cuenta» de barra del dispositivo coloca interruptor/pendientes; insignia «N claves públicas pendientes de aprobación» abre diálogo. Error en barra «Error de emisión de raíz del espacio: …», eliminando tokens y claves largas. Android muestra equivalentes en panel de cifrado.
- Reducir rol no revoca grants anteriores. Miembro eliminado/desactivado o clave ausente: borra grants, deja renovar e intenta
revoked_keysen dispositivos online compatibles. Reducir rol solo cambia grant, no añade la clave arevoked_keys; anteriores importados siguen vigentes. Nunca revoca raíz. Trabajo no terminado en 4 segundos queda local para siguiente comando, sin límite 12 horas. Clave nuevamente autorizada se desrevoca solo donde se revocó correctamente. - Registro raíz automático. Al crear superadministrador (
register/primera cuenta) o login posterior, sin raíz en Exchange cliente CLI/Web/Android publica su clave y una invitación 90 días. Invitación ausente o caduca en 30 días se refresca según límite de emisión. Manualremote-cli seal root --publishdisponible.
Configuración inicial
- Superadministrador registra/inicia sesión: genera/publica raíz (CLI stderr, Console aviso).
- Custodie raíz (una o ambas):
- Sin subir:
remote-cli seal root --export root.rsk(0600,-stdout) o «Exportar raíz privada». - Configuración manual de la copia nube:
remote-cli seal backupo panel de copia; contraseña vacía permitida, pero vacía/débil permite a Exchange descifrar raíz y controlar todos los dispositivos.
- Sin subir:
- Compatibles fijan raíz al conectar (TOFU); más seguro ejecutar antes
remote-agent seal root <根公钥>. - Miembros registran claves al login. CLI/Web/Android autoaprueban por defecto. Si está desactivado,
remote-cli seal pending, luegoremote-cli seal approve --allo «Aprobar» emite inmediatamente.
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 关闭自动批准(保持开启较弱)
Cambiar máquina / raíz
- Otra máquina: superadministrador sin raíz publicada local y copia
rsb1.en Exchange intenta restaurar automáticamente (véase sección siguiente). Si no, solicitaseal root --import <文件|->, pegarrsk1.en Console o restaurar copia (seal restore/ «Restaurar desde Exchange»). Importar raíz actual solo instala localmente. - Rotación:
seal root --rotateo importarrsk1.diferente. Con raíz actual, firmarsx1.y publica junto arsr1.mediantePUT /api/seal/root; online verifica firma antigua y cambia, offline recibe al reconectar. Aprobaciones previas permanecen locales y grants se firman con nueva raíz (90 días). Antigua guardadaseal-identity.<毫秒>.previous.pk8. Reexporte/respalde nueva. - Antigua perdida: no firma rotación. Publica nueva, pero dispositivos fijados no aceptan; ejecute
remote-agent seal root <新根公钥>y reinicie en cada uno. - Transferir superadministrador: véase transferencia raíz; cambiar rol no entrega clave fiable.
Restauración automática de raíz privada al iniciar sesión
Tras login superadministrador, antes de crear identidad local, CLI/Web/Android usan misma decisión pura (decide_auto_restore Rust, decideAutoRestore JS) para restaurar rsb1.:
- Omitir: no superadministrador, ya posee raíz publicada, nube desactivada (CLI
REMOTE_SEAL_CLOUD_BACKUP=0; WeblocalStorage.seal.cloudBackup=off; Androidremote.seal.cloudBackup=off), o raíz no publicada. - Obtener una vez:
GET /api/seal/backup, limitado por hora. Sin copia informa raíz en otro lugar sin bloquear. - Contraseña vacía silenciosa: si descifra, coincide raíz publicada y no hay otra clave local, instala/fija.
- Si no, contraseña: CLI prueba
REMOTE_SEAL_BACKUP_PASSPHRASE, entrada oculta TTY; Web/Android aviso no bloqueante para escribir en panel y «Restaurar desde Exchange». Sin diálogo bloqueante; login termina. - Seguridad: nunca sobrescribe otra clave silenciosamente (requiere
seal restore --forceo confirmación); raíz debe coincidir; fallo solo advierte; nunca imprime contraseña, semilla o cifradorsb1.. - Riesgo no bloqueante: avisa que varias máquinas con raíz aumentan exposición; éxito sin contraseña advierte que Exchange puede descifrarla.
Transferir superadministrador = transferir raíz
Rol y raíz son distintos. Solo transferir rol no entrega clave ni cambia confianza. Entregue raíz privada y rote inmediatamente para invalidar copia entregada.
- Titular con raíz usa
POST /api/superadmin/transfer,{operator_id, root_handover?}.root_handoveresrsb1.cifrado como backup (Argon2id 64 MiB, t=3, p=1, ChaCha20-Poly1305), contraseña puede ser vacía. CLIremote-cli seal handover <账号>; Web/Android solicitan contraseña opcional y adjuntan si poseen raíz. - Exchange guarda sin descifrar; verifica prefijo
rsb1./rsk1., máximo 16384 y ASCII seguro URL. Sinroot_handover, borra paquete residual del espacio. - Nuevo titular obtiene
{blob, from_operator, created_at}conGET /api/superadmin/handover(404 ausente), descifra/instala y rota: antigua firmarsx1., publica nueva. FinalmenteDELETE /api/superadmin/handover. CLIremote-cli seal accept-handover; Console muestra aceptación en panel si existe paquete. - Rotar invalida copias antiguas. Dispositivos online solo aceptan rotación firmada por raíz que ya confían. Antiguo titular no firma grants nuevos ni cambia raíz ya rotada. Antes de terminar, contraseña débil/vacía aún permite descifrar
rsb1.; comunique contraseña separadamente.
Otro canal: remote-cli seal handover <账号> --out-of-band imprime solo una línea rsb1. y omite root_handover. Entregue línea/contraseña por otros medios; destinatario remote-cli seal accept-handover <那一行>. Cancelar contraseña Console (Android «Solo transferir cuenta») transfiere rol sin subir raíz; entréguela aparte como rsk1. o rsb1. externo.
Negociación y formato de protocolo
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}; firma["remote-seal-v1/hello", v, device_id, operator_key, operator_ephemeral, nonce, grant.sig|""](array JSON canónico).rsr1.firma["remote-seal-v1/workspace-root", v, workspace_id, root_key, issued_at_ms];rsx1.={v, workspace_id, old_root, new_root, issued_at_ms, sig},old_rootfirma["remote-seal-v1/root-rotation", v, workspace_id, old_root, new_root, issued_at_ms]. Exchange→agent{"type":"seal_root","token":"rsr1.…","rotation":"rsx1.…"?}(solo control en claro desde Exchange, inválido en túnel).accept={v, device_key, device_ephemeral, nonce, level, sig}; firma["remote-seal-v1/accept", v, device_id, operator_key, operator_ephemeral, hello.nonce, hello.sig, device_key, device_ephemeral, nonce, level]; alterarlevelinvalida.- Claves:
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 byte canal (0 texto/1 binario) ‖ 3 ceros ‖ contadoru64big-endian. Por dirección/canal empieza en 0, incrementa uno; discrepancia cierra. - Texto
{"counter":n,"ciphertext":"<base64>"}. JSON interno operador→dispositivoSealedRequest(DeviceDownexistente oseal_policy_get/seal_policy_set {mode?, root_key?, revoke?, unrevoke?}); dispositivo→operadorSealedResponse(DeviceUposeal_policy). Agent reescribesession_idinterno al túnel. - Binario
StreamKind::Sealed = 4, cargacounter(u64 BE) ‖ AEAD(inner_kind ‖ bytes);inner_kind1/2/3 stdout/stderr/PTY. - Una operación por sesión (exec, PTY, transferencia…), modelo 1:1. Agent cifra final y marca
last:true; Exchange libera ruta/completa auditoría sin leer. close_session→sealed_close{detach:false}termina; desconexión operador →sealed_close{detach:true}(agent separa hold exec, termina otros).- Máximo base64 unos 2.8 MiB para captura Android 2 MiB; envoltorio
MAX_SEALED_WIRE.
Agent cifra o descarta texto en claro de IDs sellados; tras cerrar, ID queda aislado para siempre y salida hold tardía se descarta. Hold iniciado/adjuntado por sellado no aparece en lista antigua ni admite attach/tail/cat/kill antiguo.
Exportación y copia de seguridad de raíz privada
Raíz controla todos los dispositivos. Superadministrador decide custodia; Console guarda por defecto con contraseña vacía; borrar la copia desactiva el guardado automático:
- Exportar sin subir:
rsk1.+ semilla 32 bytes base64url sin padding. CLIseal root --export FILE|-(0600); Console muestra campo Web o copia portapapeles Android. Quien la obtiene controla todo. - Copia cifrada (automática en Console, manual en CLI): solo superadministrador cuya clave local es raíz publicada. Token versionado
rsb1.JSON base64url máximo 4096 caracteres. Argon2id deriva contraseña, parámetros producción incluidos: 64 MiB, 3 iteraciones, paralelismo 1. Sal 16 bytes y nonce ChaCha20-Poly1305 12 bytes aleatorios por copia. AADremote-seal-v1/key-backup:{version}:{public_key}: alterar versión/clave/cifrado falla; después comprueba clave derivada de semilla. Antes de Argon2 rechaza parámetros débiles/excesivos: memoria 8 KiB–256 MiB (producción mínimo 64 MiB), iteraciones 1–8 (producción mínimo 3), paralelismo solo 1. - Contraseña opcional, sin fuerza obligatoria. Vacía también usa Argon2id y mismo formato. CLI/Web/Android advierten si vacía/débil (menos de 12 caracteres, pocas categorías, repetida/común): Exchange o quien lea la base puede descifrar raíz y controlar todo. Rust/JS comparten reglas/vectores, incluido vacío.
PUT / GET / DELETE /api/seal/backup sirven solo cuenta actual; PUT/GET requieren superadministrador. Cuerpo {blob}; Exchange comprueba prefijo rsb1./longitud, nunca descifra. GET máximo 5 por cuenta/proceso/hora, luego 429. Solo frena ataques online; base copiada permite fuerza bruta offline. Auditoría lectura/escritura/borrado solo bytes.
- CLI
remote-cli seal backup(contraseña oculta dos veces, Intro vacío o--passphrase-stdin);remote-cli seal restorecon--forcesi hay otra clave, conservaseal-identity.<毫秒>.previous.pk8, fija raíz restaurada. También intenta al login. No imprime secretos/cifrado. - Web requiere raíz exportable; al generar sustituye WebCrypto no exportable por semilla IndexedDB. Bootstrap restaura antes de identidad;
localStorage.seal.cloudBackup=offdesactiva. - Android semilla expo-secure-store;
bootstrapSealrestaura primero,remote.seal.cloudBackup=offdesactiva.
Uso
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: todas las operaciones de dispositivo utilizan exclusivamente sesiones selladas nativas. Comandos, lista de trabajos hold y kill/tail/cat, terminal, archivos, capturas y entrada de pantalla (Android/escritorio), árbol de interfaz, sensores, ubus, registros del sistema y actualización propia pasan por el túnel sellado; la página muestra «🔒 Cifrado de extremo a extremo». El panel «Cifrado de extremo a extremo» en los detalles Web o en Ajustes de Android equivale a la CLI: muestra clave/huella local y raíz del espacio (superadministrador: exportar/importar/rotar la raíz privada, recibir su traspaso y aprobar claves nuevas), consulta/modifica claves del dispositivo y política de revocación (requiere raíz), emite autorizaciones como tokens rsg1. que CLI importa mediante seal import-grant, importa autorizaciones, confía en claves públicas de dispositivos y ofrece copia automática de la raíz privada en la nube (contraseña vacía permitida, con advertencia expresa de que Exchange puede descifrarla si es débil). La primera conexión fija la huella (TOFU); los cambios posteriores se rechazan y exigen comprobarla. Los miembros normales tienen claves distintas en CLI, navegador y móvil, que se registran por separado y reciben autorización de la raíz según el rol. Para ejercer de raíz desde varios dispositivos, el superadministrador puede subir una copia cifrada y restaurarla automáticamente al acceder desde otro equipo, o importar la raíz privada en cada uno.
Local: remote-agent seal show | root <key> | forget-root | revoke <key>; reinicie agent tras cambios; seal policy sellado aplica inmediatamente.
Comportamiento predeterminado
| Ámbito | Comportamiento actual |
|---|---|
| CLI, Web Console, Android Console | Todas las operaciones de dispositivo utilizan sesiones selladas nativas, sin interruptor para desactivarlas ni retorno a texto en claro |
| Política local del dispositivo | Todos los valores, incluido allow, rechazan operaciones sin cifrar |
- Los dispositivos nuevos fijan la raíz del espacio al conectarse a Exchange (TOFU); la raíz y sus autorizaciones permiten operar. Si no confía en Exchange durante la primera conexión, ejecute antes
remote-agent seal root <key>en el dispositivo. - Si la identidad no está disponible o la política está dañada, se bloquean las operaciones; repare las claves y la política en el dispositivo antes de volver a conectar.