const { generateKeyPairSync, sign: edSign, createPrivateKey } = require("node:crypto"); const { mkdirSync, existsSync, readFileSync, writeFileSync } = require("node:fs"); const { join } = require("node:path"); /** * The dev registry's own root Ed25519 identity — stands in for the real * oxmc.me signing key. Generated once, persisted, printed at startup so you * can paste the public half into Constants.CACHE_TRUST_ROOT_PUBKEY_BASE64 * for local device/emulator testing. The private half never leaves this * process — same shape as the real deployment, just self-issued. */ function loadOrCreateRootKey(dir) { mkdirSync(dir, { recursive: true, mode: 0o700 }); const pubPath = join(dir, "root_ed25519.pub.pem"); const privPath = join(dir, "root_ed25519.priv.pem"); if (existsSync(pubPath) && existsSync(privPath)) { return { publicKeyPem: readFileSync(pubPath, "utf8"), privateKeyPem: readFileSync(privPath, "utf8"), }; } const { publicKey, privateKey } = generateKeyPairSync("ed25519", { publicKeyEncoding: { type: "spki", format: "pem" }, privateKeyEncoding: { type: "pkcs8", format: "pem" }, }); writeFileSync(pubPath, publicKey, { mode: 0o644 }); writeFileSync(privPath, privateKey, { mode: 0o600 }); return { publicKeyPem: publicKey, privateKeyPem: privateKey }; } /** Deterministic key order — must match CacheTokenVerifier.kt's canonicalize(). */ function canonicalize(obj) { return JSON.stringify(obj, Object.keys(obj).sort()); } /** * Signs a CacheToken payload and wraps it in the wire envelope: * base64( JSON.stringify({ payload, signature }) ). */ function signCacheToken(payload, rootPrivateKeyPem) { const signature = edSign(null, Buffer.from(canonicalize(payload)), createPrivateKey(rootPrivateKeyPem)); const envelope = { payload, signature: signature.toString("base64") }; return Buffer.from(JSON.stringify(envelope), "utf8").toString("base64"); } function pemToBase64Spki(pem) { return pem .replace(/-----BEGIN PUBLIC KEY-----/, "") .replace(/-----END PUBLIC KEY-----/, "") .replace(/\s+/g, ""); } module.exports = { loadOrCreateRootKey, signCacheToken, canonicalize, pemToBase64Spki };