bookworm-smart-assistant/scripts/patches/patch-p1-settings-hmac-sign.js

81 lines
2.6 KiB
JavaScript
Raw Normal View History

#!/usr/bin/env node
/**
* patch-p1-settings-hmac-sign.js
*
* P1.4: settings.json HMAC 签名 (settings.json.sig)
*
* 镜像 feature-flags.json.sig 模式 (64 字节 HMAC-SHA256)
* .hmac-key 作为密钥已在 P0.1 收紧 ACL
*
* 设计:
* - 不修改 settings.json 本身避免破坏现有结构
* - 仅在并行写出 settings.json.sig
* - 提供 verify-settings-sig.js 验证工具
*
* 后续: 启动期 hook 应读 .sig 比对发现篡改告警/拒载
*/
'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');
function main() {
if (!fs.existsSync(SETTINGS)) {
process.stderr.write('[ERROR] settings.json not found: ' + SETTINGS + '\n');
process.exit(1);
}
if (!fs.existsSync(HMAC_KEY)) {
process.stderr.write('[ERROR] .hmac-key not found. Run hmac-init first.\n');
process.exit(1);
}
const key = fs.readFileSync(HMAC_KEY, 'utf8').trim();
if (key.length < 32) {
process.stderr.write('[ERROR] .hmac-key too short (' + key.length + ' chars). Need >=32.\n');
process.exit(1);
}
const content = fs.readFileSync(SETTINGS);
const sig = crypto.createHmac('sha256', key).update(content).digest('hex');
// 幂等
if (fs.existsSync(SIG_FILE)) {
const old = fs.readFileSync(SIG_FILE, 'utf8').trim();
if (old === sig) {
process.stdout.write('[SKIP] signature unchanged\n');
process.exit(0);
}
const ts = new Date().toISOString().replace(/[:.]/g, '-');
fs.copyFileSync(SIG_FILE, SIG_FILE + '.bak.' + ts);
process.stdout.write('[BACKUP] ' + SIG_FILE + '.bak.' + ts + '\n');
}
// 原子写
const tmpPath = SIG_FILE + '.tmp.' + process.pid;
fs.writeFileSync(tmpPath, sig);
fs.renameSync(tmpPath, SIG_FILE);
// ACL 收紧(仅当前用户)
try {
const { execSync } = require('child_process');
if (process.platform === 'win32') {
const user = process.env.USERNAME;
execSync('icacls "' + SIG_FILE + '" /inheritance:r /grant:r "' + user + ':F"', { stdio: 'ignore' });
} else {
fs.chmodSync(SIG_FILE, 0o600);
}
} catch (_) { /* best-effort */ }
process.stdout.write('[OK] settings.json.sig written (sha256-hmac, ' + sig.length + ' chars)\n');
process.stdout.write(' ' + sig.slice(0, 16) + '...' + sig.slice(-16) + '\n');
process.stdout.write(' verify with: node scripts/patches/verify-settings-sig.js\n');
}
if (require.main === module) main();