import { generateKeyPairSync, sign, verify } from "node:crypto"; import { mkdtemp, writeFile } from "node:fs/promises"; import { tmpdir } from "node:os"; import { join } from "node:path"; import { describe, expect, test } from "vitest"; import { canonicalJsonDigest, canonicalValueBytes, } from "../../src/security/canonical-digest.ts"; import { computeProductionInputRoot } from "../../src/security/production-input-root.ts"; import { APPROVAL_PROJECTION_ATTESTATION_SCOPE } from "../../src/security/approval-closure.ts"; // @ts-expect-error executable release modules intentionally have no browser declarations import * as releaseSigner from "../../scripts/release/sign-release.mjs"; const { signReleaseCandidate, signReleaseCandidateForTest, validateTrustPolicy, verifySignedReleaseEnvelope, verifySignedReleaseEnvelopeForTest, } = releaseSigner; const h = (c: string) => c.repeat(64); const roles = [ ["approval", "portfolio-approval-decision/v1"], ["projection", APPROVAL_PROJECTION_ATTESTATION_SCOPE], ["privacyWorkload", "portfolio-privacy-workload/v1"], ["policy", "portfolio-policy-attestation/v1"], ["toolImage", "portfolio-tool-image-provenance/v1"], ["release", "portfolio-release-signing/v1"], ["publisher", "portfolio-publisher-fencing/v1"], ] as const; function keys(): Record { return Object.fromEntries( roles.map(([role]) => [role, generateKeyPairSync("ed25519")]), ) as Record; } function policy(keyPairs: ReturnType): any { return { schema: "portfolio-trust-policy/v1", policyVersion: "2026-09-02", publicationFrozen: false, roots: Object.fromEntries( roles.map(([role, scope]) => [ role, { keyId: `${role}-key`, issuerEpoch: 2, scope, publicKeyPem: keyPairs[role]!.publicKey.export({ type: "spki", format: "pem", }), revokedEpochs: [1], compromiseResponse: role === "approval" ? ["freeze-publication", "audit-checkpoint", "fresh-approvals"] : role === "publisher" ? [ "freeze-publication", "rotate-credential", "advance-fence", "reconcile-staging", ] : ["freeze-publication", "revoke-epoch", "fresh-evidence"], }, ]), ), }; } function envelope( role: string, scope: string, keyPairs: ReturnType, payload: Record = {}, ) { const schema = { projection: "portfolio-approval-projection-attestation/v1", privacyWorkload: "portfolio-privacy-workload-attestation/v1", policy: "portfolio-policy-attestation/v1", toolImage: "portfolio-tool-image-provenance/v1", }[role]; const body = { schema, keyId: `${role}-key`, issuerEpoch: 2, scope, issuedAt: "2026-09-02T00:00:00Z", expiresAt: "2026-09-03T00:00:00Z", ...payload, }; return { ...(role === "projection" ? { schema: "portfolio-approval-projection-attestation-envelope/v1" } : {}), payload: body, signature: { algorithm: "Ed25519", encoding: "base64url-nopad", value: sign( null, canonicalValueBytes(body), keyPairs[role]!.privateKey, ).toString("base64url"), }, }; } function fixture() { const keyPairs = keys(); const inputDigests = { PortfolioContent: h("a"), PublicAssetManifest: h("b"), PreparedAssetSet: h("c"), SiteConfig: h("d"), }; const releaseManifest = { schema: "portfolio-release-manifest/v1", launchMode: "private-source-initial-launch", buildId: "r1", inputDigests, productionInputRoot: computeProductionInputRoot({ portfolioContentDigest: h("a"), publicAssetManifestDigest: h("b"), preparedAssetSetDigest: h("c"), siteConfigDigest: h("d"), }), artifacts: [ { path: "index.html", sha256: h("6"), bytes: 42, encoding: "identity" }, ], deployFiles: ["index.html"], sourceCommit: h("7"), sourceTree: h("8"), approvedRecordRoot: h("9"), approvedAssetRoot: h("e"), approvalProofSetDigest: h("f"), approvalProjectionAttestationDigest: h("2"), scanEvidenceDigest: h("3"), policyVersions: { approval: "1", privacy: "1", release: "1" }, toolVersions: { node: "22" }, predecessorManifestDigest: null, currentManifestDigest: null, issuedAt: "2026-09-02T00:00:00Z", expiresAt: "2026-09-02T12:00:00Z", minimumBoundExpiry: "2026-09-02T12:00:00Z", }; const sourceClosure = { schema: "portfolio-publication-closure/v1", members: [], }; const publicObjectClosure = { schema: "portfolio-public-object-closure/v1", members: [], }; const candidate = { schema: "portfolio-candidate-release-manifest/v1", releaseManifest, latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, sourceClosureDigest: canonicalJsonDigest( "portfolio-publication-closure/v1", sourceClosure, ), publicObjectClosureDigest: canonicalJsonDigest( "portfolio-public-object-closure/v1", publicObjectClosure, ), publicRefMap: {}, lfsOids: [], releaseArchives: [], buildProvenanceDigest: h("5"), privacyPolicyDigest: h("a"), toolImageDigests: [h("b"), h("c")], }; const evidence = { schema: "portfolio-scan-evidence/v1", sourceClosureDigest: candidate.sourceClosureDigest, publicObjectClosureDigest: candidate.publicObjectClosureDigest, coverageCount: 0, passedCount: 0, checks: [], }; const evidenceDigest = canonicalJsonDigest( "portfolio-scan-evidence/v1", evidence, ); candidate.releaseManifest.scanEvidenceDigest = evidenceDigest; const projection = envelope("projection", roles[1][1], keyPairs, { approvedRecordRoot: h("9"), approvedAssetRoot: h("e"), approvalProofSetDigest: h("f"), inputDigests, productionInputRoot: releaseManifest.productionInputRoot, liveLedgerHeadHash: h("1"), liveLedgerRevision: 7, policyVersion: "2026-09-02", expiresAt: "2026-09-02T12:00:00Z", earliestApprovalExpiry: "2026-09-02T12:00:00Z", }); candidate.releaseManifest.approvalProjectionAttestationDigest = canonicalJsonDigest( "portfolio-approval-projection-attestation-envelope/v1", projection, ); const candidateDigest = canonicalJsonDigest( "portfolio-candidate-release-manifest/v1", candidate, ); const attestations = { projection, privacyWorkload: envelope("privacyWorkload", roles[2][1], keyPairs, { digest: h("2"), candidateDigest, evidenceDigest, }), policy: envelope("policy", roles[3][1], keyPairs, { digest: h("a"), policyVersion: "2026-09-02", }), toolImage: envelope("toolImage", roles[4][1], keyPairs, { digest: h("b"), imageDigests: [h("b"), h("c")], }), }; return { keyPairs, sourceClosure, publicObjectClosure, candidate, evidence, candidateDigest, evidenceDigest, attestations, trustPolicy: policy(keyPairs), }; } describe("external scoped release signing", () => { test("production signer/verifier reject caller-selected time and signer reconstructs exact source/public closure", async () => { const f = fixture(); const dir = await mkdtemp(join(tmpdir(), "portfolio-release-key-")); const keyPath = join(dir, "release.pem"); await writeFile( keyPath, f.keyPairs.release!.privateKey.export({ type: "pkcs8", format: "pem" }), { mode: 0o400 }, ); await expect( signReleaseCandidate({ ...f, releaseKeyPath: keyPath, now: "2000-01-01T00:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }), ).rejects.toThrow(/now|unknown|caller/i); const nonEmptyPublic = { schema: "portfolio-public-object-closure/v1", members: [ { objectId: "private.txt", inputDigest: h("d"), requiredChecks: ["secret"], }, ], }; await expect( signReleaseCandidateForTest({ ...f, publicObjectClosure: nonEmptyPublic, releaseKeyPath: keyPath, now: "2026-09-02T01:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }), ).rejects.toThrow(/closure|digest|non-empty/i); }); test("signs exact canonical candidate bytes with an external Ed25519 key", async () => { const f = fixture(); const dir = await mkdtemp(join(tmpdir(), "portfolio-release-key-")); const keyPath = join(dir, "release.pem"); await writeFile( keyPath, f.keyPairs.release!.privateKey.export({ type: "pkcs8", format: "pem" }), { mode: 0o400 }, ); const result = await signReleaseCandidateForTest({ ...f, releaseKeyPath: keyPath, now: "2026-09-02T01:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }); expect(result.payloadDigest).toBe(f.candidateDigest); expect( verify( null, canonicalValueBytes(f.candidate), f.keyPairs.release!.publicKey, Buffer.from(result.signature.value, "base64url"), ), ).toBe(true); expect( verifySignedReleaseEnvelopeForTest({ signedRelease: result, trustPolicy: f.trustPolicy, now: "2026-09-02T01:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }), ).toMatchObject({ schema: "portfolio-verified-release-admission/v1", manifestDigest: f.candidateDigest, expiresAt: "2026-09-02T12:00:00Z", predecessorManifestDigest: null, latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, expectedFence: 5, signatureVerified: true, }); }); test("verified admission rejects stale publisher state and substituted signed payloads", async () => { const f = fixture(); const dir = await mkdtemp(join(tmpdir(), "portfolio-release-key-")); const keyPath = join(dir, "release.pem"); await writeFile( keyPath, f.keyPairs.release!.privateKey.export({ type: "pkcs8", format: "pem" }), { mode: 0o400 }, ); const state = { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }; const signedRelease = await signReleaseCandidateForTest({ ...f, releaseKeyPath: keyPath, now: "2026-09-02T01:00:00Z", protectedState: state, }); expect(() => verifySignedReleaseEnvelopeForTest({ signedRelease, trustPolicy: f.trustPolicy, now: "2026-09-02T01:00:00Z", protectedState: { ...state, latestLedgerRevision: 8 }, }), ).toThrow(/stale/i); expect(() => verifySignedReleaseEnvelopeForTest({ signedRelease: { ...signedRelease, payload: { ...signedRelease.payload, latestLedgerRevision: 8 }, }, trustPolicy: f.trustPolicy, now: "2026-09-02T01:00:00Z", protectedState: state, }), ).toThrow(); }); test("refuses repository-resident keys and non-external key material", async () => { const f = fixture(); await expect( signReleaseCandidateForTest({ ...f, releaseKeyPath: join(process.cwd(), "release.pem"), now: "2026-09-02T01:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }), ).rejects.toThrow(/outside|external/i); }); test.each(roles)( "requires current non-revoked %s issuer epoch and exact scope", (role) => { const f = fixture(); const mutated = structuredClone(f.trustPolicy); mutated.roots[role].issuerEpoch = 1; expect(() => validateTrustPolicy(mutated)).toThrow(/revoked|epoch/i); const wrongScope = structuredClone(f.trustPolicy); wrongScope.roots[role].scope = "wrong/v1"; expect(() => validateTrustPolicy(wrongScope)).toThrow(/scope/i); }, ); test("requires explicit compromise recovery semantics for approval and publisher roots", () => { const f = fixture(); const approval = structuredClone(f.trustPolicy); approval.roots.approval.compromiseResponse = ["freeze-publication"]; expect(() => validateTrustPolicy(approval)).toThrow( /checkpoint|fresh approvals/i, ); const publisher = structuredClone(f.trustPolicy); publisher.roots.publisher.compromiseResponse = [ "freeze-publication", "rotate-credential", ]; expect(() => validateTrustPolicy(publisher)).toThrow(/fence|staging/i); }); test.each([ [ "stale ledger", (f: ReturnType) => ({ protectedState: { latestLedgerHeadHash: h("9"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, }), ], [ "expired candidate", (f: ReturnType) => ({ now: "2026-09-02T12:00:00Z" }), ], [ "substituted artifact", (f: ReturnType) => ({ candidate: { ...f.candidate, releaseManifest: { ...f.candidate.releaseManifest, artifacts: [ { ...f.candidate.releaseManifest.artifacts[0], bytes: 43 }, ], }, }, }), ], [ "forged workload epoch", (f: ReturnType) => ({ attestations: { ...f.attestations, privacyWorkload: { ...f.attestations.privacyWorkload, payload: { ...f.attestations.privacyWorkload.payload, issuerEpoch: 1, }, }, }, }), ], [ "projection mismatch", (f: ReturnType) => ({ candidate: { ...f.candidate, releaseManifest: { ...f.candidate.releaseManifest, productionInputRoot: h("9"), }, }, }), ], ])("rejects %s before signing", async (_name, mutate) => { const f = fixture(); const dir = await mkdtemp(join(tmpdir(), "portfolio-release-key-")); const keyPath = join(dir, "release.pem"); await writeFile( keyPath, f.keyPairs.release!.privateKey.export({ type: "pkcs8", format: "pem" }), { mode: 0o400 }, ); await expect( signReleaseCandidateForTest({ ...f, releaseKeyPath: keyPath, now: "2026-09-02T01:00:00Z", protectedState: { latestLedgerHeadHash: h("1"), latestLedgerRevision: 7, currentManifestDigest: null, publisherFence: 4, }, ...mutate(f), }), ).rejects.toThrow(); }); });