Etapa F paquetería #4: firma del release (índice del repo firmado)

Cierra el hueco de seguridad: hoy se firmaba cada .swm (autoría del paquete) pero NO el
catálogo ⇒ un atacante podía añadir/quitar/intercambiar entradas del index.json. Firmar el
release ancla qué paquetes/versiones/hashes existen.

- hammer-core/sign.rs: extraídos `KeyPair::sign_raw` + `verify_raw` genéricos (bytes canónicos
  arbitrarios); Swm::{sign,verify_signature} ahora los reusan (DRY, sin cambio de comportamiento).
- hammer-core/repo.rs: `RepoIndex.signature` (Ed25519 sobre la lista de paquetes canónica,
  excluye la propia firma) + `sign`/`verify_signature`. `upsert` INVALIDA la firma (cualquier
  cambio al catálogo ⇒ re-firmar). 4 tests (sign→verify, survive save/load, upsert-invalida,
  tamper→BadSig).
- hammer-cli: `repo sign --key` / `repo verify`; `install` VERIFICA el release antes de resolver
  (BadSig ⇒ aborta "el índice fue manipulado"); `repo list` muestra si está firmado.
- Validado E2E host: sin firmar→firmar→trusted→install lo ve; MANIPULAR el índice sin re-firmar
  ⇒ install ABORTA; re-publicar invalida la firma. 31 suites verde.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-06-21 06:53:31 -04:00
co-authored by Claude Opus 4.8
parent b89e2dba51
commit 977f07ca6c
4 changed files with 251 additions and 42 deletions
+10
View File
@@ -144,6 +144,9 @@ CLI:
materializadas en el sandbox, + hidrata). Nunca corre un binario ajeno. `--prefix`/
`--skip-source-patch` para staging y dry-run de schema.
- `hammer repo list [--repo DIR]` — lista el catálogo (`<repo>/index.json`).
- `hammer repo sign --repo DIR --key KEY` — firma el ÍNDICE entero (release). Re-firmá tras
publicar (cada `pack --repo` invalida la firma del release).
- `hammer repo verify --repo DIR [--trust DIR]` — verifica la firma del release.
## 7. Repositorio de paquetes (Etapa F)
@@ -165,3 +168,10 @@ hay ciclo) y, para reproducir, **reconstruye un catálogo de recetas** efímero:
paquete con deps (p. ej. `bwrap``libcap`, `openssh``zlib,openssl`) se reproduce desde fuente
bit-idéntico — verificado: `install bwrap` reproduce el artefacto cacheado exacto resolviendo
`libcap` del catálogo.
**Firma del release.** Además de la firma de cada `.swm` (autoría del paquete), el ÍNDICE entero
se puede firmar como release (`RepoIndex::signature`, Ed25519 sobre la lista de paquetes
canónica). Ancla qué paquetes/versiones/hashes existen: un atacante no puede añadir, quitar ni
intercambiar entradas sin invalidar la firma. Cualquier `upsert` (publicar) la invalida ⇒
re-firmar tras publicar. `install` verifica el release antes de resolver: una firma mala aborta
("el índice fue manipulado"). Mismo primitivo Ed25519 que el `.swm` (`sign::{sign_raw,verify_raw}`).