From fa2c868e0a2cb0fd03900647d9a5e7ed4d8421ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=86=B0=E6=9C=94?= <565183519@qq.com> Date: Mon, 10 Aug 2026 02:07:25 +0800 Subject: [PATCH] feat(domain-access): verify manifest signatures --- .../server/domain-access.test.ts | 108 +++++++++++++----- .../server/domain-access.ts | 81 +++++++++++-- 2 files changed, 153 insertions(+), 36 deletions(-) diff --git a/product-source/guanghu-knowledge-base/server/domain-access.test.ts b/product-source/guanghu-knowledge-base/server/domain-access.test.ts index 3570455..b0e837a 100644 --- a/product-source/guanghu-knowledge-base/server/domain-access.test.ts +++ b/product-source/guanghu-knowledge-base/server/domain-access.test.ts @@ -1,10 +1,30 @@ import test from 'node:test'; import assert from 'node:assert/strict'; -import { evaluateDomainAccess, parseDomainAccessHandoff } from './domain-access.js'; +import { createHash, generateKeyPairSync, sign } from 'node:crypto'; +import { + domainManifestSigningBytes, + evaluateDomainAccess, + parseDomainAccessHandoff, +} from './domain-access.js'; const NOW = 1_786_291_200_000; -const DIGEST = 'a'.repeat(64); const COMMIT = 'b'.repeat(40); +const { privateKey, publicKey } = generateKeyPairSync('ed25519'); +const trustedSigner = { + algorithm: 'Ed25519', + publicKeyPem: publicKey.export({ format: 'pem', type: 'spki' }).toString(), + repositoryId: 'REPO-014', + signerId: 'GH-LIGHTHOUSE-001', +} as const; +const manifestPayload = { + domainId: 'DOM-FIFTH-0001', + repositoryId: 'REPO-014', + schema: 'gh-aios.domain-manifest/v1', + signerId: 'GH-LIGHTHOUSE-001', + sourceCommit: COMMIT, +} as const; +const signedBytes = domainManifestSigningBytes(manifestPayload); +const DIGEST = createHash('sha256').update(signedBytes).digest('hex'); const validHandoff = { connectionReceipt: { @@ -17,17 +37,8 @@ const validHandoff = { }, manifest: { digest: DIGEST, - domainId: 'DOM-FIFTH-0001', - schema: 'gh-aios.domain-manifest/v1', - signerId: 'GH-LIGHTHOUSE-001', - sourceCommit: COMMIT, - verifierReceipt: { - manifestDigest: DIGEST, - signerId: 'GH-LIGHTHOUSE-001', - state: 'verified', - verifiedAt: NOW - 1_000, - verifierId: 'GH-MANIFEST-VERIFIER-001', - }, + ...manifestPayload, + signature: sign(null, signedBytes, privateKey).toString('base64'), }, sessionCapability: { capabilityId: 'CAP-001', @@ -37,6 +48,13 @@ const validHandoff = { scopes: ['domain:enter'], }, } as const; +const verifiedHandoff = parseDomainAccessHandoff( + validHandoff, + 'DOM-FIFTH-0001', + 'LOCAL-001', + trustedSigner, + NOW, +); test('a local workspace remains available without claiming domain runtime access', () => { const status = evaluateDomainAccess({ accountVerified: false, domainId: 'DOM-FIFTH-0001', nodeId: '' }, NOW); @@ -56,7 +74,7 @@ test('a verified Forgejo account is not a domain runtime session', () => { }); test('runtime access requires matching manifest, scoped capability and online receipt', () => { - const handoff = parseDomainAccessHandoff(validHandoff, 'DOM-FIFTH-0001', 'LOCAL-001', NOW); + const handoff = parseDomainAccessHandoff(validHandoff, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW); const status = evaluateDomainAccess({ accountVerified: true, domainId: 'DOM-FIFTH-0001', @@ -73,7 +91,7 @@ test('mismatched or expired evidence fails closed', () => { accountVerified: true, connectionReceipt: { connectionId: 'CONN-001', domainId: 'DOMAIN-OTHER', manifestDigest: DIGEST, nodeId: 'LOCAL-001', receiptId: 'RECEIPT-001', state: 'online' }, domainId: 'DOM-FIFTH-0001', - manifest: validHandoff.manifest, + manifest: verifiedHandoff.manifest, nodeId: 'LOCAL-001', sessionCapability: { capabilityId: 'CAP-001', domainId: 'DOM-FIFTH-0001', expiresAt: NOW, nodeId: 'LOCAL-001', scopes: ['domain:enter'] }, }, NOW); @@ -82,18 +100,14 @@ test('mismatched or expired evidence fails closed', () => { assert.ok(status.blockers.includes('matching_connection_receipt_missing')); }); -test('a verified boolean cannot substitute for an external verifier receipt', () => { +test('the input cannot supply its own verifier receipt', () => { assert.throws(() => parseDomainAccessHandoff({ ...validHandoff, manifest: { - digest: DIGEST, - domainId: 'DOM-FIFTH-0001', - schema: 'gh-aios.domain-manifest/v1', - signerId: 'GH-LIGHTHOUSE-001', - sourceCommit: COMMIT, - verified: true, + ...validHandoff.manifest, + verifierReceipt: { state: 'verified' }, }, - }, 'DOM-FIFTH-0001', 'LOCAL-001', NOW), /domain_access_handoff_invalid/); + }, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW), /domain_access_handoff_invalid/); }); test('unknown fields and malformed digests fail closed', () => { @@ -101,13 +115,55 @@ test('unknown fields and malformed digests fail closed', () => { ...validHandoff, manifest: { ...validHandoff.manifest, digest: 'not-a-digest' }, trusted: true, - }, 'DOM-FIFTH-0001', 'LOCAL-001', NOW), /domain_access_handoff_invalid/); + }, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW), /domain_access_handoff_invalid/); }); test('the handoff is bound to the requested domain, node and manifest digest', () => { assert.throws(() => parseDomainAccessHandoff({ ...validHandoff, connectionReceipt: { ...validHandoff.connectionReceipt, manifestDigest: 'c'.repeat(64) }, - }, 'DOM-FIFTH-0001', 'LOCAL-001', NOW), /domain_access_handoff_invalid/); - assert.throws(() => parseDomainAccessHandoff(validHandoff, 'DOM-FIFTH-0001', 'OTHER-NODE', NOW), /domain_access_handoff_invalid/); + }, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW), /domain_access_handoff_invalid/); + assert.throws(() => parseDomainAccessHandoff(validHandoff, 'DOM-FIFTH-0001', 'OTHER-NODE', trustedSigner, NOW), /domain_access_handoff_invalid/); +}); + +test('manifest tampering and signatures from another key fail closed', () => { + assert.throws(() => parseDomainAccessHandoff({ + ...validHandoff, + manifest: { ...validHandoff.manifest, sourceCommit: 'c'.repeat(40) }, + }, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW), /domain_access_handoff_invalid/); + + const other = generateKeyPairSync('ed25519'); + assert.throws(() => parseDomainAccessHandoff({ + ...validHandoff, + manifest: { + ...validHandoff.manifest, + signature: sign(null, signedBytes, other.privateKey).toString('base64'), + }, + }, 'DOM-FIFTH-0001', 'LOCAL-001', trustedSigner, NOW), /domain_access_handoff_invalid/); +}); + +test('trusted signer identity and repository are external inputs, not payload authority', () => { + assert.throws(() => parseDomainAccessHandoff( + validHandoff, + 'DOM-FIFTH-0001', + 'LOCAL-001', + { ...trustedSigner, repositoryId: 'REPO-OTHER' }, + NOW, + ), /domain_access_handoff_invalid/); +}); + +test('copying verified-looking fields cannot bypass the in-process verifier boundary', () => { + const status = evaluateDomainAccess({ + accountVerified: true, + connectionReceipt: verifiedHandoff.connectionReceipt, + domainId: 'DOM-FIFTH-0001', + manifest: { + ...verifiedHandoff.manifest, + verifierReceipt: { ...verifiedHandoff.manifest.verifierReceipt }, + }, + nodeId: 'LOCAL-001', + sessionCapability: verifiedHandoff.sessionCapability, + }, NOW); + assert.equal(status.runtimeReady, false); + assert.ok(status.blockers.includes('verified_domain_manifest_missing')); }); diff --git a/product-source/guanghu-knowledge-base/server/domain-access.ts b/product-source/guanghu-knowledge-base/server/domain-access.ts index ccb5c63..dc8c19f 100644 --- a/product-source/guanghu-knowledge-base/server/domain-access.ts +++ b/product-source/guanghu-knowledge-base/server/domain-access.ts @@ -1,11 +1,16 @@ +import { createHash, verify as verifySignature } from 'node:crypto'; + const MANIFEST_SCHEMA = 'gh-aios.domain-manifest/v1' as const; const SHA256_PATTERN = /^[a-f0-9]{64}$/; const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/; +const cryptographicallyVerifiedManifests = new WeakSet(); export interface DomainManifestEvidence { digest: string; domainId: string; + repositoryId: string; schema: typeof MANIFEST_SCHEMA; + signature: string; signerId: string; sourceCommit: string; verifierReceipt: { @@ -17,6 +22,21 @@ export interface DomainManifestEvidence { }; } +export interface DomainManifestSignedPayload { + domainId: string; + repositoryId: string; + schema: typeof MANIFEST_SCHEMA; + signerId: string; + sourceCommit: string; +} + +export interface TrustedManifestSigner { + algorithm: 'Ed25519'; + publicKeyPem: string; + repositoryId: string; + signerId: string; +} + export interface DomainSessionCapability { capabilityId: string; domainId: string; @@ -75,6 +95,16 @@ function invalidHandoff(): never { throw new Error('domain_access_handoff_invalid'); } +export function domainManifestSigningBytes(payload: DomainManifestSignedPayload): Buffer { + return Buffer.from(JSON.stringify({ + domainId: payload.domainId, + repositoryId: payload.repositoryId, + schema: payload.schema, + signerId: payload.signerId, + sourceCommit: payload.sourceCommit, + }), 'utf8'); +} + /** * Validates the structure and binding of evidence returned by a trusted route. * Cryptographic signature verification is deliberately external: this boundary @@ -84,30 +114,45 @@ export function parseDomainAccessHandoff( input: unknown, expectedDomainId: string, expectedNodeId: string, + trustedSigner: TrustedManifestSigner, now = Date.now(), ): DomainAccessHandoff { if (!isRecord(input) || !hasExactKeys(input, ['connectionReceipt', 'manifest', 'sessionCapability'])) invalidHandoff(); const manifest = input.manifest; const capability = input.sessionCapability; const connection = input.connectionReceipt; - if (!isRecord(manifest) || !hasExactKeys(manifest, ['digest', 'domainId', 'schema', 'signerId', 'sourceCommit', 'verifierReceipt'])) invalidHandoff(); + if (!isRecord(manifest) || !hasExactKeys(manifest, ['digest', 'domainId', 'repositoryId', 'schema', 'signature', 'signerId', 'sourceCommit'])) invalidHandoff(); if (!isRecord(capability) || !hasExactKeys(capability, ['capabilityId', 'domainId', 'expiresAt', 'nodeId', 'scopes'])) invalidHandoff(); if (!isRecord(connection) || !hasExactKeys(connection, ['connectionId', 'domainId', 'manifestDigest', 'nodeId', 'receiptId', 'state'])) invalidHandoff(); - const verifier = manifest.verifierReceipt; - if (!isRecord(verifier) || !hasExactKeys(verifier, ['manifestDigest', 'signerId', 'state', 'verifiedAt', 'verifierId'])) invalidHandoff(); - const digest = manifest.digest; if (manifest.schema !== MANIFEST_SCHEMA || typeof digest !== 'string' || !SHA256_PATTERN.test(digest) || manifest.domainId !== expectedDomainId + || manifest.repositoryId !== trustedSigner.repositoryId + || manifest.signerId !== trustedSigner.signerId + || trustedSigner.algorithm !== 'Ed25519' + || !isIdentifier(trustedSigner.publicKeyPem) || !isIdentifier(manifest.signerId) || typeof manifest.sourceCommit !== 'string' || !COMMIT_PATTERN.test(manifest.sourceCommit) - || verifier.state !== 'verified' - || verifier.manifestDigest !== digest - || verifier.signerId !== manifest.signerId - || !isIdentifier(verifier.verifierId) - || typeof verifier.verifiedAt !== 'number' || !Number.isSafeInteger(verifier.verifiedAt) || verifier.verifiedAt > now) invalidHandoff(); + || typeof manifest.signature !== 'string') invalidHandoff(); + + const signedPayload = domainManifestSigningBytes({ + domainId: manifest.domainId, + repositoryId: manifest.repositoryId, + schema: manifest.schema, + signerId: manifest.signerId, + sourceCommit: manifest.sourceCommit, + }); + if (createHash('sha256').update(signedPayload).digest('hex') !== digest) invalidHandoff(); + let signature: Buffer; + try { + signature = Buffer.from(manifest.signature, 'base64'); + if (signature.length !== 64 || signature.toString('base64') !== manifest.signature) invalidHandoff(); + if (!verifySignature(null, signedPayload, trustedSigner.publicKeyPem, signature)) invalidHandoff(); + } catch { + invalidHandoff(); + } if (!isIdentifier(capability.capabilityId) || capability.domainId !== expectedDomainId @@ -124,7 +169,22 @@ export function parseDomainAccessHandoff( || connection.manifestDigest !== digest || connection.state !== 'online') invalidHandoff(); - return input as unknown as DomainAccessHandoff; + const handoff: DomainAccessHandoff = { + connectionReceipt: connection as unknown as DomainConnectionReceipt, + manifest: { + ...(manifest as unknown as Omit), + verifierReceipt: { + manifestDigest: digest, + signerId: manifest.signerId, + state: 'verified', + verifiedAt: now, + verifierId: 'HOLOLAKE-DESKTOP-ED25519', + }, + }, + sessionCapability: capability as unknown as DomainSessionCapability, + }; + cryptographicallyVerifiedManifests.add(handoff.manifest); + return handoff; } export function evaluateDomainAccess(evidence: DomainAccessEvidence, now = Date.now()): DomainAccessStatus { @@ -133,6 +193,7 @@ export function evaluateDomainAccess(evidence: DomainAccessEvidence, now = Date. const manifest = evidence.manifest; const verifier = manifest?.verifierReceipt; if (!manifest + || !cryptographicallyVerifiedManifests.has(manifest) || manifest.schema !== MANIFEST_SCHEMA || manifest.domainId !== evidence.domainId || !SHA256_PATTERN.test(manifest.digest)