feat(domain-access): load trusted signer registry
This commit is contained in:
parent
fa2c868e0a
commit
6bd95c76e9
7 changed files with 318 additions and 17 deletions
|
|
@ -0,0 +1,162 @@
|
|||
import { createPublicKey } from 'node:crypto';
|
||||
|
||||
const REGISTRY_SCHEMA = 'gh-aios.trusted-domain-manifest-signers/v1' as const;
|
||||
const COMMIT_PATTERN = /^[a-f0-9]{40}(?:[a-f0-9]{24})?$/;
|
||||
const IDENTIFIER_PATTERN = /^[A-Z0-9][A-Z0-9._:-]{1,159}$/;
|
||||
const VERSION_PATTERN = /^(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)\.(?:0|[1-9]\d*)$/;
|
||||
const registeredTrustedSigners = new WeakSet<object>();
|
||||
|
||||
export interface TrustedManifestSigner {
|
||||
algorithm: 'Ed25519';
|
||||
domainIds: readonly string[];
|
||||
publicKeyPem: string;
|
||||
repositoryId: string;
|
||||
signerId: string;
|
||||
status: 'ACTIVE' | 'REVOKED';
|
||||
}
|
||||
|
||||
export interface TrustedSignerRegistrySource {
|
||||
repositoryId: 'REPO-012';
|
||||
sourceCommit: string;
|
||||
sourceUrl: string;
|
||||
}
|
||||
|
||||
export interface TrustedManifestSignerRegistry {
|
||||
registryId: 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001';
|
||||
schema: typeof REGISTRY_SCHEMA;
|
||||
signers: readonly TrustedManifestSigner[];
|
||||
source: Readonly<TrustedSignerRegistrySource>;
|
||||
state: 'CURRENT';
|
||||
version: string;
|
||||
}
|
||||
|
||||
export interface TrustedSignerLookup {
|
||||
domainId: string;
|
||||
repositoryId: string;
|
||||
signerId: string;
|
||||
}
|
||||
|
||||
function invalidRegistry(): never {
|
||||
throw new Error('trusted_signer_registry_invalid');
|
||||
}
|
||||
|
||||
function invalidSource(): never {
|
||||
throw new Error('trusted_signer_registry_source_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 parseSource(source: TrustedSignerRegistrySource): Readonly<TrustedSignerRegistrySource> {
|
||||
if (!isRecord(source)
|
||||
|| !hasExactKeys(source, ['repositoryId', 'sourceCommit', 'sourceUrl'])
|
||||
|| source.repositoryId !== 'REPO-012'
|
||||
|| typeof source.sourceCommit !== 'string'
|
||||
|| !COMMIT_PATTERN.test(source.sourceCommit)
|
||||
|| typeof source.sourceUrl !== 'string') invalidSource();
|
||||
let url: URL;
|
||||
try {
|
||||
url = new URL(source.sourceUrl);
|
||||
} catch {
|
||||
invalidSource();
|
||||
}
|
||||
if (url.protocol !== 'https:'
|
||||
|| url.hostname !== 'guanghulab.com'
|
||||
|| url.port
|
||||
|| url.username
|
||||
|| url.password
|
||||
|| url.search
|
||||
|| url.hash) invalidSource();
|
||||
return Object.freeze({ ...source });
|
||||
}
|
||||
|
||||
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 parseSigner(input: unknown): TrustedManifestSigner {
|
||||
if (!isRecord(input)
|
||||
|| !hasExactKeys(input, ['algorithm', 'domainIds', 'publicKeyPem', 'repositoryId', 'signerId', 'status'])
|
||||
|| input.algorithm !== 'Ed25519'
|
||||
|| !isIdentifier(input.repositoryId)
|
||||
|| !isIdentifier(input.signerId)
|
||||
|| (input.status !== 'ACTIVE' && input.status !== 'REVOKED')
|
||||
|| !Array.isArray(input.domainIds)
|
||||
|| input.domainIds.length === 0
|
||||
|| input.domainIds.some(domainId => !isIdentifier(domainId))
|
||||
|| new Set(input.domainIds).size !== input.domainIds.length) invalidRegistry();
|
||||
const signer: TrustedManifestSigner = Object.freeze({
|
||||
algorithm: 'Ed25519',
|
||||
domainIds: Object.freeze([...input.domainIds] as string[]),
|
||||
publicKeyPem: parsePublicKey(input.publicKeyPem),
|
||||
repositoryId: input.repositoryId,
|
||||
signerId: input.signerId,
|
||||
status: input.status,
|
||||
});
|
||||
registeredTrustedSigners.add(signer);
|
||||
return signer;
|
||||
}
|
||||
|
||||
export function parseTrustedManifestSignerRegistry(
|
||||
input: unknown,
|
||||
source: TrustedSignerRegistrySource,
|
||||
): TrustedManifestSignerRegistry {
|
||||
if (!isRecord(input)
|
||||
|| !hasExactKeys(input, ['registryId', 'schema', 'signers', 'state', 'version'])
|
||||
|| input.registryId !== 'GH-AIOS-TRUSTED-DOMAIN-MANIFEST-SIGNERS-001'
|
||||
|| input.schema !== REGISTRY_SCHEMA
|
||||
|| input.state !== 'CURRENT'
|
||||
|| typeof input.version !== 'string'
|
||||
|| !VERSION_PATTERN.test(input.version)
|
||||
|| !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,
|
||||
version: input.version,
|
||||
});
|
||||
}
|
||||
|
||||
export function assertRegisteredTrustedManifestSigner(
|
||||
signer: TrustedManifestSigner,
|
||||
): TrustedManifestSigner {
|
||||
if (!registeredTrustedSigners.has(signer)) throw new Error('trusted_manifest_signer_unregistered');
|
||||
return signer;
|
||||
}
|
||||
|
||||
export function resolveTrustedManifestSigner(
|
||||
registry: TrustedManifestSignerRegistry,
|
||||
lookup: TrustedSignerLookup,
|
||||
): TrustedManifestSigner | null {
|
||||
const signer = registry.signers.find(candidate => candidate.status === 'ACTIVE'
|
||||
&& candidate.signerId === lookup.signerId
|
||||
&& candidate.repositoryId === lookup.repositoryId
|
||||
&& candidate.domainIds.includes(lookup.domainId));
|
||||
return signer ? assertRegisteredTrustedManifestSigner(signer) : null;
|
||||
}
|
||||
Loading…
Reference in a new issue