hololake-system-architecture/product-source/guanghu-knowledge-base/server/node-possession-proof.test.ts

132 lines
5.5 KiB
TypeScript

import test from 'node:test';
import assert from 'node:assert/strict';
import { createHash, generateKeyPairSync, sign } from 'node:crypto';
import {
createNodePossessionChallenge,
nodePossessionChallengeSigningBytes,
verifyNodePossessionResponse,
} from './node-possession-proof.js';
import {
nodeRegistrationClaimSigningBytes,
parseNodeRegistrationEndpointRegistry,
resolveNodeRegistrationEndpoint,
verifyNodeRegistrationClaim,
} from './node-registration.js';
const NOW = 1_786_291_200_000;
const DOMAIN_ID = 'DOM-FIFTH-0001';
const NODE_ID = 'LOCAL-001';
const registrationKeys = generateKeyPairSync('ed25519');
const nodeKeys = generateKeyPairSync('ed25519');
const nodePublicKeyPem = nodeKeys.publicKey.export({ format: 'pem', type: 'spki' }).toString();
const fingerprint = createHash('sha256')
.update(nodeKeys.publicKey.export({ format: 'der', type: 'spki' }))
.digest('hex');
function verifiedRegistration() {
const registry = parseNodeRegistrationEndpointRegistry({
endpoints: [{
algorithm: 'Ed25519',
domainIds: [DOMAIN_ID],
endpointId: 'GH-NODE-REG-001',
nodeTypes: ['local-terminal'],
publicKeyPem: registrationKeys.publicKey.export({ format: 'pem', type: 'spki' }).toString(),
signerId: 'GH-NODE-REG-SIGNER-001',
status: 'ACTIVE',
url: 'https://guanghulab.com/api/ai/v1/node-registrations/claims',
}],
registryId: 'GH-AIOS-NODE-REGISTRATION-ENDPOINTS-001',
schema: 'gh-aios.node-registration-endpoints/v1',
state: 'CURRENT',
version: '1.0.0',
}, {
repositoryId: 'REPO-012',
sourceCommit: 'a'.repeat(40),
sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${'a'.repeat(40)}/routing/node-registration-endpoints.json`,
});
const endpoint = resolveNodeRegistrationEndpoint(registry, {
domainId: DOMAIN_ID,
nodeType: 'local-terminal',
});
assert.ok(endpoint);
const payload = {
accountId: 'bingshuo',
claimId: 'NODE-CLAIM-001',
domainId: DOMAIN_ID,
endpointId: endpoint.endpointId,
expiresAt: NOW + 120_000,
issuedAt: NOW - 1_000,
nodeId: NODE_ID,
nodeKeyFingerprint: fingerprint,
nodeType: 'local-terminal' as const,
schema: 'gh-aios.node-registration-claim/v1' as const,
signerId: endpoint.signerId,
};
return verifyNodeRegistrationClaim({
...payload,
signature: sign(null, nodeRegistrationClaimSigningBytes(payload), registrationKeys.privateKey).toString('base64'),
}, {
accountId: payload.accountId,
domainId: DOMAIN_ID,
nodeId: NODE_ID,
nodeType: 'local-terminal',
}, endpoint, NOW);
}
function responseFor(challenge: ReturnType<typeof createNodePossessionChallenge>) {
return {
challengeId: challenge.challengeId,
publicKeyPem: nodePublicKeyPem,
schema: 'gh-aios.node-possession-response/v1',
signature: sign(null, nodePossessionChallengeSigningBytes(challenge), nodeKeys.privateKey).toString('base64'),
};
}
test('verifies a fresh proof from the key fingerprint bound into the signed registration', () => {
const registration = verifiedRegistration();
const challenge = createNodePossessionChallenge(
registration,
NOW,
'NODE-CHALLENGE-001',
'A'.repeat(43),
);
const response = responseFor(challenge);
assert.equal(verifyNodePossessionResponse(response, challenge, registration, NOW + 1), true);
assert.throws(() => verifyNodePossessionResponse(response, challenge, registration, NOW + 2), /proof_invalid/);
});
test('rejects a forged challenge that was not issued by the verifier', () => {
const registration = verifiedRegistration();
const challenge = {
...createNodePossessionChallenge(registration, NOW, 'NODE-CHALLENGE-001', 'A'.repeat(43)),
nonce: 'B'.repeat(43),
};
assert.throws(() => verifyNodePossessionResponse(responseFor(challenge), challenge, registration, NOW + 1), /proof_invalid/);
});
test('rejects a response signed by a different node key', () => {
const registration = verifiedRegistration();
const challenge = createNodePossessionChallenge(registration, NOW, 'NODE-CHALLENGE-001', 'A'.repeat(43));
const otherKeys = generateKeyPairSync('ed25519');
const response = {
challengeId: challenge.challengeId,
publicKeyPem: otherKeys.publicKey.export({ format: 'pem', type: 'spki' }).toString(),
schema: 'gh-aios.node-possession-response/v1',
signature: sign(null, nodePossessionChallengeSigningBytes(challenge), otherKeys.privateKey).toString('base64'),
};
assert.throws(() => verifyNodePossessionResponse(response, challenge, registration, NOW + 1), /proof_invalid/);
});
test('rejects expired, replayed-to-another-challenge and unknown-field responses', () => {
const registration = verifiedRegistration();
const first = createNodePossessionChallenge(registration, NOW, 'NODE-CHALLENGE-001', 'A'.repeat(43));
const second = createNodePossessionChallenge(registration, NOW, 'NODE-CHALLENGE-002', 'B'.repeat(43));
assert.throws(() => verifyNodePossessionResponse(responseFor(first), second, registration, NOW + 1), /proof_invalid/);
assert.throws(() => verifyNodePossessionResponse({ ...responseFor(first), extra: true }, first, registration, NOW + 1), /proof_invalid/);
assert.throws(() => verifyNodePossessionResponse(responseFor(first), first, registration, NOW + 60_000), /proof_invalid/);
});
test('does not accept a structurally copied registration as verified provenance', () => {
const copied = { ...verifiedRegistration() };
assert.throws(() => createNodePossessionChallenge(copied, NOW, 'NODE-CHALLENGE-001', 'A'.repeat(43)), /proof_invalid/);
});