#!/usr/bin/env bun /** * Generate an ed25519 signing keypair for the index. * * Prints the PUBLIC key in the exact form the host pins (`ed25519:` of * the raw 32 bytes) and writes the PRIVATE key as a PKCS#8 PEM. * * The private key is NEVER printed to stdout -- it only ever reaches a file * with 0600 permissions, so it cannot leak into a terminal scrollback, a CI * log, or a shell history via a pipe. * * Usage: * bun tools/keygen.ts [--out path/to/key.pem] [--force] * * Then: store the PEM contents in the INDEX_SIGNING_KEY CI secret, add the * public key to a host key slot, and destroy every other copy of the PEM. */ import { generateKeyPairSync } from "node:crypto"; import { existsSync, writeFileSync } from "node:fs"; import { resolve } from "node:path"; import { encodePublicKey } from "./keys.ts"; function die(message: string): never { console.error(`error: ${message}`); process.exit(1); } const args = process.argv.slice(2); let out = "index-signing-key.pem"; let force = false; for (let i = 0; i < args.length; i++) { const arg = args[i]!; if (arg === "--out") { const next = args[++i]; if (!next) die("--out needs a path"); out = next; } else if (arg.startsWith("--out=")) { out = arg.slice("--out=".length); } else if (arg === "--force") { force = true; } else { die(`unknown argument ${arg}`); } } const outPath = resolve(out); if (existsSync(outPath) && !force) { die( `${outPath} already exists. Refusing to overwrite a private key -- ` + "move it aside first, or pass --force if you are certain it is disposable.", ); } const { publicKey, privateKey } = generateKeyPairSync("ed25519"); const pem = privateKey.export({ type: "pkcs8", format: "pem" }) as string; // mode 0600 on create; also set explicitly in case of a pre-existing file. writeFileSync(outPath, pem, { mode: 0o600 }); console.log(`private key -> ${outPath} (mode 0600, NOT printed, do not commit)`); console.log(""); console.log("public key (pin this in the host):"); console.log(""); console.log(` ${encodePublicKey(publicKey)}`); console.log(""); console.log("Next steps:"); console.log(" 1. Put the PEM contents in the INDEX_SIGNING_KEY repository secret."); console.log(" 2. Add the public key above to a host key slot (the host pins two,"); console.log(" so you can roll to a new key before retiring the old one)."); console.log(" 3. Destroy every copy of the PEM outside the secret store.");