#!/usr/bin/env node import { createPublicKey } from "node:crypto"; import { readFile } from "node:fs/promises"; import { pathToFileURL } from "node:url"; import { APPROVAL_PROJECTION_ATTESTATION_SCHEMA, APPROVAL_PROJECTION_ATTESTATION_SCOPE, computeApprovalClosure, } from "../../src/security/approval-closure.ts"; import { canonicalValueBytes, isSha256Hex, parseCanonicalJson, } from "../../src/security/canonical-digest.ts"; import { computeProductionInputRoot } from "../../src/security/production-input-root.ts"; const expectedRequestFields = [ "schema", "latestLedgerHeadHash", "latestLedgerRevision", "trustedKeys", "ledgerProof", "contentRecords", "assetRecords", "inputDigests", ].sort(); const expectedKeyFields = [ "keyId", "issuerEpoch", "scope", "authorizedRoles", "publicKeyPem", ].sort(); const expectedInputs = [ "PortfolioContent", "PreparedAssetSet", "PublicAssetManifest", "SiteConfig", ]; function exactObject(value, fields, label) { if (value === null || typeof value !== "object" || Array.isArray(value)) throw new Error(`${label} must be an object.`); if (JSON.stringify(Object.keys(value).sort()) !== JSON.stringify(fields)) throw new Error(`${label} contains missing or unknown fields.`); return value; } function requestPath(args) { if (args.length !== 2 || args[0] !== "--request" || args[1].length === 0) throw new Error( "Usage: verify-approvals.mjs --request ", ); return args[1]; } function verifyApprovalRequestAt(source, now) { const request = exactObject( parseCanonicalJson(source), expectedRequestFields, "request", ); if (request.schema !== "portfolio-approval-verification-request/v1") throw new Error("Request schema is invalid."); if ( !Array.isArray(request.trustedKeys) || !Array.isArray(request.contentRecords) || !Array.isArray(request.assetRecords) ) throw new Error("Approval record lists are invalid."); const trustedKeys = request.trustedKeys.map((candidate) => { const key = exactObject(candidate, expectedKeyFields, "trusted key"); if ( key.scope !== "portfolio-approval-decision/v1" || typeof key.keyId !== "string" || !Number.isSafeInteger(key.issuerEpoch) || !Array.isArray(key.authorizedRoles) || key.authorizedRoles.some((role) => typeof role !== "string") || typeof key.publicKeyPem !== "string" ) { throw new Error("Trusted approval key is invalid."); } return { keyId: key.keyId, issuerEpoch: key.issuerEpoch, scope: key.scope, authorizedRoles: key.authorizedRoles, publicKey: createPublicKey(key.publicKeyPem), }; }); const inputDigests = exactObject( request.inputDigests, expectedInputs, "inputDigests", ); if (Object.values(inputDigests).some((digest) => !isSha256Hex(digest))) throw new Error("Input digest is invalid."); const closure = computeApprovalClosure({ contentRecords: request.contentRecords, assetRecords: request.assetRecords, trust: { now, latestLedgerHeadHash: request.latestLedgerHeadHash, latestLedgerRevision: request.latestLedgerRevision, trustedKeys, ledgerProof: request.ledgerProof, }, }); const productionInputRoot = computeProductionInputRoot({ portfolioContentDigest: inputDigests.PortfolioContent, publicAssetManifestDigest: inputDigests.PublicAssetManifest, preparedAssetSetDigest: inputDigests.PreparedAssetSet, siteConfigDigest: inputDigests.SiteConfig, }); return { schema: "portfolio-approval-verification-result/v1", projectionSchema: APPROVAL_PROJECTION_ATTESTATION_SCHEMA, projectionScope: APPROVAL_PROJECTION_ATTESTATION_SCOPE, approvedRecordRoot: closure.approvedRecordRoot, approvedAssetRoot: closure.approvedAssetRoot, approvalProofSetDigest: closure.approvalProofSetDigest, productionInputRoot, latestLedgerHeadHash: request.latestLedgerHeadHash, latestLedgerRevision: request.latestLedgerRevision, earliestApprovalExpiry: closure.earliestApprovalExpiry, }; } export function verifyApprovalRequest(source) { return verifyApprovalRequestAt(source, new Date().toISOString()); } export function verifyApprovalRequestForTest(source, clock) { if ( clock === null || typeof clock !== "object" || Object.keys(clock).length !== 1 || typeof clock.now !== "string" ) { throw new Error("Test-only approval clock is invalid."); } return verifyApprovalRequestAt(source, clock.now); } async function main() { const path = requestPath(process.argv.slice(2)); const result = verifyApprovalRequest(await readFile(path)); process.stdout.write(Buffer.from(canonicalValueBytes(result))); process.stdout.write("\n"); } if ( process.argv[1] !== undefined && import.meta.url === pathToFileURL(process.argv[1]).href ) { main().catch((error) => { const message = error instanceof Error ? error.message : "Unknown verification failure."; process.stderr.write(`Approval verification failed: ${message}\n`); process.exitCode = 1; }); }