48 lines
4.8 KiB
JavaScript
48 lines
4.8 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import crypto from "node:crypto";
|
|
import fs from "node:fs";
|
|
import os from "node:os";
|
|
import path from "node:path";
|
|
import { createRequire } from "node:module";
|
|
import { fileURLToPath } from "node:url";
|
|
import ts from "../../web/node_modules/typescript/lib/typescript.js";
|
|
|
|
const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "../..");
|
|
const reportPath = path.join(root, "tests/golden/M13-02F/signature-negative-report.json");
|
|
const manifestPath = path.join(root, "tests/golden/M13-02F/manifest.json");
|
|
const temporary = fs.mkdtempSync(path.join(os.tmpdir(), "m13-02f-signature-"));
|
|
const hashFile = (file) => crypto.createHash("sha256").update(fs.readFileSync(file)).digest("hex");
|
|
const publicKey = "03a107bff3ce10be1d70dd18e74bc09967e4d6309ba50d5f1ddc8664125531b8";
|
|
const signature = "fc396c6c68e6f6eb38a18c147becfaec1621a167f6db0a0d76874209accf3cb80dfa1fac1528ebc1bc6b090801a3ad397cae18e6ddb41740766678711c0a8804";
|
|
const script = (id = "clean", overrides = {}) => ({ id, name: id, entryPath: `scripts/${id}.py`, sourceByteLength: 128, sourceSha256: "a".repeat(64), publisher: "Team", signature, keyId: "key:new", permissions: ["READ_MAIN"], dependencies: [], module: false, cpuMs: 1000, memoryBytes: 1024 * 1024, wallMs: 5000, network: false, autorun: false, driverExpressions: false, addonInstall: false, ...overrides });
|
|
const manifest = (scripts = [script()]) => ({ schemaVersion: 1, scripts });
|
|
const key = (overrides = {}) => ({ keyId: "key:new", publisher: "Team", algorithm: "ED25519", publicKey, status: "ACTIVE", notBefore: "2026-01-01T00:00:00.000Z", notAfter: "2027-01-01T00:00:00.000Z", ...overrides });
|
|
const policy = (overrides = {}) => ({ schemaVersion: 1, issuer: "web-trust", issuedAt: "2026-01-01T00:00:00.000Z", expiresAt: "2027-01-01T00:00:00.000Z", maxClockSkewMs: 300000, keys: [key()], ...overrides });
|
|
for (const name of ["asset-path", "capability-gates", "scripting-platform"]) {
|
|
const source = fs.readFileSync(path.join(root, `web/protocol/${name}.ts`), "utf8");
|
|
const output = ts.transpileModule(source, { compilerOptions: { module: ts.ModuleKind.CommonJS, target: ts.ScriptTarget.ES2022 }, fileName: `${name}.ts` }).outputText
|
|
.replace('require("./asset-path")', 'require("./asset-path.cjs")')
|
|
.replace('require("./capability-gates")', 'require("./capability-gates.cjs")');
|
|
fs.writeFileSync(path.join(temporary, `${name}.cjs`), output);
|
|
}
|
|
const protocol = createRequire(import.meta.url)(path.join(temporary, "scripting-platform.cjs"));
|
|
try {
|
|
const at = "2026-08-18T12:00:00.000Z";
|
|
const decisions = {
|
|
missing: await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [] }), at),
|
|
expired: await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ notAfter: "2026-06-01T00:00:00.000Z" })] }), at),
|
|
notYetValid: await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ notBefore: "2026-09-01T00:00:00.000Z" })] }), at),
|
|
publisherMismatch: await protocol.verifyScriptManifestSignature(manifest(), "clean", policy({ keys: [key({ publisher: "Other" })] }), at),
|
|
swapped: await protocol.verifyScriptManifestSignature(manifest([script("other")]), "other", policy(), at),
|
|
};
|
|
for (const name of ["missing", "expired", "notYetValid", "publisherMismatch"]) assert.equal(decisions[name].code, "SCRIPT_POLICY_DENIED");
|
|
assert.equal(decisions.swapped.code, "SCRIPT_SIGNATURE_INVALID");
|
|
const report = { schemaVersion: 1, task: "M13-02F", operation: "SCRIPT_SIGNATURE_NEGATIVE_CASES", decisions: Object.fromEntries(Object.entries(decisions).map(([name, result]) => [name, result.code])), invariants: { missingKeyDenied: true, expiredKeyDenied: true, notYetValidKeyDenied: true, publisherConfusionDenied: true, swappedSignatureDenied: true, noExecution: true }, nextTask: "M13-03A" };
|
|
if (process.env.UPDATE_M13_02F_REPORT === "1") { fs.mkdirSync(path.dirname(reportPath), { recursive: true }); fs.writeFileSync(reportPath, JSON.stringify(report, null, 2) + "\n"); }
|
|
assert.deepEqual(JSON.parse(fs.readFileSync(reportPath, "utf8")), report);
|
|
const manifestValue = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
|
|
assert.deepEqual({ schemaVersion: manifestValue.schemaVersion, task: manifestValue.task, parentTask: manifestValue.parentTask, nextTask: manifestValue.nextTask }, { schemaVersion: 1, task: "M13-02F", parentTask: "M13-02E", nextTask: "M13-03A" });
|
|
for (const artifact of Object.values(manifestValue.artifacts)) assert.equal(hashFile(path.join(root, artifact.path)), artifact.sha256, `artifact hash mismatch ${artifact.path}`);
|
|
process.stdout.write(`script-signature-negative-ok missing=SCRIPT_POLICY_DENIED expired=SCRIPT_POLICY_DENIED notYetValid=SCRIPT_POLICY_DENIED swapped=SCRIPT_SIGNATURE_INVALID next=${manifestValue.nextTask}\n`);
|
|
} finally { fs.rmSync(temporary, { recursive: true, force: true }); }
|