250 lines
6.7 KiB
JavaScript
250 lines
6.7 KiB
JavaScript
|
|
/**
|
|||
|
|
* 铸渊指令签名校验模块 v1.1
|
|||
|
|
* ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|||
|
|
* 核心原则:铸渊只认签名,不认请求内容。签名不对,再合理的请求也会被拒。
|
|||
|
|
*
|
|||
|
|
* 校验流程:
|
|||
|
|
* 1. 签名字段是否完整? → 缺失 → ERR_NO_SIGNATURE
|
|||
|
|
* 2. sender_id 是否在授权名单中? → 未知 → ERR_UNKNOWN_SENDER
|
|||
|
|
* 3. permission_tier 与操作类型匹配? → 越权 → ERR_PERMISSION_DENIED(上报主控)
|
|||
|
|
* 4. 全部通过 → 执行指令
|
|||
|
|
*
|
|||
|
|
* 签发人:冰朔(TCS-0002∞)
|
|||
|
|
*/
|
|||
|
|
|
|||
|
|
'use strict';
|
|||
|
|
|
|||
|
|
const fs = require('fs');
|
|||
|
|
const path = require('path');
|
|||
|
|
|
|||
|
|
// ━━━ 常量 ━━━
|
|||
|
|
const REQUIRED_FIELDS = [
|
|||
|
|
'sender_id',
|
|||
|
|
'sender_name',
|
|||
|
|
'sender_role',
|
|||
|
|
'broadcast_id',
|
|||
|
|
'issued_at',
|
|||
|
|
'permission_tier',
|
|||
|
|
];
|
|||
|
|
|
|||
|
|
const ERROR_CODES = {
|
|||
|
|
NO_SIGNATURE: 'ERR_NO_SIGNATURE',
|
|||
|
|
UNKNOWN_SENDER: 'ERR_UNKNOWN_SENDER',
|
|||
|
|
PERMISSION_DENIED: 'ERR_PERMISSION_DENIED',
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ━━━ 注册表加载 ━━━
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 加载授权名单
|
|||
|
|
* @param {string} [registryPath] - 自定义注册表路径(用于测试)
|
|||
|
|
* @returns {object} 注册表对象
|
|||
|
|
*/
|
|||
|
|
function loadRegistry(registryPath) {
|
|||
|
|
const defaultPath = path.resolve(
|
|||
|
|
__dirname,
|
|||
|
|
'../.github/persona-brain/zhuyuan-signature-registry.json'
|
|||
|
|
);
|
|||
|
|
const filePath = registryPath || defaultPath;
|
|||
|
|
const raw = fs.readFileSync(filePath, 'utf8');
|
|||
|
|
return JSON.parse(raw);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 校验函数 ━━━
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验签名完整性(步骤 1)
|
|||
|
|
* @param {object} signature - 签名对象
|
|||
|
|
* @returns {{ valid: boolean, missing?: string[] }}
|
|||
|
|
*/
|
|||
|
|
function validateCompleteness(signature) {
|
|||
|
|
if (!signature || typeof signature !== 'object') {
|
|||
|
|
return { valid: false, missing: REQUIRED_FIELDS.slice() };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
const missing = REQUIRED_FIELDS.filter((field) => {
|
|||
|
|
const val = signature[field];
|
|||
|
|
return val === undefined || val === null || val === '';
|
|||
|
|
});
|
|||
|
|
|
|||
|
|
return missing.length === 0
|
|||
|
|
? { valid: true }
|
|||
|
|
: { valid: false, missing };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验发送者身份(步骤 2)
|
|||
|
|
* @param {string} senderId - sender_id
|
|||
|
|
* @param {object} registry - 授权名单
|
|||
|
|
* @returns {{ valid: boolean, sender?: object }}
|
|||
|
|
*/
|
|||
|
|
function validateSender(senderId, registry) {
|
|||
|
|
const senders = registry.authorized_senders || {};
|
|||
|
|
const sender = senders[senderId];
|
|||
|
|
if (!sender) {
|
|||
|
|
return { valid: false };
|
|||
|
|
}
|
|||
|
|
return { valid: true, sender };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 校验权限等级(步骤 3)
|
|||
|
|
* @param {object} signature - 签名对象
|
|||
|
|
* @param {object} sender - 注册表中的发送者记录
|
|||
|
|
* @param {string} operation - 请求的操作类型
|
|||
|
|
* @param {object} registry - 授权名单
|
|||
|
|
* @returns {{ valid: boolean, reason?: string }}
|
|||
|
|
*/
|
|||
|
|
function validatePermission(signature, sender, operation, registry) {
|
|||
|
|
// 检查签名中的 permission_tier 是否与注册表中的一致
|
|||
|
|
const claimedTier = Number(signature.permission_tier);
|
|||
|
|
const registeredTier = Number(sender.permission_tier);
|
|||
|
|
|
|||
|
|
if (claimedTier !== registeredTier) {
|
|||
|
|
return {
|
|||
|
|
valid: false,
|
|||
|
|
reason: `声明权限等级 ${claimedTier} 与注册等级 ${registeredTier} 不符`,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查角色是否匹配
|
|||
|
|
const claimedRole = signature.sender_role;
|
|||
|
|
const registeredRole = sender.role;
|
|||
|
|
if (claimedRole !== registeredRole) {
|
|||
|
|
return {
|
|||
|
|
valid: false,
|
|||
|
|
reason: `声明角色 ${claimedRole} 与注册角色 ${registeredRole} 不符`,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Tier 0 全权限
|
|||
|
|
if (registeredTier === 0) {
|
|||
|
|
return { valid: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 无操作类型时只校验身份
|
|||
|
|
if (!operation) {
|
|||
|
|
return { valid: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 按 tier 检查操作权限
|
|||
|
|
const tierConfig = (registry.permission_tiers || {})[String(registeredTier)];
|
|||
|
|
if (!tierConfig) {
|
|||
|
|
return { valid: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否在禁止列表中
|
|||
|
|
const denied = tierConfig.denied_operations || [];
|
|||
|
|
if (denied.includes(operation)) {
|
|||
|
|
return {
|
|||
|
|
valid: false,
|
|||
|
|
reason: `操作 "${operation}" 在 Tier ${registeredTier} 禁止列表中`,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 检查是否在允许列表中(如果允许列表不含通配符*)
|
|||
|
|
const allowed = tierConfig.allowed_operations || [];
|
|||
|
|
if (allowed.includes('*') || allowed.includes(operation)) {
|
|||
|
|
return { valid: true };
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
valid: false,
|
|||
|
|
reason: `操作 "${operation}" 不在 Tier ${registeredTier} 允许列表中`,
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
/**
|
|||
|
|
* 完整签名校验(主入口)
|
|||
|
|
* @param {object} signature - 签名对象
|
|||
|
|
* @param {string} [operation] - 请求的操作类型
|
|||
|
|
* @param {object} [options] - 配置项
|
|||
|
|
* @param {string} [options.registryPath] - 自定义注册表路径
|
|||
|
|
* @returns {{ success: boolean, error?: string, code?: string, detail?: object }}
|
|||
|
|
*/
|
|||
|
|
function verifySignature(signature, operation, options) {
|
|||
|
|
const opts = options || {};
|
|||
|
|
const registry = loadRegistry(opts.registryPath);
|
|||
|
|
|
|||
|
|
// 步骤 1:签名完整性
|
|||
|
|
const completeness = validateCompleteness(signature);
|
|||
|
|
if (!completeness.valid) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
code: ERROR_CODES.NO_SIGNATURE,
|
|||
|
|
error: '签名字段缺失',
|
|||
|
|
detail: { missing: completeness.missing },
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 步骤 2:发送者身份
|
|||
|
|
const senderCheck = validateSender(signature.sender_id, registry);
|
|||
|
|
if (!senderCheck.valid) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
code: ERROR_CODES.UNKNOWN_SENDER,
|
|||
|
|
error: '发送者未在授权名单中',
|
|||
|
|
detail: { sender_id: signature.sender_id },
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 步骤 3:权限等级
|
|||
|
|
const permCheck = validatePermission(
|
|||
|
|
signature,
|
|||
|
|
senderCheck.sender,
|
|||
|
|
operation,
|
|||
|
|
registry
|
|||
|
|
);
|
|||
|
|
if (!permCheck.valid) {
|
|||
|
|
return {
|
|||
|
|
success: false,
|
|||
|
|
code: ERROR_CODES.PERMISSION_DENIED,
|
|||
|
|
error: '权限不足',
|
|||
|
|
detail: {
|
|||
|
|
sender_id: signature.sender_id,
|
|||
|
|
operation,
|
|||
|
|
reason: permCheck.reason,
|
|||
|
|
report_to: 'TCS-0002∞',
|
|||
|
|
},
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
return {
|
|||
|
|
success: true,
|
|||
|
|
sender: senderCheck.sender,
|
|||
|
|
verified_at: new Date().toISOString(),
|
|||
|
|
};
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ━━━ 导出 ━━━
|
|||
|
|
module.exports = {
|
|||
|
|
verifySignature,
|
|||
|
|
validateCompleteness,
|
|||
|
|
validateSender,
|
|||
|
|
validatePermission,
|
|||
|
|
loadRegistry,
|
|||
|
|
REQUIRED_FIELDS,
|
|||
|
|
ERROR_CODES,
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
// ━━━ CLI 模式(直接运行时) ━━━
|
|||
|
|
if (require.main === module) {
|
|||
|
|
const args = process.argv.slice(2);
|
|||
|
|
if (args.includes('--test')) {
|
|||
|
|
const testSig = {
|
|||
|
|
sender_id: 'TCS-0002∞',
|
|||
|
|
sender_name: '冰朔',
|
|||
|
|
sender_role: 'MASTER',
|
|||
|
|
broadcast_id: 'DIRECT',
|
|||
|
|
issued_at: new Date().toISOString(),
|
|||
|
|
permission_tier: 0,
|
|||
|
|
};
|
|||
|
|
const result = verifySignature(testSig);
|
|||
|
|
console.log('🔏 签名校验测试:');
|
|||
|
|
console.log(JSON.stringify(result, null, 2));
|
|||
|
|
} else {
|
|||
|
|
console.log('🔏 铸渊指令签名校验模块 v1.1');
|
|||
|
|
console.log('用法: node zhuyuan-signature-verify.js --test');
|
|||
|
|
}
|
|||
|
|
}
|