Initial Commit
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
import { webcrypto } from "node:crypto";
|
||||
|
||||
type WritableGlobal = { crypto?: Crypto };
|
||||
const g = globalThis as WritableGlobal;
|
||||
if (g.crypto === undefined) {
|
||||
g.crypto = webcrypto as unknown as Crypto;
|
||||
}
|
||||
@@ -0,0 +1,155 @@
|
||||
/**
|
||||
* Spec-conformance tests for `openspec/specs/crypto-cipher/spec.md`.
|
||||
*
|
||||
* Pins structural scenarios that the per-primitive unit suites do not
|
||||
* cover directly: every AEAD has both `noble.ts` and `native.ts`,
|
||||
* every `*.select.ts` carries the GENERATED banner, every
|
||||
* `*.native.ts` is currently a `PrimitiveNotImplemented` stub
|
||||
* (runtime reflection of the dangerous-tier flip gate), the package
|
||||
* is in a consistent pre/post-Phase-0.5 vendoring state, and no
|
||||
* primitive source file consumes a CSPRNG (nonce-is-caller-supplied).
|
||||
*/
|
||||
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { describe, expect, it } from "@suaveplan/testing/runner";
|
||||
|
||||
const ROOT = join(__dirname, "..");
|
||||
const SRC = join(ROOT, "src");
|
||||
const PRIMITIVES = join(SRC, "primitives");
|
||||
|
||||
const AEAD_IDS = [
|
||||
"aes-256-gcm",
|
||||
"chacha20-poly1305",
|
||||
"xchacha20-poly1305",
|
||||
] as const;
|
||||
|
||||
function* walk(dir: string): Generator<string> {
|
||||
for (const entry of readdirSync(dir)) {
|
||||
const full = join(dir, entry);
|
||||
const st = statSync(full);
|
||||
if (
|
||||
st.isDirectory() &&
|
||||
!full.includes("node_modules") &&
|
||||
!full.includes("dist")
|
||||
) {
|
||||
yield* walk(full);
|
||||
} else if (full.endsWith(".ts") && !full.endsWith(".test.ts")) {
|
||||
yield full;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
describe("crypto-cipher — universal AEAD primitive surface", () => {
|
||||
it("every AEAD id has its primitive directory", { timeout: 5000 }, () => {
|
||||
for (const id of AEAD_IDS) {
|
||||
expect(existsSync(join(PRIMITIVES, id))).toBe(true);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("crypto-cipher — per-primitive replaceability layout", () => {
|
||||
for (const id of AEAD_IDS) {
|
||||
it(`${id} has both noble.ts and native.ts impl files`, {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
expect(
|
||||
existsSync(
|
||||
join(
|
||||
PRIMITIVES,
|
||||
id,
|
||||
"impl",
|
||||
`${id}.noble`,
|
||||
`${id}.noble.ts`
|
||||
)
|
||||
)
|
||||
).toBe(true);
|
||||
expect(
|
||||
existsSync(
|
||||
join(
|
||||
PRIMITIVES,
|
||||
id,
|
||||
"impl",
|
||||
`${id}.native`,
|
||||
`${id}.native.ts`
|
||||
)
|
||||
)
|
||||
).toBe(true);
|
||||
});
|
||||
|
||||
it(`${id}.select.ts carries the GENERATED banner`, {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const sel = join(PRIMITIVES, id, `${id}.select`, `${id}.select.ts`);
|
||||
expect(existsSync(sel)).toBe(true);
|
||||
const c = readFileSync(sel, "utf8");
|
||||
expect(c).toMatch(/GENERATED/);
|
||||
expect(c).toMatch(/DO NOT EDIT/);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
describe("crypto-cipher — vendored noble source (Phase 0.5)", () => {
|
||||
it("is a Phase-0.5 forward gate; current state is pre-0.5 (noble dep + no vendored/)", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const pkg = JSON.parse(
|
||||
readFileSync(join(ROOT, "package.json"), "utf8")
|
||||
);
|
||||
const noblePresent =
|
||||
"@noble/ciphers" in (pkg.dependencies ?? {}) ||
|
||||
"@noble/ciphers" in (pkg.peerDependencies ?? {});
|
||||
const vendoredPresent = existsSync(join(SRC, "vendored"));
|
||||
const upstreamFile = existsSync(join(ROOT, "NOBLE_UPSTREAM.md"));
|
||||
const pre05 = noblePresent && !vendoredPresent && !upstreamFile;
|
||||
const post05 = !noblePresent && vendoredPresent && upstreamFile;
|
||||
expect(pre05 || post05).toBe(true);
|
||||
});
|
||||
});
|
||||
|
||||
describe("crypto-cipher — constant-time and dangerous-tier reflection", () => {
|
||||
for (const id of AEAD_IDS) {
|
||||
it(`${id}.native.ts is currently a PrimitiveNotImplemented stub`, {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const src = readFileSync(
|
||||
join(PRIMITIVES, id, "impl", `${id}.native`, `${id}.native.ts`),
|
||||
"utf8"
|
||||
);
|
||||
expect(src).toMatch(/PrimitiveNotImplemented/);
|
||||
});
|
||||
}
|
||||
|
||||
it("every native is a stub today, so no AEAD has been flipped without an audit", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const offenders: string[] = [];
|
||||
for (const id of AEAD_IDS) {
|
||||
const src = readFileSync(
|
||||
join(PRIMITIVES, id, "impl", `${id}.native`, `${id}.native.ts`),
|
||||
"utf8"
|
||||
);
|
||||
if (!/PrimitiveNotImplemented/.test(src)) offenders.push(id);
|
||||
}
|
||||
expect(offenders).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("crypto-cipher — nonce is caller-supplied", () => {
|
||||
it("no primitive source file consumes a CSPRNG (no Math.random / crypto.getRandomValues / randomBytes)", {
|
||||
timeout: 5000,
|
||||
}, () => {
|
||||
const banned = [
|
||||
/\bMath\.random\b/,
|
||||
/\bcrypto\.getRandomValues\b/,
|
||||
/\brandomBytes\b/,
|
||||
];
|
||||
const hits: string[] = [];
|
||||
for (const file of walk(SRC)) {
|
||||
const c = readFileSync(file, "utf8");
|
||||
for (const re of banned) {
|
||||
if (re.test(c)) hits.push(`${file}: ${re}`);
|
||||
}
|
||||
}
|
||||
expect(hits).toEqual([]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user