feat: panel-hosted Reality keys (keyStore, payload keyMaterial, backfill, cleanup)
This commit is contained in:
+46
-1
@@ -315,6 +315,51 @@ function buildClientUri({ protocol, relayServer, relayPort, credentials, reality
|
||||
return null;
|
||||
}
|
||||
|
||||
// ---------- Panel-hosted Reality keys ----------
|
||||
// Reality keypairs live in the panel database (data.keyStore) so that a node
|
||||
// OS reinstall redeploys the SAME pair and client subscriptions never change.
|
||||
// Encoding is exactly the agent-side format: Node JWK export of an x25519
|
||||
// pair, whose base64url no-padding `d`/`x` values match
|
||||
// `sing-box generate reality-keypair` output byte for byte.
|
||||
|
||||
function generateRealityKeyPair() {
|
||||
const kp = crypto.generateKeyPairSync('x25519');
|
||||
const priv = kp.privateKey.export({ format: 'jwk' });
|
||||
const pub = kp.publicKey.export({ format: 'jwk' });
|
||||
if (!priv.d || !pub.x) throw new Error('X25519 密钥生成失败');
|
||||
return { privateKey: priv.d, publicKey: pub.x };
|
||||
}
|
||||
|
||||
function realityKeyIdForNode(node) {
|
||||
return (node.reality && node.reality.keyId) || `reality-${node.id}`;
|
||||
}
|
||||
|
||||
// Returns the stored keypair for a Reality node, generating and storing one
|
||||
// on first use. Non-Reality nodes return null.
|
||||
function ensureRealityKey(data, node) {
|
||||
if (!isRealityProtocol(node.protocol)) return null;
|
||||
ensureNodeSecrets(node);
|
||||
const keyId = realityKeyIdForNode(node);
|
||||
data.keyStore ||= {};
|
||||
let entry = data.keyStore[keyId];
|
||||
if (!entry || !entry.privateKey || !entry.publicKey) {
|
||||
entry = { ...generateRealityKeyPair(), updatedAt: new Date().toISOString() };
|
||||
data.keyStore[keyId] = entry;
|
||||
}
|
||||
return entry;
|
||||
}
|
||||
|
||||
// Payload fragment for apply_resource jobs: { "<keyId>": { privateKey, publicKey } }.
|
||||
function keyMaterialForNode(data, node) {
|
||||
const entry = ensureRealityKey(data, node);
|
||||
if (!entry) return null;
|
||||
return { [realityKeyIdForNode(node)]: { privateKey: entry.privateKey, publicKey: entry.publicKey } };
|
||||
}
|
||||
|
||||
function dropRealityKey(data, node) {
|
||||
if (data.keyStore) delete data.keyStore[realityKeyIdForNode(node)];
|
||||
}
|
||||
|
||||
function profile(role, protocol) { return PROFILE_CATALOG.find((item) => item.role === role && item.id === protocol); }
|
||||
function validateEntryProtocol(protocol) {
|
||||
const item = profile('relay-ingress', protocol);
|
||||
@@ -330,5 +375,5 @@ function isRealityProtocol(protocol) { return ['vless-reality-vision', 'vless-re
|
||||
module.exports = {
|
||||
PROFILE_CATALOG, REALITY_PRESETS, SHADOWSOCKS_METHODS, nodeEmail, newNodeClientCredentials,
|
||||
buildNodeResource, buildNodeExitResource, ensureNodeSecrets, buildClientUri, validateEntryProtocol, validateProtocolPair, isRealityProtocol,
|
||||
resolveBindAddress
|
||||
resolveBindAddress, generateRealityKeyPair, realityKeyIdForNode, ensureRealityKey, keyMaterialForNode, dropRealityKey
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user