#!/usr/bin/env node /** * verify-settings-sig.js — 验证 settings.json HMAC 签名是否一致 * * 用法: * node scripts/patches/verify-settings-sig.js # 退出码 0=ok / 1=mismatch * node scripts/patches/verify-settings-sig.js --quiet # 仅退出码 */ 'use strict'; const fs = require('fs'); const path = require('path'); const crypto = require('crypto'); const ROOT = path.join(__dirname, '..', '..'); const SETTINGS = path.join(ROOT, 'settings.json'); const SIG_FILE = path.join(ROOT, 'settings.json.sig'); const HMAC_KEY = path.join(ROOT, '.hmac-key'); const QUIET = process.argv.includes('--quiet'); function log(msg) { if (!QUIET) process.stdout.write(msg + '\n'); } function err(msg) { process.stderr.write(msg + '\n'); } if (!fs.existsSync(SIG_FILE)) { err('[FAIL] signature file missing: ' + SIG_FILE); process.exit(1); } if (!fs.existsSync(HMAC_KEY)) { err('[FAIL] .hmac-key missing'); process.exit(1); } const expected = fs.readFileSync(SIG_FILE, 'utf8').trim(); const key = fs.readFileSync(HMAC_KEY, 'utf8').trim(); const content = fs.readFileSync(SETTINGS); const actual = crypto.createHmac('sha256', key).update(content).digest('hex'); // timing-safe 比较 const ok = expected.length === actual.length && crypto.timingSafeEqual(Buffer.from(expected, 'hex'), Buffer.from(actual, 'hex')); if (ok) { log('[OK] settings.json signature valid'); process.exit(0); } else { err('[FAIL] settings.json TAMPERED!'); err(' expected: ' + expected.slice(0, 16) + '...' + expected.slice(-16)); err(' actual: ' + actual.slice(0, 16) + '...' + actual.slice(-16)); process.exit(1); }