Files
cache-server/dev-registry/src/signing.js
T
oxmc 685c14ba97 Initial commit: local network content cache for BgUpd component downloads
Node.js daemon: Ed25519 identity + self-signed TLS, signed registration/
renewal with the central registry, mDNS advertising, disk-backed
fetch-through asset cache with an origin allowlist. Includes a local
dev-registry stand-in for the real oxmc.me endpoints and an end-to-end
smoke test (register -> token -> cache miss/hit -> SSRF rejection).

See README.md for the full protocol and trust model.
2026-07-25 00:06:20 -07:00

56 lines
2.1 KiB
JavaScript

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 };