Implement fail-closed node keystore bridge

This commit is contained in:
冰朔 2026-08-10 09:35:31 +08:00
commit b4d6e7a4f9
5 changed files with 345 additions and 5 deletions

View file

@ -22,6 +22,7 @@ import { DomainAccessOrchestrator } from '../../guanghu-knowledge-base/server/do
import { TrustedSignerSnapshotLoader } from '../../guanghu-knowledge-base/server/trusted-signer-snapshot.js';
import { NodeRegistrationSnapshotLoader } from '../../guanghu-knowledge-base/server/node-registration-snapshot.js';
import { HttpNodeRegistrationClaimSource } from '../../guanghu-knowledge-base/server/node-registration-client.js';
import { EncryptedNodeKeyStoreProofSource } from '../../guanghu-knowledge-base/server/node-keystore-bridge.js';
import { DomainRuntimeHandoffSnapshotLoader } from '../../guanghu-knowledge-base/server/domain-runtime-handoff-snapshot.js';
import {
AnchoredDomainRuntimeHandoffSource,
@ -40,6 +41,7 @@ const KB_REPO_PATH = path.join(DATA_DIR, 'knowledge-base');
const MODEL_CONFIG_PATH = path.join(app.getPath('userData'), 'model-config.json');
const SERVER_AUTH_PATH = path.join(app.getPath('userData'), 'server-auth.json');
const SERVER_PROFILES_PATH = path.join(app.getPath('userData'), 'server-profiles.json');
const NODE_KEYSTORE_PATH = path.join(app.getPath('userData'), 'node-keystore.json');
const AGENT_STATE_PATH = path.join(app.getPath('userData'), 'agent-conversations.json');
const GIT_ASKPASS_PATH = path.join(app.getPath('userData'), 'hololake-git-askpass.sh');
interface ServerProfileDefinition {
@ -366,6 +368,37 @@ const domainRuntimeHandoffs = new AnchoredDomainRuntimeHandoffSource(
new HttpDomainRuntimeHandoffTransport(),
);
const nodeKeyStoreProofs = new EncryptedNodeKeyStoreProofSource({
isAvailable() {
return safeStorage.isEncryptionAvailable();
},
async readRecord() {
try {
const stat = fs.statSync(NODE_KEYSTORE_PATH);
if (!stat.isFile() || (stat.mode & 0o077) !== 0) return null;
return JSON.parse(fs.readFileSync(NODE_KEYSTORE_PATH, 'utf8')) as unknown;
} catch {
return null;
}
},
async decryptPrivateKeyPkcs8(encrypted) {
try {
if (!safeStorage.isEncryptionAvailable()) return null;
const ciphertext = Buffer.from(encrypted, 'base64');
if (ciphertext.length === 0 || ciphertext.toString('base64') !== encrypted) return null;
const plaintextBase64 = safeStorage.decryptString(ciphertext);
const plaintext = Buffer.from(plaintextBase64, 'base64');
if (plaintext.length === 0 || plaintext.toString('base64') !== plaintextBase64) {
plaintext.fill(0);
return null;
}
return plaintext;
} catch {
return null;
}
},
});
const domainAccessOrchestrator = new DomainAccessOrchestrator(
trustedSignerSnapshots,
nodeRegistrationSnapshots,
@ -388,11 +421,7 @@ const domainAccessOrchestrator = new DomainAccessOrchestrator(
},
},
nodeRegistrationClaims,
{
// The production node-keystore bridge is not registered yet. Returning no
// proof keeps the domain gate closed even if a future signed claim appears.
async prove() { return null; },
},
nodeKeyStoreProofs,
domainRuntimeHandoffs,
);