Blog

Hive: paquetes y lenguajes, firmados dos veces

Larva instala dos tipos de código desde la red. Los paquetes son bibliotecas contra las que compila tu programa. Los paquetes de lenguaje son compiladores que se ejecutan dentro del propio larva. Ambos están firmados, y ambos se verifican antes de usar cualquier cosa. Sin embargo, no se confía en ellos de la misma manera, porque pueden causar daños de distinta magnitud. Este artículo recorre los dos modelos tal como los implementa Larva 0.8.

Los paquetes en un minuto

Hive está integrado en larva como larva hive. Un paquete es una biblioteca con un nombre Author/Name, como Antril-Organization/SDL3. Vive en un registro de paquetes: el público en https://www.antril.org/hive-api, otro servidor HTTPS o una simple carpeta. Agregar uno a un proyecto es un solo comando:

Terminal
larva hive add Ana/Greetings@^1.0
added `package: Ana/Greetings@^1.0` to Hello.queen
  fetching Ana/Greetings 1.0 from C:\work\registry
  Ana/Greetings 1.0
installed 1 package(s); pinned in hive.lock

Esto agrega una línea package: al chunk DEPS del proyecto, instala el paquete en la caché de Hive del usuario y escribe hive.lock junto al archivo .queen. A partir de ahí, los espacios de nombres del paquete están disponibles:

Antril
import System;
import Greetings;

Main()
{
	Console.WriteLine(Greeter.Greet("Hive"));
}

Dos reglas determinan todo lo demás. Compilar nunca toca la red: larva build, larva run y el editor solo miran en la caché. E instalar no ejecuta nada del paquete: no hay scripts de instalación. Los archivos se descargan, se verifican y se desempaquetan, y solo las compilaciones posteriores los leen.

Crear un paquete

Un paquete se crea a partir de un proyecto de biblioteca. Esta es la biblioteca completa detrás de Ana/Greetings:

Antril
into namespace Greetings
{
	/// Friendly greetings.
	public class Greeter
	{
		public:
			/// A greeting for `name`.
			static Greet(str name) : str => "Hello, " + name + "!";
	}
}

Su archivo .queen dice cómo se llama el paquete y cómo se firma:

.queen
queen 1.0;

chunk PROJ:
   name: Greetings,
   type: library,
   version: v1.0,
   author: Ana,
   description: "Friendly greetings."

chunk HIVE:
   provider: ../registry,
   license: MIT,
   sign: hive.key

La clave sale de larva hive key. Es una clave ECDSA P-256 en un archivo PEM. Su huella, el SHA-256 de la clave pública, la identifica en todas partes:

Terminal
larva hive key
wrote C:\work\Greetings\hive.key: keep it private (sign packages with `sign: hive.key` in HIVE)
public key:  MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEt2nz5PDcKGyCEddkuAPfBauYEJVK4G3bfrFHnPyxn3r4UkfuUNsWCkhPMgBcQ3LBxGvu5O297PxsXhRrYvQ1UQ==
fingerprint: a59d969b683ba7f96e9fece9d1bcf78180354d077be31daeb5b9f2ebdc7ddb2d

Después, larva hive pack genera el archivo .hive, y larva hive publish lo empaqueta y lo envía al primer registro de paquetes del proyecto. Publicar en www.antril.org requiere una cuenta, un autor que te pertenezca (el Ana de Ana/Greetings) y un token de API en la variable de entorno HIVE_TOKEN. Cuentas, tokens y autores explica los tres.

Un paquete es compilado, la opción por defecto, y lleva una biblioteca Larva compilada para un modo de GC, o es de código fuente, y lleva las fuentes para que tu proyecto las compile con su propia configuración. También puede envolver una biblioteca compilada en otro lugar. El paquete SDL3, por ejemplo, lleva un binding generado y SDL3.dll, y a un proyecto que lo agrega se le copia SDL3.dll junto a su programa. Consulta Paquetes compilados y de código fuente.

Qué es un archivo .hive

Un archivo .hive es un zip con una estructura fija:

hive.json            the manifest
hive.sig             the signature of the manifest's exact bytes
lib/Greetings.dll    the files, exactly those the manifest lists
package.queen

hive.json enumera cada archivo con su tamaño y su SHA-256. hive.sig es una firma ECDSA P-256 sobre los bytes del manifiesto. Como el manifiesto contiene el hash de cada archivo, una sola firma respalda todo el paquete. Hive firma los bytes tal como están guardados, no un objeto vuelto a serializar, así que quien verifica nunca necesita reproducir el formato JSON de nadie. Calcula el hash de lo que hay en el archivo.

El empaquetado es reproducible. Las entradas están ordenadas y todas las marcas de tiempo son fijas, así que el mismo proyecto siempre produce los mismos archivos. Solo cambia la firma, porque las firmas ECDSA son aleatorias. Puedes verificar tú mismo cualquier archivo de paquete:

Terminal
larva hive verify bin/Greetings-1.0.hive
Ana/Greetings 1.0 (compiled, gc default): 2 files, 4269 bytes
  signed by a59d969b683ba7f9 (not checked: give --key)
  every file matches its hash

Con --key y la clave pública del autor, verify también comprueba la firma.

Las reglas del registro de paquetes

Un registro de paquetes es, en su mayoría, archivos: para cada paquete, un index.json y los archivos del paquete al lado. El índice enumera cada versión con su SHA-256, la huella de la clave que la firmó y sus dependencias. También enumera las claves públicas autorizadas a firmar el paquete. Un registro en una carpeta y el registro de www.antril.org aplican las mismas reglas cuando se publica una versión:

  • Una versión se publica una sola vez y nunca se reemplaza.
  • La primera versión de un paquete trae su clave.
  • Las versiones posteriores deben estar firmadas con una clave que el paquete ya tenga.
  • Una vez que un paquete está firmado, se rechaza cualquier versión sin firma.

Así, un registro de paquetes no se puede usar para cambiar en silencio quién firma un paquete. También significa que tu clave importa. Si la pierdes, el registro no aceptará versiones nuevas firmadas con otra. Firmas y claves tiene los detalles.

Qué verifica la instalación

Cuando larva hive install descarga una versión, comprueba todo esto antes de escribir un solo archivo en el disco:

  • El SHA-256 del archivo es el que indica el índice del registro de paquetes. Para larva hive restore, también debe ser el que fijó hive.lock.
  • El archivo es un paquete válido. Su manifiesto enumera todos los archivos, cada archivo coincide con su tamaño y su hash, no hay nada que no esté en la lista, ninguna ruta sale de la carpeta del paquete y se respetan los límites (512 MB desempaquetado, 20.000 archivos). Los tamaños se controlan mientras se descomprime, así que una entrada que dice ser pequeña no puede expandirse sin límite.
  • Es el paquete que se pidió: el nombre y la versión del manifiesto son los del índice.
  • La firma se verifica con la clave que el índice indica para esa versión.
  • Esa clave es una en la que tú confías para el paquete.

La última comprobación es confianza en el primer uso. La primera clave con la que se instala un paquete queda guardada en la caché, en keys.json. Si más adelante llega una versión firmada con una clave distinta, aunque venga de otro registro, la instalación se detiene y explica qué cambió:

Terminal
larva hive install
  fetching Ana/Greetings 2.1 from C:\work\mirror
larva hive: Ana/Greetings is now signed by key bc0f3c915cb3c06d, not by the key it was installed with (a59d969b683ba7f9). If its author changed keys, accept the new one with `hive trust Ana/Greetings bc0f3c915cb3c06db2e910a4f5ae662e9a89db2664453d54b7e28012c7190ec0`

Si el autor de verdad cambió de clave (confirma la huella con él), larva hive trust acepta la nueva. La clave anterior también sigue siendo de confianza.

Por defecto, los paquetes deben estar firmados. Un proyecto puede aceptar paquetes sin firma con verify: hashes en su chunk HIVE. Los hashes se siguen comprobando, y una versión que el índice declara firmada se sigue verificando con su clave.

hive.lock registra, para cada paquete, la versión exacta, el SHA-256 de su archivo, la huella de quien lo firmó y el registro de paquetes del que vino. Guárdalo junto con tus fuentes. En una máquina nueva o en un servidor de compilación, larva hive restore instala exactamente lo que fija y rechaza cualquier archivo cuyo hash sea distinto.

Paquetes de lenguaje: firmados dos veces

Un paquete es un dato que lee tu compilador. Un paquete de lenguaje es código que se ejecuta dentro del propio proceso de larva. Es el frontend que convierte el texto fuente en el modelo de programa de Larva, más los servicios para el editor detrás de larva lsp. Por eso Larva le exige más a un paquete de lenguaje.

Incluso los paquetes de lenguaje que vienen con Larva se verifican cada vez. El manifiesto de un paquete, language.json, enumera el SHA-256 de cada archivo. Antes de cargar un paquete, larva comprueba que cada archivo de la lista tenga su hash y que ningún .dll, .so, .dylib o .exe de la carpeta del paquete falte en la lista. El código que el manifiesto no nombra nunca se carga. Si el manifiesto está firmado, la firma debe coincidir con él. larva languages muestra el resultado para cada paquete instalado:

  status:    ok (files verified)
  status:    NOT USABLE: extra.dll is not among its `files`
  status:    NOT USABLE: its signature does not match its manifest (changed since it was signed)

Los paquetes del registro de lenguajes en https://www.antril.org/languages-api/ van un paso más allá. Cada paquete publicado lleva dos firmas, y larva comprueba ambas antes de cargar un paquete que haya instalado:

  1. La firma del autor. La crea el autor con larva languages pack --key author.pem. Cubre el manifiesto canónico sin signature y sin registry. La primera vez que larva instala un lenguaje, recuerda la huella de la clave del autor para ese id de lenguaje, en trust.json. Después de eso, solo acepta versiones firmadas con la misma clave.
  2. La firma del registro. La agrega antril.org, como un objeto registry en el manifiesto, cuando un administrador aprueba la publicación. Cubre el manifiesto canónico sin registry, así que la firma del autor forma parte de lo que firma. larva trae la clave pública del registro y rechaza un paquete instalado que no tenga una firma de registro válida.
json
{
  "id": "mylang",
  "files": { "MyLang.dll": "<sha-256>" },
  "publicKey": "<author's key, base64 SubjectPublicKeyInfo>",
  "fingerprint": "<sha-256 of the author's key>",
  "signature": "<author's ECDSA P-256 signature>",
  "registry": {
    "fingerprint": "<sha-256 of the registry's key>",
    "signature": "<registry's ECDSA P-256 signature>"
  }
}

(El manifiesto real también tiene name, version, abi, larva, entry y extensions. Consulta El registro de lenguajes.)

El resultado es fácil de enunciar: un paquete de lenguaje debe estar aprobado por antril.org y además hecho por el autor original del lenguaje. Una clave de autor robada no basta sin una revisión. Un registro comprometido no basta sin la clave del autor, al menos para quien ya tiene el lenguaje instalado.

A diferencia de hive.json, un manifiesto de lenguaje se firma en una forma canónica. Las claves se ordenan por comparación ordinal, no hay espacios en blanco y las cadenas se escapan como lo hace un escritor de JSON estricto para incrustarlas de forma segura. Una consecuencia es que +, que aparece en toda clave y firma en base64, siempre se escribe \u002B. Como files y publicKey están dentro del texto firmado, cambiar una biblioteca, agregar una o reemplazar la clave rompe un hash o la firma.

Publicar un lenguaje

Cualquiera puede pedir que se publique un lenguaje, y un administrador de antril.org revisa cada solicitud a mano. En resumen:

  1. Crea una clave una sola vez con larva languages key author.pem y mantenla en privado. Todas las versiones deben firmarse con ella.
  2. Compila el paquete para cada plataforma que soportes, una carpeta por plataforma (win-x64, linux-x64, osx-arm64…).
  3. Envía la solicitud con larva languages publish packs/ --key author.pem y tu token de API en HIVE_TOKEN.

La primera solicitud para un id de lenguaje te convierte en su dueño y fija tu clave a él. Algunos ids están reservados, como antril. larva languages status <request> y la página de tu cuenta muestran en qué estado está una solicitud. Después, los usuarios instalan con larva languages install mylang, y update y remove funcionan como esperarías. remove conserva la clave de autor recordada, a menos que agregues --forget-key, para cuando un autor realmente cambió de clave.

El registro de lenguajes es nuevo en esta versión y todavía está cambiando. Los equipos pueden tener uno privado, una carpeta con la misma estructura firmada con su propia clave, usando larva languages approve y larva config languages.trust. Consulta Registros de lenguajes en el libro de internos.

Lo que todavía falta

  • Retirar versiones (yank): el índice tiene un campo yanked y los instaladores lo respetan, pero todavía nada lo activa en www.antril.org.
  • Un autor solo tiene dueños. Todavía no se pueden agregar otras personas a un autor.

Para saber más

Explora los paquetes en Hive, y trae tus preguntas al foro.

Todas las entradas · Feed Atom · RSS

Esc