feat: define signed persona authorization trust contract

This commit is contained in:
冰朔 2026-08-12 05:09:56 +08:00
commit c4504fa115
13 changed files with 470 additions and 14 deletions

View file

@ -0,0 +1,121 @@
import assert from 'node:assert/strict';
import { generateKeyPairSync, sign } from 'node:crypto';
import test from 'node:test';
import {
PERSONA_CONTROL_AUTHORIZATION_SCHEMA,
personaControlAuthorizationSigningBytes,
parsePersonaControlAuthorizationSignerRegistry,
verifyPersonaControlAuthorization,
} from './persona-control-authorization.js';
const COMMIT = 'd'.repeat(40);
const source = {
repositoryId: 'REPO-012',
sourceCommit: COMMIT,
sourcePath: 'routing/persona-control-authorization-signers.json',
sourceUrl: `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${COMMIT}/routing/persona-control-authorization-signers.json`,
} as const;
const keys = generateKeyPairSync('ed25519');
const publicKeyPem = keys.publicKey.export({ format: 'pem', type: 'spki' }).toString();
function registry(status: 'ACTIVE' | 'REVOKED' = 'ACTIVE') {
return {
registryId: 'GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001',
schema: 'gh-aios.persona-control-authorization-signers/v1',
signers: [{
algorithm: 'Ed25519',
humanResponsibilitySubjects: ['BINGSHUO'],
personaIds: ['ICE-P-ZY001'],
publicKeyPem,
scopes: ['PERSONA_PRIMARY_LANGUAGE_PLANNING'],
signerId: 'GH-AIOS-AUTHORIZER-001',
status,
}],
state: 'CURRENT',
};
}
const unsigned = {
schema: PERSONA_CONTROL_AUTHORIZATION_SCHEMA,
outcome: 'VERIFIED' as const,
authorizationId: 'AUTH-001',
verifier: 'GUANGHU_OS' as const,
scope: 'PERSONA_PRIMARY_LANGUAGE_PLANNING' as const,
personaId: 'ICE-P-ZY001',
humanResponsibilitySubject: 'BINGSHUO',
repositoryHead: 'a'.repeat(40),
modelInstanceId: 'MODEL-INSTANCE-001',
requestId: 'REQ-001',
sourceLanguageAnchor: 'HLP-CURRENT-ARCH-001@2026-08-12.12',
issuedAt: '2026-08-12T05:00:00+08:00',
validUntil: '2026-08-12T05:10:00+08:00',
evidenceDigest: 'b'.repeat(64),
signerId: 'GH-AIOS-AUTHORIZER-001',
signatureAlgorithm: 'Ed25519' as const,
};
function receipt(overrides: Record<string, unknown> = {}) {
const candidate = { ...unsigned, ...overrides };
return {
...candidate,
signature: sign(null, personaControlAuthorizationSigningBytes(candidate), keys.privateKey).toString('base64url'),
};
}
const expected = {
personaId: unsigned.personaId,
humanResponsibilitySubject: unsigned.humanResponsibilitySubject,
repositoryHead: unsigned.repositoryHead,
modelInstanceId: unsigned.modelInstanceId,
requestId: unsigned.requestId,
sourceLanguageAnchor: unsigned.sourceLanguageAnchor,
observedAt: Date.parse('2026-08-12T05:05:00+08:00'),
};
test('an empty published registry trusts no signer', () => {
const parsed = parsePersonaControlAuthorizationSignerRegistry({ ...registry(), signers: [] }, source);
assert.equal(verifyPersonaControlAuthorization(receipt(), parsed, expected), null);
});
test('an exact current Ed25519 receipt verifies against its scoped registered signer', () => {
const parsed = parsePersonaControlAuthorizationSignerRegistry(registry(), source);
const verified = verifyPersonaControlAuthorization(receipt(), parsed, expected);
assert.equal(verified?.signerId, unsigned.signerId);
assert.equal(verified?.sourceCommit, COMMIT);
});
test('tampering any signed request binding fails closed', () => {
const parsed = parsePersonaControlAuthorizationSignerRegistry(registry(), source);
const signed = receipt();
assert.equal(verifyPersonaControlAuthorization({ ...signed, requestId: 'REQ-OTHER' }, parsed, { ...expected, requestId: 'REQ-OTHER' }), null);
});
test('revoked, wrong-scope and wrong-subject signers never authorize', () => {
const revoked = parsePersonaControlAuthorizationSignerRegistry(registry('REVOKED'), source);
assert.equal(verifyPersonaControlAuthorization(receipt(), revoked, expected), null);
const wrongSubjectInput = registry();
wrongSubjectInput.signers[0].humanResponsibilitySubjects = ['OTHER'];
const wrongSubject = parsePersonaControlAuthorizationSignerRegistry(wrongSubjectInput, source);
assert.equal(verifyPersonaControlAuthorization(receipt(), wrongSubject, expected), null);
});
test('stale, future and mismatched request evidence fail closed', () => {
const parsed = parsePersonaControlAuthorizationSignerRegistry(registry(), source);
assert.equal(verifyPersonaControlAuthorization(receipt(), parsed, { ...expected, observedAt: Date.parse('2026-08-12T05:10:01+08:00') }), null);
assert.equal(verifyPersonaControlAuthorization(receipt(), parsed, { ...expected, observedAt: Date.parse('2026-08-12T04:59:59+08:00') }), null);
assert.equal(verifyPersonaControlAuthorization(receipt(), parsed, { ...expected, repositoryHead: 'c'.repeat(40) }), null);
});
test('registry provenance is pinned to the exact REPO-012 commit path', () => {
assert.throws(() => parsePersonaControlAuthorizationSignerRegistry(registry(), { ...source, sourceCommit: 'short' }), /source_invalid/);
assert.throws(() => parsePersonaControlAuthorizationSignerRegistry(registry(), { ...source, sourceUrl: 'https://example.com/registry.json' }), /source_invalid/);
});
test('duplicate signers, private keys and unknown fields fail closed', () => {
assert.throws(() => parsePersonaControlAuthorizationSignerRegistry({ ...registry(), signers: [...registry().signers, ...registry().signers] }, source), /registry_invalid/);
const privateKeyPem = keys.privateKey.export({ format: 'pem', type: 'pkcs8' }).toString();
const privateRegistry = registry();
privateRegistry.signers[0].publicKeyPem = privateKeyPem;
assert.throws(() => parsePersonaControlAuthorizationSignerRegistry(privateRegistry, source), /registry_invalid/);
assert.throws(() => parsePersonaControlAuthorizationSignerRegistry({ ...registry(), extra: true }, source), /registry_invalid/);
});

View file

@ -0,0 +1,294 @@
import { createPublicKey, verify as verifySignature } from 'node:crypto';
export const PERSONA_CONTROL_AUTHORIZATION_SCHEMA = 'hololake.persona-control-authorization/v2' as const;
export const PERSONA_CONTROL_AUTHORIZATION_SIGNER_REGISTRY_SCHEMA = 'gh-aios.persona-control-authorization-signers/v1' as const;
export const PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE = 'PERSONA_PRIMARY_LANGUAGE_PLANNING' as const;
const SIGNING_CONTEXT = 'hololake.persona-control-authorization/signing/v1';
const REGISTRY_PATH = 'routing/persona-control-authorization-signers.json';
const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
const DIGEST_PATTERN = /^[a-f0-9]{64}$/;
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:@-]{1,159}$/;
const SIGNATURE_PATTERN = /^[A-Za-z0-9_-]{80,128}$/;
export interface PersonaControlAuthorizationReceipt {
schema: typeof PERSONA_CONTROL_AUTHORIZATION_SCHEMA;
outcome: 'VERIFIED';
authorizationId: string;
verifier: 'GUANGHU_OS';
scope: typeof PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE;
personaId: string;
humanResponsibilitySubject: string;
repositoryHead: string;
modelInstanceId: string;
requestId: string;
sourceLanguageAnchor: string;
issuedAt: string;
validUntil: string;
evidenceDigest: string;
signerId: string;
signatureAlgorithm: 'Ed25519';
signature: string;
}
export interface PersonaControlAuthorizationSigner {
algorithm: 'Ed25519';
humanResponsibilitySubjects: readonly string[];
personaIds: readonly string[];
publicKeyPem: string;
scopes: readonly [typeof PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE, ...string[]];
signerId: string;
status: 'ACTIVE' | 'REVOKED';
}
export interface PersonaControlAuthorizationSignerRegistrySource {
repositoryId: 'REPO-012';
sourceCommit: string;
sourcePath: typeof REGISTRY_PATH;
sourceUrl: string;
}
export interface PersonaControlAuthorizationSignerRegistry {
registryId: 'GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001';
schema: typeof PERSONA_CONTROL_AUTHORIZATION_SIGNER_REGISTRY_SCHEMA;
signers: readonly PersonaControlAuthorizationSigner[];
source: Readonly<PersonaControlAuthorizationSignerRegistrySource>;
state: 'CURRENT';
}
export interface PersonaControlAuthorizationExpectation {
personaId: string;
humanResponsibilitySubject: string;
repositoryHead: string;
modelInstanceId: string;
requestId: string;
sourceLanguageAnchor: string;
observedAt: number;
}
export interface VerifiedPersonaControlAuthorization {
receipt: Readonly<PersonaControlAuthorizationReceipt>;
signerId: string;
sourceCommit: string;
verifiedAt: string;
}
function invalidRegistry(): never {
throw new Error('persona_control_authorization_signer_registry_invalid');
}
function invalidSource(): never {
throw new Error('persona_control_authorization_signer_registry_source_invalid');
}
function invalidReceipt(): never {
throw new Error('persona_control_authorization_receipt_invalid');
}
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null && !Array.isArray(value);
}
function hasExactKeys(record: Record<string, unknown>, keys: string[]): boolean {
const expected = [...keys].sort();
const actual = Object.keys(record).sort();
return actual.length === expected.length && actual.every((key, index) => key === expected[index]);
}
function isIdentifier(value: unknown): value is string {
return typeof value === 'string' && IDENTIFIER_PATTERN.test(value);
}
function exactIdentifierList(value: unknown): readonly string[] {
if (!Array.isArray(value)
|| value.length === 0
|| value.some(item => !isIdentifier(item))
|| new Set(value).size !== value.length) invalidRegistry();
return Object.freeze([...value] as string[]);
}
function parsePublicKey(value: unknown): string {
if (typeof value !== 'string'
|| value.length > 4096
|| value.includes('PRIVATE KEY')
|| !value.startsWith('-----BEGIN PUBLIC KEY-----\n')
|| !value.endsWith('-----END PUBLIC KEY-----\n')) invalidRegistry();
try {
const key = createPublicKey({ format: 'pem', key: value });
if (key.asymmetricKeyType !== 'ed25519') invalidRegistry();
} catch {
invalidRegistry();
}
return value;
}
function parseSource(source: PersonaControlAuthorizationSignerRegistrySource): Readonly<PersonaControlAuthorizationSignerRegistrySource> {
if (!isRecord(source)
|| !hasExactKeys(source, ['repositoryId', 'sourceCommit', 'sourcePath', 'sourceUrl'])
|| source.repositoryId !== 'REPO-012'
|| typeof source.sourceCommit !== 'string'
|| !COMMIT_PATTERN.test(source.sourceCommit)
|| source.sourcePath !== REGISTRY_PATH
|| typeof source.sourceUrl !== 'string') invalidSource();
const expectedUrl = `https://guanghulab.com/code/bingshuo/guanghu-ice-heart/raw/commit/${source.sourceCommit}/${REGISTRY_PATH}`;
if (source.sourceUrl !== expectedUrl) invalidSource();
return Object.freeze({ ...source });
}
function parseSigner(input: unknown): PersonaControlAuthorizationSigner {
if (!isRecord(input)
|| !hasExactKeys(input, ['algorithm', 'humanResponsibilitySubjects', 'personaIds', 'publicKeyPem', 'scopes', 'signerId', 'status'])
|| input.algorithm !== 'Ed25519'
|| !isIdentifier(input.signerId)
|| (input.status !== 'ACTIVE' && input.status !== 'REVOKED')) invalidRegistry();
const scopes = exactIdentifierList(input.scopes);
if (!scopes.includes(PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE)) invalidRegistry();
return Object.freeze({
algorithm: 'Ed25519',
humanResponsibilitySubjects: exactIdentifierList(input.humanResponsibilitySubjects),
personaIds: exactIdentifierList(input.personaIds),
publicKeyPem: parsePublicKey(input.publicKeyPem),
scopes: scopes as PersonaControlAuthorizationSigner['scopes'],
signerId: input.signerId,
status: input.status,
});
}
export function parsePersonaControlAuthorizationSignerRegistry(
input: unknown,
source: PersonaControlAuthorizationSignerRegistrySource,
): PersonaControlAuthorizationSignerRegistry {
if (!isRecord(input)
|| !hasExactKeys(input, ['registryId', 'schema', 'signers', 'state'])
|| input.registryId !== 'GH-AIOS-PERSONA-CONTROL-AUTHORIZATION-SIGNERS-001'
|| input.schema !== PERSONA_CONTROL_AUTHORIZATION_SIGNER_REGISTRY_SCHEMA
|| input.state !== 'CURRENT'
|| !Array.isArray(input.signers)) invalidRegistry();
const signers = input.signers.map(parseSigner);
if (new Set(signers.map(signer => signer.signerId)).size !== signers.length) invalidRegistry();
return Object.freeze({
registryId: input.registryId,
schema: input.schema,
signers: Object.freeze(signers),
source: parseSource(source),
state: input.state,
});
}
function requiredString(record: Record<string, unknown>, key: string): string {
const value = Reflect.get(record, key);
if (typeof value !== 'string' || !value.trim()) invalidReceipt();
return value;
}
export function parsePersonaControlAuthorizationReceipt(input: unknown): PersonaControlAuthorizationReceipt {
const keys = [
'authorizationId', 'evidenceDigest', 'humanResponsibilitySubject', 'issuedAt', 'modelInstanceId',
'outcome', 'personaId', 'repositoryHead', 'requestId', 'schema', 'scope', 'signature',
'signatureAlgorithm', 'signerId', 'sourceLanguageAnchor', 'validUntil', 'verifier',
];
if (!isRecord(input)
|| !hasExactKeys(input, keys)
|| input.schema !== PERSONA_CONTROL_AUTHORIZATION_SCHEMA
|| input.outcome !== 'VERIFIED'
|| input.verifier !== 'GUANGHU_OS'
|| input.scope !== PERSONA_PRIMARY_LANGUAGE_PLANNING_SCOPE
|| input.signatureAlgorithm !== 'Ed25519') invalidReceipt();
const receipt = {
schema: input.schema,
outcome: input.outcome,
authorizationId: requiredString(input, 'authorizationId'),
verifier: input.verifier,
scope: input.scope,
personaId: requiredString(input, 'personaId'),
humanResponsibilitySubject: requiredString(input, 'humanResponsibilitySubject'),
repositoryHead: requiredString(input, 'repositoryHead'),
modelInstanceId: requiredString(input, 'modelInstanceId'),
requestId: requiredString(input, 'requestId'),
sourceLanguageAnchor: requiredString(input, 'sourceLanguageAnchor'),
issuedAt: requiredString(input, 'issuedAt'),
validUntil: requiredString(input, 'validUntil'),
evidenceDigest: requiredString(input, 'evidenceDigest'),
signerId: requiredString(input, 'signerId'),
signatureAlgorithm: input.signatureAlgorithm,
signature: requiredString(input, 'signature'),
} satisfies PersonaControlAuthorizationReceipt;
if (!isIdentifier(receipt.authorizationId)
|| !isIdentifier(receipt.personaId)
|| !isIdentifier(receipt.humanResponsibilitySubject)
|| !COMMIT_PATTERN.test(receipt.repositoryHead)
|| !isIdentifier(receipt.modelInstanceId)
|| !isIdentifier(receipt.requestId)
|| !isIdentifier(receipt.signerId)
|| !DIGEST_PATTERN.test(receipt.evidenceDigest)
|| !SIGNATURE_PATTERN.test(receipt.signature)) invalidReceipt();
return Object.freeze(receipt);
}
export function personaControlAuthorizationSigningBytes(receipt: Omit<PersonaControlAuthorizationReceipt, 'signature'>): Buffer {
return Buffer.from(`${JSON.stringify([
SIGNING_CONTEXT,
receipt.schema,
receipt.outcome,
receipt.authorizationId,
receipt.verifier,
receipt.scope,
receipt.personaId,
receipt.humanResponsibilitySubject,
receipt.repositoryHead,
receipt.modelInstanceId,
receipt.requestId,
receipt.sourceLanguageAnchor,
receipt.issuedAt,
receipt.validUntil,
receipt.evidenceDigest,
receipt.signerId,
receipt.signatureAlgorithm,
])}\n`, 'utf8');
}
export function verifyPersonaControlAuthorization(
input: unknown,
registry: PersonaControlAuthorizationSignerRegistry,
expected: PersonaControlAuthorizationExpectation,
): VerifiedPersonaControlAuthorization | null {
let receipt: PersonaControlAuthorizationReceipt;
try {
receipt = parsePersonaControlAuthorizationReceipt(input);
} catch {
return null;
}
const issuedAt = Date.parse(receipt.issuedAt);
const validUntil = Date.parse(receipt.validUntil);
if (receipt.personaId !== expected.personaId
|| receipt.humanResponsibilitySubject !== expected.humanResponsibilitySubject
|| receipt.repositoryHead !== expected.repositoryHead
|| receipt.modelInstanceId !== expected.modelInstanceId
|| receipt.requestId !== expected.requestId
|| receipt.sourceLanguageAnchor !== expected.sourceLanguageAnchor
|| !Number.isFinite(issuedAt)
|| !Number.isFinite(validUntil)
|| issuedAt > expected.observedAt
|| expected.observedAt > validUntil
|| issuedAt >= validUntil) return null;
const signer = registry.signers.find(candidate => candidate.status === 'ACTIVE'
&& candidate.signerId === receipt.signerId
&& candidate.personaIds.includes(receipt.personaId)
&& candidate.humanResponsibilitySubjects.includes(receipt.humanResponsibilitySubject)
&& candidate.scopes.includes(receipt.scope));
if (!signer) return null;
const { signature, ...signedReceipt } = receipt;
let signatureBytes: Buffer;
try {
signatureBytes = Buffer.from(signature, 'base64url');
} catch {
return null;
}
if (!verifySignature(null, personaControlAuthorizationSigningBytes(signedReceipt), signer.publicKeyPem, signatureBytes)) return null;
return Object.freeze({
receipt,
signerId: signer.signerId,
sourceCommit: registry.source.sourceCommit,
verifiedAt: new Date(expected.observedAt).toISOString(),
});
}