// Copyright (C) 2026 Rootiest // SPDX-License-Identifier: AGPL-3.0-or-later import { createDecipheriv, randomBytes } from "node:crypto"; import { mkdirSync, readFileSync, writeFileSync } from "node:fs"; import { homedir } from "node:os"; import { dirname, join } from "node:path"; // Cryptographic self-contained tokens: the placeholder carries its own // ciphertext, so the proxy is stateless. The only persistent state is the // master key, kept in a 0600 file so tokens survive restarts. const KEY_PATH = process.env.GG_MASTER_KEY_FILE || join(homedir(), ".config", "claude-tokenization-proxy", "master.key"); // AES-256-GCM layout inside the hex blob: 12-byte IV, 16-byte auth tag, then // the ciphertext. detokenize slices on these fixed boundaries. export const IV_BYTES = 12; export const TAG_BYTES = 16; const IV_HEX = IV_BYTES * 2; const TAG_HEX = TAG_BYTES * 2; let masterKey: Buffer | undefined; /** * Load the 256-bit master key from disk, generating and persisting one (0600) * on first run. Call once at startup before serving traffic. Idempotent. */ export function initMasterKey(): Buffer { if (masterKey) return masterKey; try { const key = readFileSync(KEY_PATH); if (key.length !== 32) { throw new Error(`master key at ${KEY_PATH} is ${key.length} bytes, expected 32`); } masterKey = key; } catch (err) { if ((err as NodeJS.ErrnoException).code !== "ENOENT") throw err; const key = randomBytes(32); mkdirSync(dirname(KEY_PATH), { recursive: true, mode: 0o700 }); writeFileSync(KEY_PATH, key, { mode: 0o600 }); console.warn(`[proxy] generated new master key at ${KEY_PATH}`); masterKey = key; } return masterKey; } export function getMasterKey(): Buffer { if (!masterKey) throw new Error("master key not initialized — call initMasterKey() first"); return masterKey; } /** * Decrypt a full placeholder token back to its secret. Returns undefined for * anything that isn't one of our tokens or that fails authentication, so * unknown/corrupt placeholders pass through the stream untouched. */ export function resolveToken(token: string): string | undefined { const m = /^<<>>$/.exec(token); if (!m) return undefined; const blob = m[1]; if (blob.length <= IV_HEX + TAG_HEX) return undefined; try { const iv = Buffer.from(blob.slice(0, IV_HEX), "hex"); const tag = Buffer.from(blob.slice(IV_HEX, IV_HEX + TAG_HEX), "hex"); const ciphertext = Buffer.from(blob.slice(IV_HEX + TAG_HEX), "hex"); const decipher = createDecipheriv("aes-256-gcm", getMasterKey(), iv); decipher.setAuthTag(tag); return Buffer.concat([decipher.update(ciphertext), decipher.final()]).toString("utf8"); } catch { return undefined; } }