bookworm-smart-assistant/scripts/patches/patch-p1-settings-hmac-sign.js
Bookworm Admin b7a8e29d21 release: v6.7.0 - OTA E2E test release
- VERSION file as authoritative version source
- export.mjs reads VERSION with package.json fallback
- bw-ota.ps1 DryRun mode for safe testing
- auto-setup.ps1 bumped to v3.2.0 (Phase 8 OTA)
2026-04-27 17:59:44 +08:00

81 lines
2.6 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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();