Stage 7a: the default registry checks datekeys.capsule; README, changelog

StandardExtensions checks by default the data of datekeys.capsule with
checkCapsuleData, as locator.Standard of Go: when a .dkk is opened with
it, an extension whose data does not read is unusable, with the text of
Go. With validateCapsule: null it checks only that there is data, as
Go's extension.Standard without ValidateCapsule: the writer of a .dkk
keeps that one, as Go's writer does, and so do the tests of the formats
that compare with extension.Standard. Inspection and opening give Go's
results on every fixture and vector, and the tests of the locator use the
default registry.

lib/datekeys.dart exports locator.dart. The README has the section of
part 7a, its vectors and how the generator makes them; the changelog has
its entry, with its tests and its injected faults.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
v0.11
dev 2 days ago
parent bbb2946af5
commit b23a0eeb5c

@ -4,6 +4,27 @@ Cambios notables de la librería Dart. El proyecto usa versionado semántico; mi
## Especificación 0.11, en la rama `v0.11` — sin versión ## Especificación 0.11, en la rama `v0.11` — sin versión
### Etapa 7a: el localizador y el sobre de `datekeys.capsule` (06-10-2026)
- **El localizador y el sobre** (`lib/src/locator.dart`), port del paquete `locator` de `datekeys-go` en `c531e93`, el borrador v0.12 (§43 a §44.1, con el cambio 6 del §76), salvo `Seal`, con las mismas comprobaciones en el mismo orden, los mismos códigos y los mismos textos de error:
- los datos de la extensión: `CapsuleInfo`; `parseCapsuleInfo`, la nota con las reglas del §24.1, la DateKey canónica y un localizador que es un fichero `age` con un stanza tlock de su ronda, con `ERR_EXTENSION_DATA_INVALID` como único código; `CapsuleInfo.toExtension`, que lee lo que escribe (§72); y `checkCapsuleData`, el `ValidateCapsule` de `locator.Standard`;
- las direcciones: `checkAddressUri` y `LocatorAddress.host`, sobre los bytes UTF-8 de la dirección y con el `%q` de Go; y `checkResolvedIp`, la comprobación de la IP a la que resuelve un nombre, que la app hace en cada conexión, sobre los 4 o 16 bytes de la dirección. Go no la tiene, porque su lector no descarga;
- el localizador: `unmarshalLocator`, con la longitud que da `Marshal` y ninguna otra; `Locator.marshal`, con la clave 6 hasta el menor múltiplo de 4096 que llena, y `locatorPlaintextLength`; `Locator.usable`; y su apertura, `openLocator` y `CapsuleInfo.openLocator`, que lee como Go a lo sumo 1 MiB de texto, como `io.LimitReader`;
- el sobre: `Locator.restIn`, `Locator.openEnvelope`, `hideRest` y `splitEnvelope`, el corte del fichero `age` de `NewEnvelope`.
Los errores no llevan código, como en Go: son una `LocatorException` con su texto. Un `Locator` tiene los tipos de Go: claves y resúmenes de 32 bytes, y ningún tamaño ni desplazamiento negativo.
- **Las direcciones IP** (`lib/src/ipaddr.dart`, interno): la aceptación exacta de `netip.ParseAddr`, su `String` y `publicIP` con los bloques del §44.1, byte a byte para que una IPv6 sea exacta en la web, y sin `dart:io`.
- **`StandardExtensions`** comprueba por defecto la `data` de `datekeys.capsule` con `checkCapsuleData`, como `locator.Standard` de Go; con `validateCapsule: null`, solo que la haya, como `extension.Standard`. El escritor de la `.dkk` (`lib/src/accesskey.dart`) usa este último, como el de Go, y también las pruebas de los formatos que comparan con `extension.Standard` (`formats_extension_test.dart` y `formats_differential.dart`). La inspección y la apertura dan lo mismo que antes en cada fixture y cada vector.
- **`lib/datekeys.dart`** exporta `locator.dart`.
- **Vectores de Go:** `tool/locator_go_vectors.go` corre en el módulo de `datekeys-go`, sin cambiarlo, y escribe:
- `locator_uris.json`: `CheckURI` y `Address.Host` en las 247 direcciones de `locator.json` y en 2 800 más, en cada borde del §44.1 y de una semilla; `netip.ParseAddr` en 1 700 cadenas; y `publicIP`, al que llega con `go:linkname`, en 868 direcciones;
- `locator_vectors.json`: los textos de los demás casos de `locator.json`, `PlaintextLength` de -4100 a 16484, 482 plaintexts, `Marshal` en sus límites, 118 aperturas de cuatro rondas, ficheros que pasan de 1 MiB de texto, el sobre, sus restos y su corte, `Info.Extension`, `Info.OpenLocator`, `ParseInfo`, `locator.Standard` como registro y la apertura de un fixture cuya `.dkk` lleva `datekeys.capsule`.
`crypto/rand.Reader` es un ChaCha8 de una semilla fija, así que la salida es la misma en cada ejecución. `locator_uris.g.dart` lleva todo el primero, y `locator_vectors.g.dart` una parte del segundo, para Node.js.
- **Pruebas.** Todo se compara con Go: cada caso de `locator.json`, con su texto y su código, y cada uno de los dos ficheros de vectores. En Node.js corren las direcciones, las IP y una parte de los demás casos. 43 pruebas nuevas en la VM y 20 en Node.js: 1615 y 352 en total antes de integrarse; encima de la 6a, 1713 en la VM y 432 en Node.js.
- **Fallos inyectados**, uno a uno y revertidos, cada uno con las pruebas del localizador en la VM y en Node.js. Las pruebas detectan los diez del encargo: un bloque no público aceptado en su primera dirección, y en su última; una IPv4 con un cero a la izquierda; el puerto `:0443`; el esquema en mayúsculas; un segmento de 64 caracteres; un CID con los bits sobrantes distintos de cero; una dirección de NAT64 con una IPv4 pública dentro; un resto con otro SHA-256; y la comparación de los bloques de IPv6 sobre 64 bits, que detecta Node.js y no la VM, donde un `int` tiene 64 bits exactos. Y cuatro de cinco más: el texto de un localizador leído más allá de 1 MiB, que detectan las pruebas de la VM, las únicas que escriben esos ficheros; el byte fuera de RFC 3986 citado como byte y no como runa; un último segmento numérico con ceros a la izquierda leído como nombre; y el registro por defecto sin la comprobación de `datekeys.capsule`. El quinto, una zona aceptada en un literal IPv6, no lo detecta ninguna prueba porque ninguna dirección llega a él: el `%` de una zona hace rechazar antes la autoridad, también en Go, cuya comprobación de la zona tampoco se alcanza.
- **Para la parte 7b:** `Seal` y el cifrado de `NewEnvelope`, sobre el escritor de `age` de la 6a: un recipient tlock con `wrapTlockStanza`, un recipient X25519, el STREAM, la cabecera con su MAC y el azar, y después `splitEnvelope`.
### Etapa 6a: la escritura de `age` (06-10-2026) ### Etapa 6a: la escritura de `age` (06-10-2026)
- **La escritura de los ficheros `age`** (`lib/src/age_writer.dart`), port de `age.Encrypt`, `internal/format` e `internal/stream` de `filippo.io/age` v1.3.2, con sus comprobaciones y sus textos: - **La escritura de los ficheros `age`** (`lib/src/age_writer.dart`), port de `age.Encrypt`, `internal/format` e `internal/stream` de `filippo.io/age` v1.3.2, con sus comprobaciones y sus textos:

@ -6,7 +6,7 @@ Es la tercera implementación de la especificación, después de la de referenci
## Estado ## Estado
Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trabajo) y la parte 6a de la etapa 6: Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trabajo), la parte 6a de la etapa 6 y la parte 7a de la etapa 7:
- la etapa 0, el paquete, sus herramientas y `testdata/` sincronizado; - la etapa 0, el paquete, sus herramientas y `testdata/` sincronizado;
- la etapa 1, los errores normativos, los bytes, el perfil CBOR del §58 y el DER estricto, también el de los tiempos; - la etapa 1, los errores normativos, los bytes, el perfil CBOR del §58 y el DER estricto, también el de los tiempos;
- la etapa 2, las primitivas y la lectura de `age`; - la etapa 2, las primitivas y la lectura de `age`;
@ -20,10 +20,11 @@ Están hechas las etapas 0 a 5 del plan (`docs/PLAN_dart.md` del espacio de trab
- la 5a, el lector de las firmas CMS y de los sellos RFC 3161 con el perfil de certificado del borrador v0.12, ECDSA y RSA. Es interno; - la 5a, el lector de las firmas CMS y de los sellos RFC 3161 con el perfil de certificado del borrador v0.12, ECDSA y RSA. Es interno;
- la 5c, la firma de `alg` 2 y el sello de `seal_type` 2 en los veredictos, que une el lector de la 5a a la evaluación de la 5b; - la 5c, la firma de `alg` 2 y el sello de `seal_type` 2 en los veredictos, que une el lector de la 5a a la evaluación de la 5b;
- de la etapa 6, la parte 6a: la escritura de `age`, con la fuente inyectable de lo aleatorio, los recipients X25519, scrypt y tlock y las longitudes de un fichero antes de escribirlo. Es interna: la usarán el escritor de la cápsula, la parte 6b, y las claves de autor, la etapa 7b. - de la etapa 6, la parte 6a: la escritura de `age`, con la fuente inyectable de lo aleatorio, los recipients X25519, scrypt y tlock y las longitudes de un fichero antes de escribirlo. Es interna: la usarán el escritor de la cápsula, la parte 6b, y las claves de autor, la etapa 7b.
- la parte 7a de la etapa 7, el localizador y el sobre de `datekeys.capsule`: los datos de la extensión, la lectura y la apertura del localizador, las reglas de sus direcciones y de la IP a la que resuelve un nombre, y el resto del sobre. Sellar un localizador y crear un sobre necesitan el escritor de `age` de la etapa 6: son de la parte 7b.
La librería ya abre cápsulas reales, de los tres formatos, con todas sus credenciales, y evalúa toda su área de seguridad como Go: la firma de `alg` 1, la de `alg` 2 con certificados, el sello de `seal_type` 2 y todos los veredictos de su forma. Y ya escribe los ficheros `age` que forman una cápsula, byte a byte como Go con los mismos valores aleatorios. La librería ya abre cápsulas reales, de los tres formatos, con todas sus credenciales, y evalúa toda su área de seguridad como Go: la firma de `alg` 1, la de `alg` 2 con certificados, el sello de `seal_type` 2 y todos los veredictos de su forma. Y ya escribe los ficheros `age` que forman una cápsula, byte a byte como Go con los mismos valores aleatorios.
Las etapas 2 y 3 se hicieron en paralelo, en ramas aparte desde la etapa 1, y se integraron el 5 de octubre de 2026. Las partes 4a y 4b también: la 4a, en la rama `stage4a`, se integró encima de la 4b el mismo día. La 4c se hizo después, en la rama `v0.11`, y la 5b también, en paralelo con la 5a, el lector de CMS, que se hizo en la rama `stage5a` y se integró encima de la 5b el mismo día. La 5c, que necesitaba las dos, se hizo después, en la rama `v0.11`. La 6a se hizo en la rama `v0.11`, en paralelo con la 7a, la lectura del localizador, en la rama `stage7a`. Las etapas 2 y 3 se hicieron en paralelo, en ramas aparte desde la etapa 1, y se integraron el 5 de octubre de 2026. Las partes 4a y 4b también: la 4a, en la rama `stage4a`, se integró encima de la 4b el mismo día. La 4c se hizo después, en la rama `v0.11`, y la 5b también, en paralelo con la 5a, el lector de CMS, que se hizo en la rama `stage5a` y se integró encima de la 5b el mismo día. La 5c, que necesitaba las dos, se hizo después, en la rama `v0.11`. La 6a se hizo en la rama `v0.11`, en paralelo con la 7a, la lectura del localizador, en la rama `stage7a`. La 7a se integró encima de la 6a el mismo día.
La etapa 1 porta tres ficheros de `datekeys-go` en `601e6d2`, con las mismas lecturas, las mismas comprobaciones en el mismo orden y los mismos textos de error: La etapa 1 porta tres ficheros de `datekeys-go` en `601e6d2`, con las mismas lecturas, las mismas comprobaciones en el mismo orden y los mismos textos de error:
@ -215,6 +216,25 @@ Notas de la parte 6a:
- **`Random.secure` en Node.js** no está disponible dentro de `dart test -p node`, así que las pruebas compiladas a JavaScript usan `SeededRandomSource`. Fuera de las pruebas, con `globalThis.self = globalThis`, sí lo está. - **`Random.secure` en Node.js** no está disponible dentro de `dart test -p node`, así que las pruebas compiladas a JavaScript usan `SeededRandomSource`. Fuera de las pruebas, con `globalThis.self = globalThis`, sí lo está.
- **Lo que se exporta:** nada. La escritura de `age` es interna, como su lectura y `agewrap`; el escritor de la parte 6b exportará lo que necesite la app. - **Lo que se exporta:** nada. La escritura de `age` es interna, como su lectura y `agewrap`; el escritor de la parte 6b exportará lo que necesite la app.
La parte 7a de la etapa 7 porta el paquete `locator` de `datekeys-go` en `c531e93`, el borrador v0.12 (§43 a §44.1, con el cambio 6 del §76), salvo `Seal`, con las mismas comprobaciones en el mismo orden, los mismos códigos y los mismos textos de error:
| Módulo | Contenido | En Go |
|---|---|---|
| `lib/src/locator.dart` | Los datos de `datekeys.capsule` (`CapsuleInfo`, `parseCapsuleInfo`, `CapsuleInfo.toExtension` y `checkCapsuleData`); las direcciones (`LocatorAddress` con su `host`, y `checkAddressUri`) y la IP a la que resuelve un nombre (`checkResolvedIp`); el localizador (`Locator`, `unmarshalLocator`, `Locator.marshal`, `locatorPlaintextLength` y `Locator.usable`) y su apertura (`openLocator` y `CapsuleInfo.openLocator`); y el sobre (`Locator.restIn`, `Locator.openEnvelope`, `hideRest` y `splitEnvelope`) | `locator.go`, `open.go` y `hide.go` de `locator` |
| `lib/src/ipaddr.dart` | Las direcciones IP como las lee `netip.ParseAddr` y las escribe su `String`, y `publicIP` con los bloques del §44.1 | `net/netip`; `publicIP` de `locator.go` |
Notas de la parte 7a:
- **Lo que queda fuera.** La librería no descarga nada. La app pide el resto solo cuando la persona lo pide, después de mostrarle el host o el CID (`LocatorAddress.host`), y solo a una dirección que acepte `checkAddressUri` (`Locator.usable`); no sigue una redirección a una dirección que `checkAddressUri` rechace; comprueba con `checkResolvedIp`, en cada conexión, que la IP a la que resuelve un nombre es pública; lee solo los bytes del resto, y se los da a `Locator.openEnvelope`, que comprueba su SHA-256, descifra el `.dkc` y comprueba el suyo (§44.1).
- **`checkResolvedIp`** no está en Go, cuyo lector no descarga. Recibe los 4 o los 16 bytes de la dirección, los de `InternetAddress.rawAddress`, y la clasifica como `publicIP`: una IPv4 mapeada en IPv6 no es pública, así que la app pasa una IPv4 como sus 4 bytes. En una red móvil solo IPv6, el NAT64 del sistema da a un nombre con solo IPv4 una dirección de `64:ff9b::/96`, que el §44.1 no deja usar.
- **Sellar y crear un sobre.** `Seal` y el cifrado `age` de `NewEnvelope` necesitan el escritor de `age` de la etapa 6: son de la parte 7b. `splitEnvelope` es el resto de `NewEnvelope`: parte el fichero `age` del sobre en su cabecera y su resto, con los textos de `headerEnd`.
- **Las direcciones** se leen como están escritas, sin decodificar nada, sobre sus bytes UTF-8, como lee Go un string: el primer byte que RFC 3986 no admite se cita como lo cita el `%q` de Go, una runa (0xc3 es `'Ã'`). Las IP las lee y las clasifica `ipaddr.dart`, código propio con la aceptación exacta de `netip.ParseAddr`, nunca `InternetAddress` de `dart:io`, que acepta otras notaciones; una IPv6 son 16 bytes, y cada bloque se compara byte a byte.
- **Dos rarezas de Go que se conservan,** porque los vectores son los de Go: el host de una IPv6 es `strings.Trim(host, "[]")`, así que `https://[[2000::]/` vale; e `isCIDv1` mira que los bits sobrantes sean cero, no cuántos son, así que un CID con un carácter más cuyos bits son cero vale también, con otro texto que el canónico. Son de Go, no del texto del §44.1: si Go las corrige, sus vectores lo dirán.
- **La apertura** lee, como Go con `io.LimitReader`, como mucho 1 MiB del texto del localizador: un chunk de STREAM después de ese MiB no se descifra ni se comprueba, y lo leído no tiene la longitud de un localizador.
- **Los errores no llevan código normativo,** como en Go: un localizador que no se lee o no se abre, o una dirección fuera de las reglas, es inutilizable, y su error es una `LocatorException` con el texto de Go. Los datos de la extensión llevan un código solo, `ERR_EXTENSION_DATA_INVALID`, que la hace inutilizable a ella y nunca a la `.dkk` (§54).
- **`StandardExtensions`** comprueba por defecto la `data` de `datekeys.capsule` con `checkCapsuleData`, como `locator.Standard` de Go: al abrir una `.dkk` con ella, una extensión `datekeys.capsule` que no se lee queda inutilizable, con el texto de Go. Con `validateCapsule: null` solo comprueba que haya `data`, como el `extension.Standard` de Go sin `ValidateCapsule`: es el registro con el que el escritor de la `.dkk` comprueba lo que escribe, como el de Go, porque el codificador de `datekeys.capsule`, `CapsuleInfo.toExtension`, lee lo que escribe.
- **Los tipos de Go.** Un `Locator` tiene claves y resúmenes de 32 bytes, y ningún tamaño ni desplazamiento negativo, como los arrays y los `uint64` de Go: lo contrario es un `ArgumentError`. `CapsuleInfo.openLocator` usa el registro de perfiles por defecto, Quicknet, si no se le da uno, como la apertura de una cápsula; Go lo exige.
- **Lo que se exporta.** `lib/datekeys.dart` exporta `locator.dart` entero, como el paquete público `locator` de Go. `ipaddr.dart` es interno.
Los enteros son exactos en la VM y en la web. El `int` de Dart tiene 64 bits con signo en la VM y en la web es un double, exacto hasta 2^53. Por eso la librería no usa un `int` por encima de 2^53-1, ni desplazamientos u operaciones de bits de más de 31 bits: Los enteros son exactos en la VM y en la web. El `int` de Dart tiene 64 bits con signo en la VM y en la web es un double, exacto hasta 2^53. Por eso la librería no usa un `int` por encima de 2^53-1, ni desplazamientos u operaciones de bits de más de 31 bits:
- un entero de CBOR es un `int` hasta 2^53-1 y un `BigInt` por encima, como el `number | bigint` de `datekeys-ts`; - un entero de CBOR es un `int` hasta 2^53-1 y un `BigInt` por encima, como el `number | bigint` de `datekeys-ts`;
- `CborDecoder.uint` devuelve un `int`, porque todos los esquemas acotan sus enteros en 2^53-1, y `uint64` devuelve un `BigInt`; - `CborDecoder.uint` devuelve un `int`, porque todos los esquemas acotan sus enteros en 2^53-1, y `uint64` devuelve un `BigInt`;
@ -225,6 +245,7 @@ Los enteros son exactos en la VM y en la web. El `int` de Dart tiene 64 bits con
- en las tablas de Unicode, la clase de combinación de un punto de código va en una entrada que vale el punto por 256 más la clase, por debajo de 2^29, así que sus desplazamientos quedan en 32 bits; - en las tablas de Unicode, la clase de combinación de un punto de código va en una entrada que vale el punto por 256 más la clase, por debajo de 2^29, así que sus desplazamientos quedan en 32 bits;
- en ECDSA y RSA los enteros grandes son `BigInt`; las longitudes de DER, los INTEGER pequeños y el exponente de RSA son un `int` de cuatro bytes como mucho, por debajo de 2^32, que se lee sin desplazamientos; - en ECDSA y RSA los enteros grandes son `BigInt`; las longitudes de DER, los INTEGER pequeños y el exponente de RSA son un `int` de cuatro bytes como mucho, por debajo de 2^32, que se lee sin desplazamientos;
- al escribir `age`, el contador de 11 bytes del nonce del STREAM se incrementa byte a byte, las longitudes son sumas y cocientes por debajo de 2^53, y `randomIndex` lee sus cuatro bytes como mucho multiplicando por 256, sin un desplazamiento que la web truncaría. - al escribir `age`, el contador de 11 bytes del nonce del STREAM se incrementa byte a byte, las longitudes son sumas y cocientes por debajo de 2^53, y `randomIndex` lee sus cuatro bytes como mucho multiplicando por 256, sin un desplazamiento que la web truncaría.
- en el localizador, una IPv6 son 16 bytes y sus bloques se comparan byte a byte, nunca como un entero de 64 o 128 bits, y los varint de un CID, de hasta 63 bits, son `BigInt`; el tamaño del resto y los desplazamientos son un `int` hasta 2^53-1.
**`BigInt` no es de tiempo constante.** En las primitivas se usa solo con datos públicos: la reducción de escalares módulo ℓ y las comprobaciones de `onCurve` en Ed25519, que solo verifica, y el factor de trabajo de un stanza scrypt. Los secretos de las primitivas (el escalar de X25519, las claves de HMAC, ChaCha20 y Poly1305) van en la aritmética de limbs, sin ramas ni índices que dependan de ellos; ni la VM ni un motor de JavaScript prometen tiempo constante, aun así. **`BigInt` no es de tiempo constante.** En las primitivas se usa solo con datos públicos: la reducción de escalares módulo ℓ y las comprobaciones de `onCurve` en Ed25519, que solo verifica, y el factor de trabajo de un stanza scrypt. Los secretos de las primitivas (el escalar de X25519, las claves de HMAC, ChaCha20 y Poly1305) van en la aritmética de limbs, sin ramas ni índices que dependan de ellos; ni la VM ni un motor de JavaScript prometen tiempo constante, aun así.
@ -237,7 +258,7 @@ Las etapas siguientes traen el resto del protocolo en este orden:
| Etapa | Contenido | | Etapa | Contenido |
|---|---| |---|---|
| 6 | Escritor: la parte 6b, la cápsula de formato 3, `time_only` y `time_and_key`, con la llave de palabras y la nota pública | | 6 | Escritor: la parte 6b, la cápsula de formato 3, `time_only` y `time_and_key`, con la llave de palabras y la nota pública |
| 7 | Localizador y claves de autor | | 7 | El sellado del localizador y el sobre nuevo (parte 7b), y las claves de autor |
## Rendimiento ## Rendimiento
@ -452,7 +473,7 @@ dart run tool/sync_testdata.dart check --against ../datekeys-go
- Los ficheros de `testdata/` no se editan ni se generan aquí. - Los ficheros de `testdata/` no se editan ni se generan aquí.
- En cada `dart test`, `test/testdata_test.dart` comprueba la copia, y que cada fichero nombre `specVersion`. - En cada `dart test`, `test/testdata_test.dart` comprueba la copia, y que cada fichero nombre `specVersion`.
`test/vectors/` tiene los vectores de las primitivas, de la lectura y la escritura de `age`, de BLS12-381, de tlock, de las reglas de rutas y textos, de la llave de palabras, de los formatos, de la apertura, del área de seguridad, de la firma con certificados y el sello, y del lector de CMS. Los escribe Go, con las librerías de la caché de módulos que usa `datekeys-go` (`x/crypto`, `filippo.io/age`, `kilic/bls12-381`, `drand/kyber`, `kyber-bls12381` y `tlock`) y sus paquetes, como `provider`, `agewrap`, `capsule`, `internal/pathrule`, `internal/testkit` y `wordkey`; ningún valor esperado se escribe a mano. Los generadores van en `tool/`, con `//go:build ignore`, y se ejecutan en el contexto del módulo de `datekeys-go`, sin cambiar nada en él; los de las rutas, de la apertura, del lector de CMS y del escritor de `age`, en una exportación suya, y el último, `tool/cms_go_vectors_test.go`, como una prueba de Go. Los de BLS12-381 y tlock dan la misma salida con el tag `spec-v0.11` y con el borrador v0.12, y leen ficheros congelados de `datekeys-ts`, cuya carpeta en esta máquina se llama todavía `App`: `test/vectors/` tiene los vectores de las primitivas, de la lectura y la escritura de `age`, de BLS12-381, de tlock, de las reglas de rutas y textos, de la llave de palabras, de los formatos, de la apertura, del área de seguridad, de la firma con certificados y el sello, del lector de CMS y del localizador. Los escribe Go, con las librerías de la caché de módulos que usa `datekeys-go` (`x/crypto`, `filippo.io/age`, `kilic/bls12-381`, `drand/kyber`, `kyber-bls12381` y `tlock`) y sus paquetes, como `provider`, `agewrap`, `capsule`, `internal/pathrule`, `internal/testkit` y `wordkey`; ningún valor esperado se escribe a mano. Los generadores van en `tool/`, con `//go:build ignore`, y se ejecutan en el contexto del módulo de `datekeys-go`, sin cambiar nada en él; los de las rutas, de la apertura, del lector de CMS y del escritor de `age`, en una exportación suya, y el último, `tool/cms_go_vectors_test.go`, como una prueba de Go. Los de BLS12-381 y tlock dan la misma salida con el tag `spec-v0.11` y con el borrador v0.12, y leen ficheros congelados de `datekeys-ts`, cuya carpeta en esta máquina se llama todavía `App`:
```bash ```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/gen_primitive_vectors.go -out ../datekeys-dart/test/vectors cd ../datekeys-go && go run ../datekeys-dart/tool/gen_primitive_vectors.go -out ../datekeys-dart/test/vectors
@ -494,6 +515,12 @@ cd ../datekeys-go && go run ../datekeys-dart/tool/mutation_go_texts.go ../dateke
cd ../datekeys-go && go run ../datekeys-dart/tool/security_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors cd ../datekeys-go && go run ../datekeys-dart/tool/security_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
``` ```
```bash
cd ../datekeys-go && go run ../datekeys-dart/tool/locator_go_vectors.go -testdata ../datekeys-dart/testdata -out ../datekeys-dart/test/vectors
```
`publicIP` y `headerEnd` no se exportan en el paquete `locator`: `tool/locator_go_vectors.go` llega a ellos con `go:linkname`. Lo aleatorio de `age`, tlock y `NewEnvelope` sale de `crypto/rand.Reader`, que el generador cambia por un ChaCha8 de una semilla fija: la salida es la misma en cada ejecución.
`holderText` no se exporta en el paquete `capsule`: `tool/security_go_vectors.go` llega a él con `go:linkname`, que Go permite con un paquete de fuera de la biblioteca estándar. El mismo programa escribe los vectores de la parte 5c, `securitycms_vectors.json`, con firmas CMS y tokens RFC 3161 que hace él mismo como los hace `internal/cms/cmstest` para las pruebas de la referencia: ese paquete no se puede importar desde fuera del árbol de `datekeys-go`, así que el generador reescribe la parte que necesita. Las claves salen de etiquetas, ECDSA firma con el nonce del RFC 6979 y RSA con PKCS #1 v1.5, así que la salida no depende de `crypto/rand`. `holderText` no se exporta en el paquete `capsule`: `tool/security_go_vectors.go` llega a él con `go:linkname`, que Go permite con un paquete de fuera de la biblioteca estándar. El mismo programa escribe los vectores de la parte 5c, `securitycms_vectors.json`, con firmas CMS y tokens RFC 3161 que hace él mismo como los hace `internal/cms/cmstest` para las pruebas de la referencia: ese paquete no se puede importar desde fuera del árbol de `datekeys-go`, así que el generador reescribe la parte que necesita. Las claves salen de etiquetas, ECDSA firma con el nonce del RFC 6979 y RSA con PKCS #1 v1.5, así que la salida no depende de `crypto/rand`.
`internal/pathrule` solo se puede importar desde el árbol de `datekeys-go`, así que el generador de las rutas corre en una exportación suya, hecha con `git archive`, sin tocar el repositorio: `internal/pathrule` solo se puede importar desde el árbol de `datekeys-go`, así que el generador de las rutas corre en una exportación suya, hecha con `git archive`, sin tocar el repositorio:
@ -595,13 +622,17 @@ rm -rf "$tmp"
| `age_writer.g.dart` | El mismo JSON como constante de Dart | | `age_writer.g.dart` | El mismo JSON como constante de Dart |
| `age_interop.json` | Los ficheros que escribe este repositorio con `SeededRandomSource`, con las recetas de `test/age_interop_support.dart`: X25519 con textos de 0 a 3 MiB, uno escrito en trozos, tres y dieciséis recipients, tlock en la ronda 1000, tlock sobre dieciséis X25519 como un `SEALED_CONTROL`, y scrypt con los factores de trabajo 10 y 16. Cada uno con su longitud, su SHA-256, el fichero si es pequeño, la longitud de su cabecera, sus stanzas, las reglas de `agewrap` sobre ellos y el veredicto de Go con cada identity: el texto que abre, o el error | | `age_interop.json` | Los ficheros que escribe este repositorio con `SeededRandomSource`, con las recetas de `test/age_interop_support.dart`: X25519 con textos de 0 a 3 MiB, uno escrito en trozos, tres y dieciséis recipients, tlock en la ronda 1000, tlock sobre dieciséis X25519 como un `SEALED_CONTROL`, y scrypt con los factores de trabajo 10 y 16. Cada uno con su longitud, su SHA-256, el fichero si es pequeño, la longitud de su cabecera, sus stanzas, las reglas de `agewrap` sobre ellos y el veredicto de Go con cada identity: el texto que abre, o el error |
| `age_interop.g.dart` | El mismo JSON como constante de Dart | | `age_interop.g.dart` | El mismo JSON como constante de Dart |
| `locator_uris.json` | `CheckURI` y `Address.Host` en las 247 direcciones de `locator.json`, con su texto, y en 2 800 más, hechas en cada borde del §44.1 y sacadas de una semilla: IPv4 e IPv6 en cada notación que acepta o rechaza `netip.ParseAddr`, con zonas, mapeadas, compatibles, de NAT64, 6to4 y Teredo, la primera y la última dirección de cada bloque de IANA y sus vecinas, segmentos largos y punycode, nombres locales en mayúsculas y minúsculas, puertos, porcentajes, segmentos «.» y «..» y CID canónicos o no; `CheckURI` en bytes con un surrogate; `netip.ParseAddr`, con la dirección, su zona y su `String`, en 1 700 cadenas; y `publicIP` en los bytes de 868 direcciones |
| `locator_uris.g.dart` | El mismo JSON como constante de Dart |
| `locator_vectors.json` | Los textos de los demás casos de `locator.json`; `PlaintextLength` de -4100 a 16484; `Unmarshal` en 482 plaintexts de tres bases, válidos y rotos byte a byte y campo a campo; `Marshal` en cada límite y en cada borde del relleno; `Open` en 118 localizadores sellados de cuatro rondas, editados o con otros releases y perfiles; `Open` de ficheros cuyo texto pasa de 1 MiB; `OpenEnvelope`, `RestIn`, `Hide` y el corte de `NewEnvelope`; `Info.Extension`, `Info.OpenLocator` y `ParseInfo`; `locator.Standard` como registro; y `capsule.Open` de un fixture cuya `.dkk` lleva `datekeys.capsule` |
| `locator_vectors.g.dart` | Una parte de `locator_vectors.json`, como constante de Dart |
- Los fixtures que leen los generadores son los de `testdata/` de este repositorio, la copia sincronizada. - Los fixtures que leen los generadores son los de `testdata/` de este repositorio, la copia sincronizada.
- `primitives.json`, los cuatro ficheros de BLS12-381 y tlock, `pathrule_vectors.json`, `wordkey_vectors.json`, los de los formatos, `security_vectors.json`, `securitycms_vectors.json`, los del lector de CMS y los del escritor de `age` salen iguales en cada ejecución, los del lector de CMS con Go 1.26.8. Lo aleatorio de tlock, los ciphertexts de kyber con su sigma y los de `datekeys-ts`, se lee de los ficheros congelados de `datekeys-ts` en `289fe71`, y Go los descifra otra vez. `age`, en cambio, saca sus claves y nonces de `crypto/rand`, así que `age.json` y `age_fixtures.json` cambian en cada ejecución; las pruebas leen lo que esté en el repositorio. `age.json` no lee `testdata/` y es el congelado de la etapa 2; `age_fixtures.json` se escribió otra vez con el `testdata/` de la v0.12, y fuera de los dos fixtures nuevos y del de `seal_type` 4294967295 sale igual. - `primitives.json`, los cuatro ficheros de BLS12-381 y tlock, `pathrule_vectors.json`, `wordkey_vectors.json`, los de los formatos, `security_vectors.json`, `securitycms_vectors.json`, los del lector de CMS, los del escritor de `age` y los del localizador salen iguales en cada ejecución, los del lector de CMS y los del localizador con Go 1.26.8. Lo aleatorio de tlock, los ciphertexts de kyber con su sigma y los de `datekeys-ts`, se lee de los ficheros congelados de `datekeys-ts` en `289fe71`, y Go los descifra otra vez. `age`, en cambio, saca sus claves y nonces de `crypto/rand`, así que `age.json` y `age_fixtures.json` cambian en cada ejecución; las pruebas leen lo que esté en el repositorio. `age.json` no lee `testdata/` y es el congelado de la etapa 2; `age_fixtures.json` se escribió otra vez con el `testdata/` de la v0.12, y fuera de los dos fixtures nuevos y del de `seal_type` 4294967295 sale igual.
- Un fichero `age` de más de un chunk se guarda como su cabecera, su nonce y su file key: la prueba cifra otra vez el texto documentado y comprueba el SHA-256 del fichero entero antes de leerlo. Las cabeceras de megabytes se escriben como partes que se repiten. - Un fichero `age` de más de un chunk se guarda como su cabecera, su nonce y su file key: la prueba cifra otra vez el texto documentado y comprueba el SHA-256 del fichero entero antes de leerlo. Las cabeceras de megabytes se escriben como partes que se repiten.
- `locator_vectors.json` guarda un valor editado como ediciones de una base, con una forma más: `[at, 0, byte, n]` inserta n copias del byte. Una dirección con una racha de 32 bytes iguales o más va como `[antes, byte, n, después]`, y un texto de error, como su índice en `texts`. Los ficheros cuyo texto pasa de 1 MiB van como su cabecera, su nonce, su file key y la lista de sus chunks: la prueba los escribe otra vez y comprueba su SHA-256 antes de abrirlos.
- Los casos de `open_cases.json` que editan un fixture lo sellan otra vez como el `testkit` de la referencia, con las file keys y los nonces del fixture, así que salen iguales en cada ejecución, y se guardan como ediciones del fixture. - Los casos de `open_cases.json` que editan un fixture lo sellan otra vez como el `testkit` de la referencia, con las file keys y los nonces del fixture, así que salen iguales en cada ejecución, y se guardan como ediciones del fixture.
- Los ficheros del lector de CMS escriben una vez por fichero los certificados que se repiten: el DER de un caso es entonces una lista de trozos, el hexadecimal de unos bytes o el índice de un certificado, con el SHA-256 del resultado. `securitycms_vectors.json` hace lo mismo con los certificados, los tokens y los `SignerInfo`, cada trozo hecho a su vez de los anteriores, y una mutación se guarda como su base, el objetivo de la edición (la `SignedData`, `SIGNERS` o el token) y la edición. - Los ficheros del lector de CMS escriben una vez por fichero los certificados que se repiten: el DER de un caso es entonces una lista de trozos, el hexadecimal de unos bytes o el índice de un certificado, con el SHA-256 del resultado. `securitycms_vectors.json` hace lo mismo con los certificados, los tokens y los `SignerInfo`, cada trozo hecho a su vez de los anteriores, y una mutación se guarda como su base, el objetivo de la edición (la `SignedData`, `SIGNERS` o el token) y la edición.
- Las pruebas que corren en Node.js no leen ficheros. Los valores de Go que usan están en `primitives.g.dart`, `pathrule_vectors.g.dart`, `wordkey_vectors.g.dart`, `formats_vectors.g.dart`, `open_vectors.g.dart`, `security_vectors.g.dart`, `securitycms_vectors.g.dart`, `cms_vectors.g.dart`, `age_writer.g.dart`, `age_interop.g.dart`, `test/bls12381_constants.dart` y `test/ibe_constants.dart`. Unas pruebas en la VM comparan con los JSON los de las rutas, la llave de palabras, los formatos, la apertura, el área de seguridad, la firma con certificados y el sello, el lector de CMS, el escritor de `age`, BLS12-381 y el IBE, y con `testdata/` los fixtures que llevan.
## Licencia ## Licencia

@ -23,12 +23,19 @@
/// tlock recipients, for the writer of a capsule and the files of author /// tlock recipients, for the writer of a capsule and the files of author
/// keys. /// keys.
/// ///
/// And part 7a of stage 7: the extension datekeys.capsule of a .dkk, its
/// data, which [StandardExtensions] checks by default as locator.Standard of
/// Go, and what it points to, the locator sealed for the date and the
/// envelope of the .dkc: reading and opening a locator, the rules of its
/// addresses and of the IP that a name resolves to, and the rest of the
/// envelope. Sealing a locator and making an envelope come with part 7b.
///
/// DER, the primitives, the reading and the writing of age, the recipients /// DER, the primitives, the reading and the writing of age, the recipients
/// and the random sources, agewrap, the curve arithmetic, the IBE of tlock /// and the random sources, agewrap, the curve arithmetic, the IBE of tlock
/// and its stanza, the rules of paths and texts on the Unicode tables, the /// and its stanza, the rules of paths and texts on the Unicode tables, the
/// frame of BODY, the digest of a capsule, the steps of the opening, and /// frame of BODY, the digest of a capsule, the steps of the opening, the
/// the reader of CMS with its ECDSA and RSA are internal, as in the Go /// reader of CMS with its ECDSA and RSA, and the parser of IP addresses are
/// reference and datekeys-ts. /// internal, as in the Go reference and datekeys-ts.
library; library;
export 'src/accesskey.dart'; export 'src/accesskey.dart';
@ -56,6 +63,7 @@ export 'src/inspect.dart'
inspectView, inspectView,
inspectedLength, inspectedLength,
maxAccessKeyRead; maxAccessKeyRead;
export 'src/locator.dart';
export 'src/note.dart'; export 'src/note.dart';
export 'src/open.dart' show OpenOptions, Opened, openCapsule, openCapsuleSource; export 'src/open.dart' show OpenOptions, Opened, openCapsule, openCapsuleSource;
export 'src/padding.dart'; export 'src/padding.dart';

@ -301,13 +301,15 @@ Uint8List marshalAccessKeyBody(AccessKey k) {
..noncritical = canonicalExtensions(k.noncritical); ..noncritical = canonicalExtensions(k.noncritical);
checkDisjoint(w.critical, w.noncritical); checkDisjoint(w.critical, w.noncritical);
// Spec §72: datekeys.capsule goes only in the noncritical array of a .dkk, // Spec §72: datekeys.capsule goes only in the noncritical array of a .dkk,
// and datekeys.note never in a .dkk. // and datekeys.note never in a .dkk. Of the data of datekeys.capsule only
// its presence is checked, as Go's writer checks it with extension.Standard
// without ValidateCapsule: its encoder reads what it writes.
for (final (arr, exts) in [ for (final (arr, exts) in [
(ExtensionArray.critical, w.critical), (ExtensionArray.critical, w.critical),
(ExtensionArray.noncritical, w.noncritical), (ExtensionArray.noncritical, w.noncritical),
]) { ]) {
checkWrite( checkWrite(
const StandardExtensions(), const StandardExtensions(validateCapsule: null),
ExtensionObject.accessKey, ExtensionObject.accessKey,
arr, arr,
exts, exts,

@ -25,6 +25,7 @@ import 'dart:typed_data';
import 'bytes.dart'; import 'bytes.dart';
import 'cbor.dart'; import 'cbor.dart';
import 'errors.dart'; import 'errors.dart';
import 'locator.dart';
import 'note.dart'; import 'note.dart';
/// The largest extension_id, in UTF-8 bytes: an implementation limit of the /// The largest extension_id, in UTF-8 bytes: an implementation limit of the
@ -536,13 +537,19 @@ const capsuleExtensionId = 'datekeys.capsule';
/// ///
/// The data of a note is checked with the rules of spec §24.1, as Go's /// The data of a note is checked with the rules of spec §24.1, as Go's
/// CheckNote ([checkNoteData]). The data of datekeys.capsule is the /// CheckNote ([checkNoteData]). The data of datekeys.capsule is the
/// locator's: [validateCapsule] checks it, and without it only the presence /// locator's, and [validateCapsule] checks it: by default with the rules of
/// of the data is checked, as Go's Standard without ValidateCapsule. /// spec §44.1 ([checkCapsuleData]), as locator.Standard of Go. With
/// `validateCapsule: null`, only the presence of the data is checked, as
/// Go's extension.Standard without ValidateCapsule, the registry with which
/// the writers of Go check what they write: the encoder of datekeys.capsule
/// reads what it writes ([CapsuleInfo.toExtension]).
final class StandardExtensions extends ExtensionRegistry { final class StandardExtensions extends ExtensionRegistry {
/// The registry, with the check of the data of datekeys.capsule. /// The registry, with [validateCapsule] as the check of the data of
const StandardExtensions({this.validateCapsule}); /// datekeys.capsule.
const StandardExtensions({this.validateCapsule = checkCapsuleData});
/// Checks the data, present, of datekeys.capsule. /// Checks the data, present, of datekeys.capsule: [checkCapsuleData]
/// unless the caller gives another, or none.
final Object? Function(Extension e)? validateCapsule; final Object? Function(Extension e)? validateCapsule;
@override @override

@ -431,7 +431,8 @@ void formatsDifferential(
expectOutcome( expectOutcome(
c, c,
() => checkWrite( () => checkWrite(
const StandardExtensions(), // Go's extension.Standard, without ValidateCapsule.
const StandardExtensions(validateCapsule: null),
objectsByName[c['object']]!, objectsByName[c['object']]!,
arraysByName[c['array']]!, arraysByName[c['array']]!,
extensionsOf(c['extensions']), extensionsOf(c['extensions']),

@ -254,7 +254,8 @@ void main() {
1, 1,
Uint8List.fromList([0xa0]), Uint8List.fromList([0xa0]),
); );
const std = StandardExtensions(); // Go's extension.Standard, without ValidateCapsule.
const std = StandardExtensions(validateCapsule: null);
for (final o in ExtensionObject.values) { for (final o in ExtensionObject.values) {
for (final a in ExtensionArray.values) { for (final a in ExtensionArray.values) {
final noteHere = final noteHere =

@ -293,8 +293,11 @@ void locatorCases(Json v) {
test('the registry of locator.Standard checks the data of datekeys.capsule ' test('the registry of locator.Standard checks the data of datekeys.capsule '
'in a .dkk, and an encoder only its presence', () { 'in a .dkk, and an encoder only its presence', () {
const std = StandardExtensions(validateCapsule: checkCapsuleData); // The default registry of the library is locator.Standard of Go, and
// without ValidateCapsule it is the extension.Standard of Go's writers.
const std = StandardExtensions();
const presence = StandardExtensions(validateCapsule: null); const presence = StandardExtensions(validateCapsule: null);
expect(std.validateCapsule, checkCapsuleData);
for (final c in rows(v, 'registry')) { for (final c in rows(v, 'registry')) {
final d = c[2]! as int; final d = c[2]! as int;
final x = Extension( final x = Extension(

@ -352,9 +352,7 @@ void main() {
now: () => now, now: () => now,
accessKeyFile: file, accessKeyFile: file,
output: output, output: output,
extensions: c['standard'] == true extensions: c['standard'] == true ? const StandardExtensions() : null,
? const StandardExtensions(validateCapsule: checkCapsuleData)
: null,
), ),
); );
expect(opened.error?.message ?? '', textOf(v, c['text'])); expect(opened.error?.message ?? '', textOf(v, c['text']));

Loading…
Cancel
Save

Powered by TurnKey Linux.