From 48b496fdf29c499ea879a64830a3781ce24b9b30 Mon Sep 17 00:00:00 2001 From: dev Date: Thu, 1 Oct 2026 19:08:18 +0200 Subject: [PATCH] Spec v0.11 draft: the envelope can hide inside another file As the author asks: the rest of the envelope can be stored inside a host file, an image, a video or any file, and each address of the locator says at which byte it starts (44.1). - The envelope is split: its age header, up to the MAC, goes in the time-locked locator, and only the rest, nonce and STREAM, is stored outside. Those bytes carry no mark: nobody can tell they are an age file, let alone a capsule. The reader joins both and decrypts with I_SOBRE. - An address is a map with the URI and an optional offset; the reader asks only for those bytes, with an HTTP range when it can, and checks their SHA-256. - Concealment, not steganography: an analysis of the host can see extra bytes, not what they are. Only storage that keeps the file byte for byte works; social networks and messaging applications spoil a host. - The CDDL of the locator follows. Co-Authored-By: Claude Opus 5.5 --- spec/DateKeys_Protocol_Specification_v0.11.md | 51 ++++++++++++------- spec/datekeys.cddl | 19 ++++--- 2 files changed, 45 insertions(+), 25 deletions(-) diff --git a/spec/DateKeys_Protocol_Specification_v0.11.md b/spec/DateKeys_Protocol_Specification_v0.11.md index 7c5e793..781b095 100644 --- a/spec/DateKeys_Protocol_Specification_v0.11.md +++ b/spec/DateKeys_Protocol_Specification_v0.11.md @@ -2002,7 +2002,7 @@ Una identity X25519 solo podrá abrir el `INNER_ACCESS_AGE` para el que fue util Si no existe metadata de verificación, la clave `verification_metadata` MUST omitirse. Un mapa vacío no es una representación canónica válida de ausencia en V1. -Con un localizador (§44.1), `capsule_digest` en claro no delata dónde está la cápsula: lo que se guarda fuera es un sobre cifrado, con otro hash. +Con un localizador (§44.1), `capsule_digest` en claro no delata dónde está la cápsula: lo que se guarda fuera es el resto de un sobre cifrado, con otro hash. --- @@ -2037,26 +2037,37 @@ La extensión `datekeys.capsule`, versión 1, registrada para el array no críti - El localizador es un fichero `age` con un único stanza tlock para la ronda y la cadena de esa DateKey (§28.1, §35). Su plaintext es otro mapa CBOR con el perfil de §58: ```text - 0 → direcciones (array de 1 a 8 textos, de 1 a 1024 bytes cada uno) + 0 → direcciones (array de 1 a 8 mapas de dirección) 1 → I_SOBRE (32 bytes: la identity X25519 cruda del sobre) - 2 → sobre_digest (32 bytes: el SHA-256 del sobre) - 3 → sobre_size (entero: la longitud del sobre en bytes) - 4 → capsule_digest (32 bytes, §43) - 5 → relleno (cadena de bytes a cero, opcional) + 2 → cabecera del sobre (cadena de bytes de 1 a 1024) + 3 → resto_digest (32 bytes: el SHA-256 del resto del sobre) + 4 → resto_size (entero: la longitud del resto en bytes) + 5 → capsule_digest (32 bytes, §43) + 6 → relleno (cadena de bytes a cero, opcional) + + dirección: + 0 → URI (texto de 1 a 1024 bytes) + 1 → desplazamiento (entero, opcional: 0 si falta) ``` - El plaintext mide exactamente 4096 bytes, o el menor múltiplo de 4096 en el que quepa: la clave 5 completa lo que falte, así que su longitud no delata cuántas direcciones hay ni de qué tipo. + El plaintext mide exactamente 4096 bytes, o el menor múltiplo de 4096 en el que quepa: la clave 6 completa lo que falte, así que su longitud no delata cuántas direcciones hay ni de qué tipo. -**El sobre.** Lo que se guarda fuera no es el `.dkc`, sino un sobre: un fichero `age` con un único stanza X25519 para la pública de `I_SOBRE`, una identity nueva de un CSPRNG, cuyo plaintext son los bytes exactos del `.dkc`. Quien encuentra el sobre ve un fichero `age` y su tamaño, nada de la cápsula: ni `capsule_id`, ni la fecha, ni la nota. Nadie, tampoco quien tiene la llave, lee el localizador ni el sobre antes de la fecha. En la fecha, el mismo release que abre la cápsula descifra el localizador. +**El sobre.** Quien crea la cápsula cifra el `.dkc` con `age` para la pública de `I_SOBRE`, una identity nueva de un CSPRNG, y parte ese fichero `age` en dos: la cabecera, hasta el salto de línea de su MAC incluido, que va en el localizador; y el resto, el nonce y los chunks de STREAM, que es lo único que se guarda fuera. El resto no lleva ninguna marca: sus bytes no se distinguen del azar, así que quien lo encuentra no sabe que es un fichero `age` ni una cápsula. Nadie, tampoco quien tiene la llave, lee el localizador antes de la fecha. En la fecha, el mismo release que abre la cápsula lo descifra, y el lector une la cabecera y el resto y descifra el `.dkc` con `I_SOBRE`. -**Direcciones.** Cada una es un URI ASCII de RFC 3986, con el esquema `https` o `ipfs` (un CID v1), sin userinfo. Un lector: +**Dentro de otro fichero.** El resto puede guardarse solo o dentro de otro fichero, el huésped: una imagen, un vídeo, un documento o un fichero de cualquier tipo, normalmente añadido al final, donde casi todos los programas lo ignoran. Su dirección dice entonces en qué byte del huésped empieza; su longitud es `resto_size`. + +- Es ocultación, no esteganografía: quien analice el huésped puede ver que lleva bytes de más, pero no qué son ni de qué cápsula. +- Solo sirve un almacenamiento que conserva el fichero byte a byte, como un disco en la nube, IPFS o un servidor propio. Una red social o una aplicación de mensajería recomprimen las imágenes y los vídeos, o quitan lo que sobra, y el resto se pierde. + +**Direcciones.** Cada URI es ASCII, de RFC 3986, con el esquema `https` o `ipfs` (un CID v1), sin userinfo. Un lector: - MUST NOT descargar sin que la persona lo pida, y MUST mostrar antes el host o el CID: la descarga revela a quien controla la dirección cuándo y desde dónde se usa la llave, y en IPFS la ven la pasarela y los pares; -- MUST NOT seguir una redirección a otro esquema ni a una dirección de loopback, privada o de enlace local, y MUST cortar la descarga al pasar de `sobre_size` bytes; -- MUST comprobar que el SHA-256 del sobre es `sobre_digest`, descifrarlo con `I_SOBRE` y comprobar que el SHA-256 del `.dkc` es `capsule_digest` antes de usarlo, y SHOULD guardar una copia; +- MUST pedir solo los bytes del resto, con un rango de HTTP si el servidor lo admite; si no, MUST dejar de leer al llegar al desplazamiento más `resto_size`; +- MUST NOT seguir una redirección a otro esquema ni a una dirección de loopback, privada o de enlace local; +- MUST comprobar que el SHA-256 del resto es `resto_digest`, descifrar el sobre con `I_SOBRE` y comprobar que el SHA-256 del `.dkc` es `capsule_digest` antes de usarlo, y SHOULD guardar una copia; - MUST tratar como inutilizable un localizador cuya ronda o cuya cadena no son las de `compact_datekey`. -`sobre_digest` y `capsule_digest` protegen frente a quien guarda el sobre, no frente a quien escribió la `.dkk`. +`resto_digest` y `capsule_digest` protegen frente a quien guarda el resto o el huésped, no frente a quien escribió la `.dkk`. Como toda extensión de la `.dkk`, es informativa: nada la ata a la cápsula (§44). Un lector: @@ -2065,8 +2076,9 @@ Como toda extensión de la `.dkk`, es informativa: nada la ata a la cápsula (§ Un escritor: -- MUST guardar fuera solo el sobre, nunca el `.dkc`, y conocer las direcciones antes de escribir la `.dkk`: primero crea el sobre y lo guarda, también en IPFS (§62.1, regla 24); -- SHOULD avisar de que con un localizador la `.dkk` basta para encontrar la cápsula y abrirla en la fecha; de que quien solo tiene la llave no sabe dónde está la cápsula hasta la fecha, aunque quien guarda el sobre lo tiene; y de que una dirección que deja de existir pierde la cápsula para quien no tenga otra copia. +- MUST guardar fuera solo el resto del sobre, nunca el `.dkc` ni la cabecera del sobre, y conocer las direcciones antes de escribir la `.dkk`: primero guarda el resto, solo o en su huésped, también en IPFS (§62.1, regla 24); +- con un huésped, SHOULD añadir el resto al final, y MUST comprobar antes de escribir la `.dkk` que el fichero que guarda tiene el resto en ese desplazamiento; +- SHOULD avisar de que con un localizador la `.dkk` basta para encontrar la cápsula y abrirla en la fecha; de que quien solo tiene la llave no sabe dónde está la cápsula hasta la fecha; de que una red social o una aplicación de mensajería estropean un huésped; y de que una dirección que deja de existir pierde la cápsula para quien no tenga otra copia. --- @@ -2375,7 +2387,7 @@ Ese supuesto no resiste a un adversario cuántico futuro (§7.7): quien conserve ### Fuera del `.dkc` - una `.dkk` lleva en claro la credencial, `access_material`, y además `capsule_id`, `credential_id`, el `capsule_digest` opcional, que la ata a los bytes exactos de un `.dkc`, y la `data` de sus extensiones (§41, §43, §44); -- la extensión `datekeys.capsule` de una `.dkk` lleva en claro la nota y la fecha, y cifrado para la fecha el localizador. Lo que se guarda fuera es un sobre cifrado: quien lo aloja ve su tamaño y quién lo sube o lo baja, nada de la cápsula (§44.1); +- la extensión `datekeys.capsule` de una `.dkk` lleva en claro la nota y la fecha, y cifrado para la fecha el localizador. Lo que se guarda fuera es el resto de un sobre cifrado, sin cabecera, solo o dentro de otro fichero: quien lo aloja ve bytes al azar, su tamaño y quién los sube o los baja, nada de la cápsula (§44.1); - pedir el release revela a la Release API y a los relays el perfil, la ronda y la dirección de quien pregunta, pero no `capsule_id` (§45, §48); - el nombre del fichero `.dkc`, sus fechas en el sistema de ficheros y el canal de entrega no pertenecen al protocolo (§6). En formato 3, los nombres y las mtimes de los ficheros que guarda la cápsula sí van dentro, cifrados en el head (§29.4). @@ -2741,7 +2753,7 @@ Con firma o sello, además, MUST: Con nota pública, llave de palabras o extensión de cápsula, MUST: 23. **Nota pública.** Escribir la extensión `datekeys.note` solo si la persona la pide, con un texto que cumpla las reglas del autor declarado de §29.6, y SHOULD avisar de que es pública y de que, con la fecha, puede identificar a alguien (§24.1). -24. **Llaves.** Cumplir las reglas del escritor de §38.1 para una llave de palabras. Para una `.dkk` con localizador, crear el sobre, guardarlo fuera y escribir después la `.dkk` con sus direcciones; nunca guardar fuera el `.dkc` (§44.1). +24. **Llaves.** Cumplir las reglas del escritor de §38.1 para una llave de palabras. Para una `.dkk` con localizador, crear el sobre, guardar fuera su resto, solo o en un huésped, y escribir después la `.dkk` con sus direcciones; nunca guardar fuera el `.dkc` ni la cabecera del sobre (§44.1). Con firma o sello, además, MUST: @@ -3159,7 +3171,7 @@ Firma, sello, nota y llave de palabras (v0.11), además: - el mismo contenido con firma y sin ella, en el área común: la misma P; - la nota pública cambiada en `PUBLIC_HEADER`: `ERR_HEADER_BINDING` en el paso 15; con un salto de línea o un carácter que §29.6 prohíbe: inutilizable y sin mostrarse; - la llave de palabras: el vector de §38.1; textos con mayúsculas, acentos y espacios que dan las mismas palabras; y un texto con U+200B, que el escritor rechaza; -- la extensión `datekeys.capsule`: un localizador para otra ronda (inutilizable); una dirección `file:` o `http:` (no se descarga); un sobre cuyo SHA-256 no es `sobre_digest` (no se usa). +- la extensión `datekeys.capsule`: un localizador para otra ronda (inutilizable); una dirección `file:` o `http:` (no se descarga); un resto cuyo SHA-256 no es `resto_digest`, porque alguien recomprimió el huésped o el desplazamiento no es el suyo (no se usa); un recurso que sigue tras el resto, del que se lee solo hasta el desplazamiento más `resto_size`. --- ## 65. Test vectors Quicknet @@ -3632,7 +3644,8 @@ word key public note = datekeys.note en PUBLIC_HEADER, pública y atada en el paso 15; copia informativa en datekeys.capsule de la .dkk, con la fecha y un - localizador cifrado para la fecha, que apunta a un sobre opaco + localizador cifrado para la fecha, que apunta al resto de un sobre + opaco, solo o dentro de otro fichero recovery = puede obtener release directamente del provider @@ -4013,7 +4026,7 @@ La v0.11 define la firma de autor y el sello de tiempo del área `security`, que - Caso: la página `/create` de `datekeys-ts` y la CLI de Go la implementan, y la CLI abre una cápsula hecha en la página. Su primera versión no llevaba `capsule_id` en la sal: las mismas palabras daban la misma llave en todas las cápsulas de una ronda, y un diccionario las atacaba a la vez (revisión del borrador). - Pruebas previstas: el vector y la normalización (§64). 7. **Nota pública y extensión de cápsula** (§24.1, §43, §44.1, §72). - - Cambio: `datekeys.note` en `PUBLIC_HEADER`, y `datekeys.capsule` en la `.dkk`, con la nota, la fecha y un localizador cifrado para la fecha que apunta a un sobre opaco. + - Cambio: `datekeys.note` en `PUBLIC_HEADER`, y `datekeys.capsule` en la `.dkk`, con la nota, la fecha y un localizador cifrado para la fecha que apunta al resto de un sobre opaco, sin cabecera, solo o dentro de otro fichero. - Motivo: una aplicación que lista cápsulas y llaves solo puede distinguirlas hoy por `capsule_id`, y una llave suelta no dice de qué cápsula es ni dónde está. - Caso: una `.dkk` de la v0.10 lleva `capsule_id` y `capsule_digest`, nada legible para una persona. Y en IPFS un `.dkc` es público: un rastreador indexaría `capsule_id`, la nota y la fecha, y quien tiene la `.dkk` encontraría la cápsula antes de la fecha; por eso se guarda un sobre. - Pruebas previstas: la nota cambiada y la nota inválida, y los casos de `datekeys.capsule` (§64). diff --git a/spec/datekeys.cddl b/spec/datekeys.cddl index 985c6d0..f570f82 100644 --- a/spec/datekeys.cddl +++ b/spec/datekeys.cddl @@ -214,14 +214,21 @@ capsule-data = { ; for the round and chain of key 1 } ; Plaintext of the locator, readable at the unlock date: exactly 4096 -; bytes, or the least multiple of 4096 that holds it, with key 5. +; bytes, or the least multiple of 4096 that holds it, with key 6. The +; envelope is an age file of the .dkc for I_SOBRE: its header goes here, and +; only the rest, nonce and STREAM, is stored outside, alone or in a host file. capsule-locator = { - 0 => [1*8 tstr .size (1..1024)], ; addresses: https or ipfs URI + 0 => [1*8 capsule-address], ; where the rest of the envelope is 1 => bstr .size 32, ; I_SOBRE, identity of the envelope - 2 => bstr .size 32, ; SHA-256 of the envelope - 3 => uint, ; size of the envelope in bytes - 4 => bstr .size 32, ; capsule_digest - ? 5 => bstr, ; zero padding + 2 => bstr .size (1..1024), ; age header of the envelope, up to its MAC + 3 => bstr .size 32, ; SHA-256 of the rest + 4 => uint, ; size of the rest in bytes + 5 => bstr .size 32, ; capsule_digest + ? 6 => bstr, ; zero padding +} +capsule-address = { + 0 => tstr .size (1..1024), ; https or ipfs URI + ? 1 => uint, ; offset of the rest in that resource; 0 if absent } ; Spec section 29.4. HEAD_CBOR of format 3, at most 16 MiB. Always version 1