Files
enricobuehlerandClaude Fable 5 05e772fedd
publish / publish (push) Successful in 26s
chore(index): repin to the signing key held in this repo's secret
The previous key was pasted into unom/punktfunk's secret store rather than
this one. Gitea secrets don't cross repos and can't be read back, so that
key was unusable here and is abandoned; INDEX_SIGNING_KEY on THIS repo now
holds the private half of the key pinned below.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 23:05:39 +02:00

60 lines
2.1 KiB
TypeScript

/**
* Shared ed25519 key helpers.
*
* The host pins public keys in the exact string form `ed25519:<base64>`, where
* the base64 payload is the RAW 32-byte ed25519 public key -- NOT a DER/SPKI
* blob and NOT a PEM. node:crypto only exports SPKI, so we slice the raw key
* out of it (and splice it back in on the way in).
*
* An ed25519 SPKI DER is always exactly 44 bytes:
* 30 2a 30 05 06 03 2b 65 70 03 21 00 || <32-byte raw key>
* so the prefix is a fixed 12-byte constant.
*/
import { createPublicKey, type KeyObject } from "node:crypto";
/** Fixed 12-byte DER prefix of an ed25519 SubjectPublicKeyInfo. */
const SPKI_PREFIX = Buffer.from("302a300506032b6570032100", "hex");
export const PUBKEY_PREFIX = "ed25519:";
/**
* The public key currently compiled into the Punktfunk host (slot 1).
* `verify` defaults to this so a signed index can be checked with no arguments.
*/
export const DEFAULT_PUBLIC_KEY =
"ed25519:V7KKMg8sq2A2TW7D/GFWaM0ruAvigpld9r93JdWcQHw=";
/** Encode a node KeyObject public key as the host's `ed25519:<base64>` form. */
export function encodePublicKey(key: KeyObject): string {
const spki = key.export({ type: "spki", format: "der" });
if (spki.length !== 44 || !spki.subarray(0, 12).equals(SPKI_PREFIX)) {
throw new Error(
`not an ed25519 public key (unexpected SPKI: ${spki.length} bytes)`,
);
}
return PUBKEY_PREFIX + spki.subarray(12).toString("base64");
}
/** Parse the host's `ed25519:<base64>` form into a usable KeyObject. */
export function decodePublicKey(text: string): KeyObject {
const trimmed = text.trim();
if (!trimmed.startsWith(PUBKEY_PREFIX)) {
throw new Error(`public key must start with "${PUBKEY_PREFIX}"`);
}
const b64 = trimmed.slice(PUBKEY_PREFIX.length);
if (!/^[A-Za-z0-9+/]{43}=$/.test(b64)) {
throw new Error(
"public key payload must be base64 of exactly 32 raw bytes",
);
}
const raw = Buffer.from(b64, "base64");
if (raw.length !== 32) {
throw new Error(`public key must be 32 raw bytes, got ${raw.length}`);
}
return createPublicKey({
key: Buffer.concat([SPKI_PREFIX, raw]),
format: "der",
type: "spki",
});
}