publish / publish (push) Successful in 26s
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>
60 lines
2.1 KiB
TypeScript
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",
|
|
});
|
|
}
|