Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,71 @@
/**
* Shared helpers for the drive-template test suite. Builds canonical
* sample templates, signs them, and exposes injectable clocks +
* deterministic Ed25519 keypairs so individual tests stay focused on
* the requirement they verify.
*/
import {
getPublicKey,
randomPrivateKey,
} from "@suaveplan/crypto-curve/ed25519";
import { toHex } from "@suaveplan/crypto-utils";
import { signTemplate } from "../src/codec/sign.js";
import type {
DriveTemplate,
FolderTemplate,
PerFilePolicy,
} from "../src/types/template.types.js";
export interface SampleKeys {
readonly secret: Uint8Array;
readonly publicHex: string;
}
export function makeKeys(): SampleKeys {
const secret = randomPrivateKey();
const publicHex = toHex(getPublicKey(secret));
return { secret, publicHex };
}
export function makeNonce(value = 7): Uint8Array {
return new Uint8Array(16).fill(value);
}
export const DEFAULT_POLICY: PerFilePolicy = {
autoDownload: false,
meshAllowed: true,
cdnAllowed: true,
retention: "lru",
};
export function emptyRoot(): FolderTemplate {
return { name: "/", children: [] };
}
export interface BuildOptions {
readonly issuer: string;
readonly nonce?: Uint8Array;
readonly issuedAt?: number;
readonly expiresAt?: number;
readonly rootFolder?: FolderTemplate;
}
export function buildTemplate(
options: BuildOptions
): Omit<DriveTemplate, "serverSignature"> {
return {
schemaVersion: 1,
driveId: "drive-1",
rootFolder: options.rootFolder ?? emptyRoot(),
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: options.issuer,
issuedAt: options.issuedAt ?? Date.now() - 1_000,
expiresAt: options.expiresAt ?? Date.now() + 24 * 60 * 60 * 1000,
nonce: options.nonce ?? makeNonce(),
};
}
export function signed(options: BuildOptions, keys: SampleKeys): DriveTemplate {
return signTemplate(buildTemplate(options), keys.secret);
}

View File

@@ -0,0 +1,305 @@
/**
* Branch-coverage filler for canonical.ts encryption / retention /
* optional-field paths and other defence-in-depth code that the
* spec-tied tests don't exercise.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { canonicalSerialize } from "../src/codec/canonical.js";
import type {
DriveTemplate,
FileTemplate,
FolderTemplate,
} from "../src/index.js";
import { TemplateNonceLru, validateTemplate } from "../src/index.js";
import {
DEFAULT_POLICY,
emptyRoot,
makeKeys,
makeNonce,
signed,
} from "./_helpers.js";
const baseFile: FileTemplate = {
name: "x.bin",
fileLocId: "loc-1",
fileId: "f".repeat(64),
size: 100,
totalChunks: 1,
chunkSize: 1024,
checksum: "sha256:abcdef",
flags: DEFAULT_POLICY,
manifestKey: "manifest:f",
manifestSig: new Uint8Array(64).fill(1),
};
describe("canonicalSerialize encryption branches", () => {
it("file with kind:inline encryption serializes", { timeout: 5000 }, () => {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: {
name: "/",
children: [
{
...baseFile,
encryption: {
algorithm: "xchacha20-poly1305",
keyDistribution: {
kind: "inline",
wrappedKey: new Uint8Array(80).fill(0xab),
},
},
},
],
},
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
it("file with kind:retroactive encryption serializes", {
timeout: 5000,
}, () => {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: {
name: "/",
children: [
{
...baseFile,
encryption: {
algorithm: "xchacha20-poly1305",
keyDistribution: {
kind: "retroactive",
keyAuthority: "auth-1",
},
},
},
],
},
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
it("retention 'permanent' / 'lru' / { maxAgeMs } / undefined serialize", {
timeout: 5000,
}, () => {
for (const retention of [
undefined,
"permanent" as const,
"lru" as const,
{ maxAgeMs: 1000 },
]) {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: {
defaultPolicy: { ...DEFAULT_POLICY, retention },
},
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: emptyRoot(),
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
}
});
it("GlobalPolicy with maxLocalCacheBytes / maxConcurrentFetches set", {
timeout: 5000,
}, () => {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: {
defaultPolicy: DEFAULT_POLICY,
maxLocalCacheBytes: 1_000_000,
maxConcurrentFetches: 4,
trustedKeyAuthorities: ["a", "b"],
},
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: emptyRoot(),
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
it("CdnFallbackConfig with optional uploadEndpoint serialises", {
timeout: 5000,
}, () => {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: emptyRoot(),
cdnFallback: {
baseUrl: "https://example.com",
pathScheme: "reverse-id",
levels: 3,
padding: 9,
uploadEndpoint: "https://upload.example.com",
},
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
it("CdnFallbackConfig WITHOUT uploadEndpoint covers optional false branch", {
timeout: 5000,
}, () => {
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: emptyRoot(),
cdnFallback: {
baseUrl: "https://example.com",
pathScheme: "reverse-id",
levels: 3,
padding: 9,
},
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
it("nested folder serialises with TAG_FOLDER recursion", {
timeout: 5000,
}, () => {
const inner: FolderTemplate = { name: "deep", children: [] };
const outer: FolderTemplate = { name: "/", children: [inner] };
const tpl: Omit<DriveTemplate, "serverSignature"> = {
schemaVersion: 1,
driveId: "d",
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
rootFolder: outer,
};
expect(canonicalSerialize(tpl).length).toBeGreaterThan(0);
});
});
describe("schema invocation coverage", () => {
it("DriveTemplateSchema rejects nonce of wrong length", {
timeout: 5000,
}, async () => {
const { DriveTemplateSchema } = await import("../src/index.js");
const r = DriveTemplateSchema.safeParse({
schemaVersion: 1,
driveId: "d",
rootFolder: { name: "/", children: [] },
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: new Uint8Array(8), // wrong length
serverSignature: new Uint8Array(64),
});
expect(r.success).toBe(false);
});
it("DriveTemplateSchema accepts a well-formed payload", {
timeout: 5000,
}, async () => {
const { DriveTemplateSchema } = await import("../src/index.js");
const r = DriveTemplateSchema.safeParse({
schemaVersion: 1,
driveId: "d",
rootFolder: { name: "/", children: [] },
policy: { defaultPolicy: DEFAULT_POLICY },
issuer: "00".repeat(32),
issuedAt: 1,
expiresAt: 1_000,
nonce: makeNonce(),
serverSignature: new Uint8Array(64),
});
expect(r.success).toBe(true);
});
it("FolderTemplateSchema parses recursive folder/file mix", {
timeout: 5000,
}, async () => {
const { FolderTemplateSchema } = await import("../src/index.js");
const r = FolderTemplateSchema.safeParse({
name: "/",
children: [
{
name: "subfolder",
children: [],
},
{
name: "file.bin",
fileLocId: "loc",
fileId: "fid",
size: 1,
totalChunks: 1,
chunkSize: 1024,
checksum: "blake3:abcd",
flags: DEFAULT_POLICY,
manifestKey: "k",
manifestSig: new Uint8Array(64),
},
],
});
expect(r.success).toBe(true);
});
});
describe("depth check accepts a folder containing a file", () => {
it("walk descends into folder, returns from file branch", {
timeout: 5000,
}, async () => {
const { assertFolderDepth } = await import("../src/index.js");
const tree: FolderTemplate = {
name: "/",
children: [baseFile],
};
expect(() => assertFolderDepth(tree)).not.toThrow();
});
});
describe("validate.replay LRU empty-on-evict edge", () => {
it("LRU eviction on size==capacity is a no-op pre-add", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const ctx = {
clock: () => Date.now(),
nonceLru: new TemplateNonceLru(2),
};
const tpl1 = signed(
{ issuer: keys.publicHex, nonce: makeNonce(1) },
keys
);
const tpl2 = signed(
{ issuer: keys.publicHex, nonce: makeNonce(2) },
keys
);
validateTemplate(tpl1, ctx);
validateTemplate(tpl2, ctx);
expect(ctx.nonceLru.size).toBe(2);
});
});

View File

@@ -0,0 +1,117 @@
/**
* DTP-17 — CDN reversed-ID URL convention.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { CdnFallbackConfig } from "../src/index.js";
import {
buildCdnChunkUrl,
DEFAULT_CDN_LEVELS,
DEFAULT_CDN_PADDING,
} from "../src/index.js";
const BASE: CdnFallbackConfig = {
baseUrl: "https://chunks.example.com",
pathScheme: "reverse-id",
levels: DEFAULT_CDN_LEVELS,
padding: DEFAULT_CDN_PADDING,
};
describe("DTP-17 — reverse-id URL convention", () => {
it("URL builder produces correct path for various levels and padding values", {
timeout: 5000,
}, () => {
const url = buildCdnChunkUrl(BASE, "abcdef1234567890", 47);
expect(url).toBe(
"https://chunks.example.com/0/9/8/abcdef1234567890_000000047.chunk"
);
});
it("levels override changes the directory depth", { timeout: 5000 }, () => {
const url = buildCdnChunkUrl(
{ ...BASE, levels: 1 },
"abcdef1234567890",
0
);
expect(url).toBe(
"https://chunks.example.com/0/abcdef1234567890_000000000.chunk"
);
});
it("levels=0 produces no directory prefix", { timeout: 5000 }, () => {
const url = buildCdnChunkUrl({ ...BASE, levels: 0 }, "abcdef", 0);
expect(url).toBe("https://chunks.example.com/abcdef_000000000.chunk");
});
it("padding override affects chunk-id zero-padding", {
timeout: 5000,
}, () => {
const url = buildCdnChunkUrl(
{ ...BASE, padding: 4 },
"abcdef1234567890",
12
);
expect(url).toBe(
"https://chunks.example.com/0/9/8/abcdef1234567890_0012.chunk"
);
});
it("trailing slash on baseUrl is stripped", { timeout: 5000 }, () => {
const url = buildCdnChunkUrl(
{ ...BASE, baseUrl: "https://chunks.example.com/" },
"abcdef1234567890",
1
);
expect(url.startsWith("https://chunks.example.com/0/9/8/")).toBe(true);
});
it("identical inputs produce byte-identical URLs", {
timeout: 5000,
}, () => {
const a = buildCdnChunkUrl(BASE, "abcdef", 99);
const b = buildCdnChunkUrl(BASE, "abcdef", 99);
expect(a).toBe(b);
});
it("rejects empty fileId", { timeout: 5000 }, () => {
expect(() => buildCdnChunkUrl(BASE, "", 0)).toThrow();
});
it("rejects negative chunkId", { timeout: 5000 }, () => {
expect(() => buildCdnChunkUrl(BASE, "abcdef", -1)).toThrow();
});
it("rejects non-integer chunkId", { timeout: 5000 }, () => {
expect(() => buildCdnChunkUrl(BASE, "abcdef", 1.5)).toThrow();
});
it("rejects negative levels", { timeout: 5000 }, () => {
expect(() =>
buildCdnChunkUrl({ ...BASE, levels: -1 }, "abcdef", 0)
).toThrow();
});
it("rejects non-integer levels", { timeout: 5000 }, () => {
expect(() =>
buildCdnChunkUrl({ ...BASE, levels: 1.5 }, "abcdef", 0)
).toThrow();
});
it("rejects levels > fileId length", { timeout: 5000 }, () => {
expect(() =>
buildCdnChunkUrl({ ...BASE, levels: 10 }, "abc", 0)
).toThrow();
});
it("rejects negative padding", { timeout: 5000 }, () => {
expect(() =>
buildCdnChunkUrl({ ...BASE, padding: -1 }, "abcdef", 0)
).toThrow();
});
it("rejects non-integer padding", { timeout: 5000 }, () => {
expect(() =>
buildCdnChunkUrl({ ...BASE, padding: 1.5 }, "abcdef", 0)
).toThrow();
});
});

View File

@@ -0,0 +1,170 @@
/**
* DTP-19 — CdnUploadToken issuance and verification.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { CdnUploadToken } from "../src/index.js";
import {
canonicalSerializeUploadToken,
type DriveTemplateError,
DTP_CODES,
signUploadToken,
UPLOAD_TOKEN_TTL_DEFAULT_MS,
UPLOAD_TOKEN_TTL_MAX_MS,
verifyUploadToken,
} from "../src/index.js";
import { makeKeys } from "./_helpers.js";
const NOW = 2_000_000_000;
function baseToken(): Omit<CdnUploadToken, "serverSignature" | "expiresAt"> {
return {
fileId: "f".repeat(64),
chunkId: 12,
chunkHash: "blake3:abcdef",
uploaderPubkey: "pubkey-hex",
};
}
describe("DTP-19 — CdnUploadToken", () => {
it("default TTL pinned at 60s; max at 5m", { timeout: 5000 }, () => {
expect(UPLOAD_TOKEN_TTL_DEFAULT_MS).toBe(60_000);
expect(UPLOAD_TOKEN_TTL_MAX_MS).toBe(300_000);
});
it("valid token accepted", { timeout: 5000 }, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
keys.secret,
{ clock: () => NOW }
);
expect(verifyUploadToken(tok, keys.publicHex, () => NOW + 1_000)).toBe(
true
);
});
it("expired token rejected", { timeout: 5000 }, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
keys.secret,
{ clock: () => NOW }
);
try {
verifyUploadToken(tok, keys.publicHex, () => NOW + 100_000);
throw new Error("expected expired");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
}
});
it("tampered token rejected", { timeout: 5000 }, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
keys.secret,
{ clock: () => NOW }
);
const tampered = { ...tok, chunkId: tok.chunkId + 1 };
try {
verifyUploadToken(tampered, keys.publicHex, () => NOW);
throw new Error("expected sig failure");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
}
});
it("token signed by wrong key rejected", { timeout: 5000 }, () => {
const k1 = makeKeys();
const k2 = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
k1.secret,
{ clock: () => NOW }
);
try {
verifyUploadToken(tok, k2.publicHex, () => NOW);
throw new Error("expected sig failure");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
}
});
it("invalid server pubkey hex rejected", { timeout: 5000 }, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
keys.secret,
{ clock: () => NOW }
);
try {
verifyUploadToken(tok, "not-hex", () => NOW);
throw new Error("expected pubkey rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
}
});
it("ttlMs out of range rejected at sign", { timeout: 5000 }, () => {
const keys = makeKeys();
expect(() =>
signUploadToken({ ...baseToken(), expiresAt: 0 }, keys.secret, {
clock: () => NOW,
ttlMs: 0,
})
).toThrow();
expect(() =>
signUploadToken({ ...baseToken(), expiresAt: 0 }, keys.secret, {
clock: () => NOW,
ttlMs: UPLOAD_TOKEN_TTL_MAX_MS + 1,
})
).toThrow();
});
it("canonicalSerializeUploadToken is deterministic", {
timeout: 5000,
}, () => {
const tok = { ...baseToken(), expiresAt: NOW + 30_000 };
const a = canonicalSerializeUploadToken(tok);
const b = canonicalSerializeUploadToken(tok);
expect(a).toEqual(b);
});
it("default options use Date.now() as clock", { timeout: 5000 }, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: 0 },
keys.secret
);
expect(tok.expiresAt).toBeGreaterThan(0);
});
it("malformed signature buffer triggers verify catch arm", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tok = signUploadToken(
{ ...baseToken(), expiresAt: NOW + 30_000 },
keys.secret,
{ clock: () => NOW }
);
const broken = { ...tok, serverSignature: new Uint8Array(8) };
try {
verifyUploadToken(broken, keys.publicHex, () => NOW);
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
}
});
});

View File

@@ -0,0 +1,48 @@
/**
* DTP error-codes spec conformance — registry frozen, every code
* usable to construct a `DriveTemplateError`, namespaced under `DTP.`.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { DriveTemplateError, DTP_CODES } from "../src/index.js";
describe("DTP_CODES is frozen", () => {
it("DTP_CODES is frozen", { timeout: 5000 }, () => {
expect(Object.isFrozen(DTP_CODES)).toBe(true);
});
it("every value namespaced under DTP.", { timeout: 5000 }, () => {
for (const v of Object.values(DTP_CODES)) {
expect(v).toMatch(/^DTP\./);
}
});
it("every code constructs a DriveTemplateError that preserves it", {
timeout: 5000,
}, () => {
for (const code of Object.values(DTP_CODES)) {
const e = new DriveTemplateError(code, "ok");
expect(e.code).toBe(code);
}
});
it("registry contains the eight pipeline + two operational codes", {
timeout: 5000,
}, () => {
const expected = [
"TEMPLATE_VERSION_UNSUPPORTED",
"TEMPLATE_EXPIRED",
"TEMPLATE_STALE",
"TEMPLATE_REPLAY",
"TEMPLATE_UNTRUSTED_ISSUER",
"TEMPLATE_SIG_INVALID",
"TEMPLATE_TOO_DEEP",
"TEMPLATE_MALFORMED",
"CDN_BAD_SIGNATURE",
"KEY_GRANT_REJECTED",
] as const;
for (const k of expected) {
expect(DTP_CODES).toHaveProperty(k);
}
});
});

View File

@@ -0,0 +1,75 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
DriveTemplateError,
DTP_CODES,
type DtpErrorCode,
} from "../src/error-codes.js";
describe("@suaveplan/drive-template — DTP_CODES registry", () => {
it("is frozen", { timeout: 5000 }, () => {
expect(Object.isFrozen(DTP_CODES)).toBe(true);
});
it("every code value starts with the DTP. namespace", {
timeout: 5000,
}, () => {
for (const value of Object.values(DTP_CODES)) {
expect(value).toMatch(/^DTP\./);
}
});
it("includes every documented rejection path", { timeout: 5000 }, () => {
const expected = [
"TEMPLATE_VERSION_UNSUPPORTED",
"TEMPLATE_EXPIRED",
"TEMPLATE_STALE",
"TEMPLATE_REPLAY",
"TEMPLATE_UNTRUSTED_ISSUER",
"TEMPLATE_SIG_INVALID",
"TEMPLATE_TOO_DEEP",
"TEMPLATE_MALFORMED",
"CDN_BAD_SIGNATURE",
"KEY_GRANT_REJECTED",
] as const;
for (const key of expected) {
expect(DTP_CODES).toHaveProperty(key);
}
});
});
describe("@suaveplan/drive-template — DriveTemplateError class", () => {
it("preserves the code", { timeout: 5000 }, () => {
const err = new DriveTemplateError(
DTP_CODES.TEMPLATE_EXPIRED,
"test message"
);
expect(err.code).toBe(DTP_CODES.TEMPLATE_EXPIRED);
expect(err.message).toBe("test message");
});
it("threads cause and context through SuaveplanError options", {
timeout: 5000,
}, () => {
const cause = new Error("inner");
const err = new DriveTemplateError(
DTP_CODES.TEMPLATE_SIG_INVALID,
"bad sig",
{
cause,
context: { issuer: "alice" },
}
);
expect(err.cause).toBe(cause);
expect(err.context).toEqual({ issuer: "alice" });
});
it("permits all codes via DtpErrorCode union", { timeout: 5000 }, () => {
const codes: DtpErrorCode[] = Object.values(DTP_CODES);
expect(codes.length).toBeGreaterThanOrEqual(10);
for (const code of codes) {
const err = new DriveTemplateError(code, "ok");
expect(err.code).toBe(code);
}
});
});

View File

@@ -0,0 +1,228 @@
/**
* DTP-21 — KeyGrantEnvelope validator (issuer / recipient / signature
* / expiry checks in spec-mandated order).
*/
import { sign } from "@suaveplan/crypto-curve/ed25519";
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { KeyGrantPayload } from "../src/index.js";
import {
canonicalSerializeKeyGrant,
type DriveTemplateError,
DTP_CODES,
validateKeyGrant,
} from "../src/index.js";
import { makeKeys } from "./_helpers.js";
const NOW = 2_000_000_000;
function makeGrant(
issuerHex: string,
recipientHex: string,
issuerSecret: Uint8Array,
overrides: Partial<KeyGrantPayload> = {}
): KeyGrantPayload {
const base: Omit<KeyGrantPayload, "issuerSignature"> = {
fileId: "f".repeat(64),
recipient: recipientHex,
wrappedKey: new Uint8Array(48).fill(7),
issuer: issuerHex,
expiresAt: NOW + 60_000,
...overrides,
};
const canonical = canonicalSerializeKeyGrant(base);
const issuerSignature = sign(canonical, issuerSecret);
return { ...base, issuerSignature, ...overrides };
}
describe("DTP-21 — key-grant validator", () => {
it("valid grant passes", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant = makeGrant(
issuer.publicHex,
recipient.publicHex,
issuer.secret
);
expect(
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
})
).toBe(true);
});
it("issuer mismatch rejected (DMP-21 step 1)", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant = makeGrant(
issuer.publicHex,
recipient.publicHex,
issuer.secret
);
try {
validateKeyGrant(grant, {
expectedKeyAuthority: "different-authority",
ourPubkey: recipient.publicHex,
clock: () => NOW,
});
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
});
it("recipient mismatch rejected (step 2)", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const someoneElse = makeKeys();
const grant = makeGrant(
issuer.publicHex,
recipient.publicHex,
issuer.secret
);
try {
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: someoneElse.publicHex,
clock: () => NOW,
});
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
});
it("invalid issuer hex rejected", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant: KeyGrantPayload = {
fileId: "f",
recipient: recipient.publicHex,
wrappedKey: new Uint8Array(8),
issuer: "not-hex-zzz",
issuerSignature: new Uint8Array(64),
expiresAt: NOW + 60_000,
};
try {
validateKeyGrant(grant, {
expectedKeyAuthority: "not-hex-zzz",
ourPubkey: recipient.publicHex,
clock: () => NOW,
});
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
// Suppress unused-issuer lint
void issuer;
});
it("bad signature rejected (step 3)", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant = makeGrant(
issuer.publicHex,
recipient.publicHex,
issuer.secret
);
const tampered = {
...grant,
wrappedKey: new Uint8Array(48).fill(0xff),
};
try {
validateKeyGrant(tampered, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
});
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
});
it("expired grant rejected (step 4)", { timeout: 5000 }, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant = makeGrant(
issuer.publicHex,
recipient.publicHex,
issuer.secret,
{ expiresAt: NOW - 1 }
);
try {
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
});
throw new Error("expected expired");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
});
it("malformed signature buffer triggers verify catch arm", {
timeout: 5000,
}, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const grant: KeyGrantPayload = {
fileId: "f",
recipient: recipient.publicHex,
wrappedKey: new Uint8Array(8),
issuer: issuer.publicHex,
issuerSignature: new Uint8Array(8),
};
try {
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
});
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
}
});
it("grant without expiresAt accepted indefinitely", {
timeout: 5000,
}, () => {
const issuer = makeKeys();
const recipient = makeKeys();
// Build without expiresAt then re-sign over the same canonical bytes.
const base: Omit<KeyGrantPayload, "issuerSignature"> = {
fileId: "f".repeat(64),
recipient: recipient.publicHex,
wrappedKey: new Uint8Array(48).fill(7),
issuer: issuer.publicHex,
};
const canonical = canonicalSerializeKeyGrant(base);
const grant: KeyGrantPayload = {
...base,
issuerSignature: sign(canonical, issuer.secret),
};
expect(
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
})
).toBe(true);
});
});

View File

@@ -0,0 +1,66 @@
/**
* DTP-05 step 2 — TEMPLATE_EXPIRED rejection.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
type DriveTemplateError,
DTP_CODES,
TemplateNonceLru,
validateTemplate,
} from "../src/index.js";
import { makeKeys, signed } from "./_helpers.js";
describe("DTP-05 step 2 — expired templates", () => {
it("template past expiresAt rejected with TEMPLATE_EXPIRED", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const NOW = 2_000_000_000;
const tpl = signed(
{
issuer: keys.publicHex,
issuedAt: NOW - 1_000,
expiresAt: NOW - 1, // already expired
},
keys
);
const ctx = {
clock: () => NOW,
nonceLru: new TemplateNonceLru(),
};
try {
validateTemplate(tpl, ctx);
throw new Error("expected EXPIRED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_EXPIRED
);
}
});
it("template at exactly expiresAt rejected (>=)", { timeout: 5000 }, () => {
const keys = makeKeys();
const NOW = 2_000_000_000;
const tpl = signed(
{
issuer: keys.publicHex,
issuedAt: NOW - 1_000,
expiresAt: NOW,
},
keys
);
const ctx = {
clock: () => NOW,
nonceLru: new TemplateNonceLru(),
};
try {
validateTemplate(tpl, ctx);
throw new Error("expected EXPIRED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_EXPIRED
);
}
});
});

View File

@@ -0,0 +1,157 @@
/**
* DTP-02 / DTP-09 / DTP-26 — schema-version pinning + per-file
* consistency invariants + structured error context.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import type {
DriveTemplate,
FileTemplate,
FolderTemplate,
} from "../src/index.js";
import {
assertFile,
assertFolderFileInvariants,
type DriveTemplateError,
DTP_CODES,
TemplateNonceLru,
validateTemplate,
} from "../src/index.js";
import { DEFAULT_POLICY, makeKeys, signed } from "./_helpers.js";
const baseFile: FileTemplate = {
name: "x.bin",
fileLocId: "loc-1",
fileId: "f".repeat(64),
size: 100,
totalChunks: 1,
chunkSize: 1024,
checksum: "blake3:abcd",
flags: DEFAULT_POLICY,
manifestKey: "manifest:f",
manifestSig: new Uint8Array(64).fill(1),
};
describe("DTP-02 — schema version pinned to 1", () => {
it("schemaVersion ≠ 1 rejected with TEMPLATE_VERSION_UNSUPPORTED", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signed({ issuer: keys.publicHex }, keys);
// Cast through unknown to bypass the literal-1 type guard.
const broken = { ...tpl, schemaVersion: 2 } as unknown as DriveTemplate;
try {
validateTemplate(broken, {
clock: () => Date.now(),
nonceLru: new TemplateNonceLru(),
});
throw new Error("expected VERSION_UNSUPPORTED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_VERSION_UNSUPPORTED
);
}
});
});
describe("DTP-09 — per-file consistency invariants", () => {
it("chunkSize 164 rejected", { timeout: 5000 }, () => {
try {
assertFile({ ...baseFile, chunkSize: 164 }, "/x.bin");
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
expect((err as DriveTemplateError).context).toMatchObject({
file: "/x.bin",
});
}
});
it("totalChunks insufficient for size rejected", { timeout: 5000 }, () => {
try {
assertFile(
{ ...baseFile, totalChunks: 1, chunkSize: 1024, size: 4096 },
"/x.bin"
);
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
}
});
it("valid file passes through assertFile (positive path)", {
timeout: 5000,
}, () => {
expect(() => assertFile(baseFile, "/x.bin")).not.toThrow();
});
it("empty manifestKey rejected", { timeout: 5000 }, () => {
try {
assertFile({ ...baseFile, manifestKey: "" }, "/x.bin");
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
}
});
it("malformed checksum rejected", { timeout: 5000 }, () => {
try {
assertFile({ ...baseFile, checksum: "md5:abc" }, "/x.bin");
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
}
});
it("empty manifestSig rejected", { timeout: 5000 }, () => {
try {
assertFile(
{ ...baseFile, manifestSig: new Uint8Array(0) },
"/x.bin"
);
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
}
});
it("nested folder: deepest file's failure surfaces with full path", {
timeout: 5000,
}, () => {
const tree: FolderTemplate = {
name: "/",
children: [
{
name: "photos",
children: [
{
name: "wedding",
children: [{ ...baseFile, chunkSize: 164 }],
},
],
},
],
};
try {
assertFolderFileInvariants(tree);
throw new Error("expected MALFORMED");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_MALFORMED
);
const ctx = (err as DriveTemplateError).context as { file: string };
expect(ctx.file).toContain("photos");
expect(ctx.file).toContain("wedding");
}
});
});

View File

@@ -0,0 +1,94 @@
/**
* DTP-06 — replay protection via per-client nonce LRU.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
type DriveTemplateError,
DTP_CODES,
TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY,
TemplateNonceLru,
validateTemplate,
} from "../src/index.js";
import { makeKeys, makeNonce, signed } from "./_helpers.js";
describe("DTP-06 — replay protection", () => {
it("hydrate with same (driveId, nonce) twice rejects on second call", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const lru = new TemplateNonceLru();
const tpl = signed(
{
issuer: keys.publicHex,
nonce: makeNonce(0x42),
},
keys
);
const ctx = { clock: () => Date.now(), nonceLru: lru };
validateTemplate(tpl, ctx);
try {
validateTemplate(tpl, ctx);
throw new Error("expected REPLAY rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_REPLAY
);
}
});
it("LRU capacity is the documented default", { timeout: 5000 }, () => {
expect(TEMPLATE_NONCE_LRU_DEFAULT_CAPACITY).toBe(1024);
});
it("LRU evicts oldest entry past capacity", { timeout: 5000 }, () => {
const lru = new TemplateNonceLru(2);
const a = makeNonce(1);
const b = makeNonce(2);
const c = makeNonce(3);
lru.remember("d", a);
lru.remember("d", b);
lru.remember("d", c);
expect(lru.has("d", a)).toBe(false);
expect(lru.has("d", b)).toBe(true);
expect(lru.has("d", c)).toBe(true);
expect(lru.size).toBe(2);
});
it("LRU reinserts on duplicate to refresh recency", {
timeout: 5000,
}, () => {
const lru = new TemplateNonceLru(2);
const a = makeNonce(1);
const b = makeNonce(2);
lru.remember("d", a);
lru.remember("d", b);
lru.remember("d", a); // refresh
const c = makeNonce(3);
lru.remember("d", c); // evicts b, not a
expect(lru.has("d", a)).toBe(true);
expect(lru.has("d", b)).toBe(false);
expect(lru.has("d", c)).toBe(true);
});
it("LRU rejects non-positive capacity", { timeout: 5000 }, () => {
expect(() => new TemplateNonceLru(0)).toThrow();
expect(() => new TemplateNonceLru(-1)).toThrow();
expect(() => new TemplateNonceLru(1.5)).toThrow();
});
it("clear() empties the LRU", { timeout: 5000 }, () => {
const lru = new TemplateNonceLru(8);
lru.remember("d", makeNonce(1));
expect(lru.size).toBe(1);
lru.clear();
expect(lru.size).toBe(0);
});
it("capacity getter exposes the configured cap", {
timeout: 5000,
}, () => {
const lru = new TemplateNonceLru(99);
expect(lru.capacity).toBe(99);
});
});

View File

@@ -0,0 +1,83 @@
/**
* DTP-01 / DTP-03 — DriveTemplate shape + canonical serialization
* deterministic across calls.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import { canonicalSerialize } from "../src/codec/canonical.js";
import type { FileTemplate, FolderTemplate } from "../src/index.js";
import { signTemplate, verifyTemplateSignature } from "../src/index.js";
import { buildTemplate, DEFAULT_POLICY, makeKeys } from "./_helpers.js";
const FILE: FileTemplate = {
name: "photo.jpg",
fileLocId: "loc-1",
fileId: "f".repeat(64),
size: 1024,
totalChunks: 1,
chunkSize: 1024,
checksum: "blake3:abcd",
flags: DEFAULT_POLICY,
manifestKey: "manifest:f",
manifestSig: new Uint8Array(64).fill(1),
};
const ROOT: FolderTemplate = {
name: "/",
children: [{ name: "photos", children: [FILE] }],
};
describe("DTP-01 — DriveTemplate shape", () => {
it("DriveTemplate with all required fields encodes/decodes", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex, rootFolder: ROOT }),
keys.secret
);
expect(verifyTemplateSignature(tpl)).toBe(true);
});
it("Minimal empty-rootFolder template signs and verifies", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
expect(verifyTemplateSignature(tpl)).toBe(true);
});
});
describe("DTP-03 — canonicalSerialize is deterministic", () => {
it("canonicalSerialize is deterministic across calls", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = buildTemplate({ issuer: keys.publicHex, rootFolder: ROOT });
const a = canonicalSerialize(tpl);
const b = canonicalSerialize(tpl);
expect(a).toEqual(b);
});
it("Field-reorder produces identical bytes", { timeout: 5000 }, () => {
const keys = makeKeys();
const a = buildTemplate({ issuer: keys.publicHex, rootFolder: ROOT });
// Recreate with fields in a different in-memory order — the
// canonical encoder's fixed schema-driven order means the bytes
// are still identical.
const reordered = {
policy: a.policy,
schemaVersion: a.schemaVersion,
issuedAt: a.issuedAt,
issuer: a.issuer,
driveId: a.driveId,
expiresAt: a.expiresAt,
nonce: a.nonce,
rootFolder: a.rootFolder,
} as typeof a;
expect(canonicalSerialize(a)).toEqual(canonicalSerialize(reordered));
});
});

View File

@@ -0,0 +1,68 @@
/**
* DTP-05 step 3 / DTP-07 — issuance window enforcement.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
type DriveTemplateError,
DTP_CODES,
ISSUANCE_WINDOW_MS,
TemplateNonceLru,
validateTemplate,
} from "../src/index.js";
import { makeKeys, signed } from "./_helpers.js";
describe("DTP-07 — 24-hour issuance window", () => {
it("ISSUANCE_WINDOW_MS pinned at 24h", { timeout: 5000 }, () => {
expect(ISSUANCE_WINDOW_MS).toBe(24 * 60 * 60 * 1000);
});
it("template issued 24h+1s ago rejected as STALE", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const NOW = 5_000_000_000;
const issuedAt = NOW - ISSUANCE_WINDOW_MS - 1_000;
const tpl = signed(
{
issuer: keys.publicHex,
issuedAt,
expiresAt: NOW + 60_000,
},
keys
);
const ctx = {
clock: () => NOW,
nonceLru: new TemplateNonceLru(),
};
try {
validateTemplate(tpl, ctx);
throw new Error("expected STALE");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_STALE
);
}
});
it("template issued just under 24h ago accepted", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const NOW = 5_000_000_000;
const issuedAt = NOW - ISSUANCE_WINDOW_MS + 1_000;
const tpl = signed(
{
issuer: keys.publicHex,
issuedAt,
expiresAt: NOW + 60_000,
},
keys
);
const ctx = {
clock: () => NOW,
nonceLru: new TemplateNonceLru(),
};
expect(() => validateTemplate(tpl, ctx)).not.toThrow();
});
});

View File

@@ -0,0 +1,152 @@
/**
* DTP-04 — single bit flip in any field causes signature verification
* to fail. Property test using fast-check picks a random byte position
* inside the signed canonical bytes and flips one bit; verification
* MUST throw `DRIVE.TEMPLATE_SIG_INVALID`.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import fc from "fast-check";
import { canonicalSerialize } from "../src/codec/canonical.js";
import {
DriveTemplateError,
DTP_CODES,
signTemplate,
verifyTemplateSignature,
} from "../src/index.js";
import { buildTemplate, makeKeys, makeNonce } from "./_helpers.js";
describe("DTP-04 — server signature covers every field", () => {
it("single bit flip in any field causes signature failure", {
timeout: 10_000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
expect(verifyTemplateSignature(tpl)).toBe(true);
// Property: for any byte position inside the canonical bytes,
// a flipped bit must make verification fail.
const canonical = canonicalSerialize(tpl);
fc.assert(
fc.property(
fc.integer({ min: 0, max: canonical.length - 1 }),
fc.integer({ min: 0, max: 7 }),
(bytePos, bitPos) => {
// Flip the bit in the nonce field (byte index inside nonce).
const flippedNonce = new Uint8Array(tpl.nonce);
if (bytePos < flippedNonce.length) {
flippedNonce[bytePos] =
(flippedNonce[bytePos] ?? 0) ^ (1 << bitPos);
const tampered = { ...tpl, nonce: flippedNonce };
try {
verifyTemplateSignature(tampered);
return false; // verification should not pass
} catch (err) {
return (
err instanceof DriveTemplateError &&
err.code === DTP_CODES.TEMPLATE_SIG_INVALID
);
}
}
return true;
}
),
{ numRuns: 30 }
);
});
it("flipping issuedAt fails verification", { timeout: 5000 }, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
const tampered = { ...tpl, issuedAt: tpl.issuedAt + 1 };
try {
verifyTemplateSignature(tampered);
throw new Error("expected sig failure");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
}
});
it("flipping nonce fails verification", { timeout: 5000 }, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex, nonce: makeNonce(0xab) }),
keys.secret
);
const tampered = { ...tpl, nonce: makeNonce(0xcd) };
try {
verifyTemplateSignature(tampered);
throw new Error("expected sig failure");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
}
});
it("verification under wrong issuer key fails", { timeout: 5000 }, () => {
const k1 = makeKeys();
const k2 = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: k1.publicHex }),
k1.secret
);
const wrongIssuerTpl = { ...tpl, issuer: k2.publicHex };
try {
verifyTemplateSignature(wrongIssuerTpl);
throw new Error("expected sig failure");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
}
});
it("invalid issuer hex rejected with TEMPLATE_SIG_INVALID", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
const broken = { ...tpl, issuer: "not-hex-zzz" };
try {
verifyTemplateSignature(broken);
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
}
});
it("malformed signature buffer (wrong length) rejected via catch arm", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
const broken = {
...tpl,
serverSignature: new Uint8Array(8),
};
try {
verifyTemplateSignature(broken);
throw new Error("expected rejection");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
}
});
});

View File

@@ -0,0 +1,43 @@
/**
* DTP-08 — folder depth cap.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { FolderTemplate } from "../src/index.js";
import {
assertFolderDepth,
type DriveTemplateError,
DTP_CODES,
MAX_FOLDER_DEPTH,
} from "../src/index.js";
function chain(depth: number): FolderTemplate {
let node: FolderTemplate = { name: `level-${depth}`, children: [] };
for (let i = depth - 1; i >= 0; i--) {
node = { name: `level-${i}`, children: [node] };
}
return node;
}
describe("DTP-08 — folder depth cap", () => {
it("MAX_FOLDER_DEPTH pinned at 32", { timeout: 5000 }, () => {
expect(MAX_FOLDER_DEPTH).toBe(32);
});
it("depth-32 nested folder accepted", { timeout: 5000 }, () => {
const tree = chain(32);
expect(() => assertFolderDepth(tree)).not.toThrow();
});
it("depth-33 nested folder rejected", { timeout: 5000 }, () => {
const tree = chain(33);
try {
assertFolderDepth(tree);
throw new Error("expected TOO_DEEP");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_TOO_DEEP
);
}
});
});

View File

@@ -0,0 +1,98 @@
/**
* DTP-05 step 5 — TEMPLATE_UNTRUSTED_ISSUER rejection.
*/
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
type DriveTemplateError,
DTP_CODES,
signTemplate,
TemplateNonceLru,
validateTemplate,
} from "../src/index.js";
import { buildTemplate, emptyRoot, makeKeys } from "./_helpers.js";
describe("DTP-05 step 5 — trusted issuer gate", () => {
it("issuer not in trustedKeyAuthorities rejected", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
{
...buildTemplate({ issuer: keys.publicHex }),
policy: {
defaultPolicy: {
autoDownload: false,
meshAllowed: true,
cdnAllowed: true,
},
trustedKeyAuthorities: ["someone-else"],
},
},
keys.secret
);
const ctx = {
clock: () => Date.now(),
nonceLru: new TemplateNonceLru(),
};
try {
validateTemplate(tpl, ctx);
throw new Error("expected UNTRUSTED_ISSUER");
} catch (err) {
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_UNTRUSTED_ISSUER
);
}
});
it("empty trustedKeyAuthorities permits any issuer (no allowlist)", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
{
...buildTemplate({ issuer: keys.publicHex }),
policy: {
defaultPolicy: {
autoDownload: false,
meshAllowed: true,
cdnAllowed: true,
},
trustedKeyAuthorities: [],
},
},
keys.secret
);
const ctx = {
clock: () => Date.now(),
nonceLru: new TemplateNonceLru(),
};
expect(() => validateTemplate(tpl, ctx)).not.toThrow();
});
it("issuer in allowlist accepted", { timeout: 5000 }, () => {
const keys = makeKeys();
const tpl = signTemplate(
{
...buildTemplate({
issuer: keys.publicHex,
rootFolder: emptyRoot(),
}),
policy: {
defaultPolicy: {
autoDownload: false,
meshAllowed: true,
cdnAllowed: true,
},
trustedKeyAuthorities: [keys.publicHex],
},
},
keys.secret
);
const ctx = {
clock: () => Date.now(),
nonceLru: new TemplateNonceLru(),
};
expect(() => validateTemplate(tpl, ctx)).not.toThrow();
});
});

View File

@@ -0,0 +1,117 @@
/**
* Forward-defensive catch arms in `codec/verify`, `key/grant-validator`,
* and `cdn/upload-token` — exercised here by injecting a throwing
* `verifyFn` so the catch path runs in production code rather than
* being papered over with `istanbul ignore` annotations. Per AGENTS.md
* §7a, defensive guards must have a real test exercising them.
*/
import { sign } from "@suaveplan/crypto-curve/ed25519";
import { describe, expect, it } from "@suaveplan/testing/runner";
import type { KeyGrantPayload } from "../src/index.js";
import {
canonicalSerializeKeyGrant,
DriveTemplateError,
DTP_CODES,
signTemplate,
signUploadToken,
validateKeyGrant,
verifyTemplateSignature,
verifyUploadToken,
} from "../src/index.js";
import { buildTemplate, makeKeys } from "./_helpers.js";
const NOW = 2_000_000_000;
function throwingVerify(): never {
throw new Error("synthetic verify failure");
}
describe("AGENTS §7a — verify catch arms", () => {
it("verifyTemplateSignature surfaces a thrown verifier as TEMPLATE_SIG_INVALID", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tpl = signTemplate(
buildTemplate({ issuer: keys.publicHex }),
keys.secret
);
try {
verifyTemplateSignature(tpl, throwingVerify);
throw new Error("expected SIG_INVALID");
} catch (err) {
expect(err).toBeInstanceOf(DriveTemplateError);
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.TEMPLATE_SIG_INVALID
);
expect((err as DriveTemplateError).message).toContain(
"synthetic verify failure"
);
}
});
it("validateKeyGrant surfaces a thrown verifier as KEY_GRANT_REJECTED", {
timeout: 5000,
}, () => {
const issuer = makeKeys();
const recipient = makeKeys();
const base: Omit<KeyGrantPayload, "issuerSignature"> = {
fileId: "f".repeat(64),
recipient: recipient.publicHex,
wrappedKey: new Uint8Array(48).fill(7),
issuer: issuer.publicHex,
expiresAt: NOW + 60_000,
};
const canonical = canonicalSerializeKeyGrant(base);
const grant: KeyGrantPayload = {
...base,
issuerSignature: sign(canonical, issuer.secret),
};
try {
validateKeyGrant(grant, {
expectedKeyAuthority: issuer.publicHex,
ourPubkey: recipient.publicHex,
clock: () => NOW,
verifyFn: throwingVerify,
});
throw new Error("expected KEY_GRANT_REJECTED");
} catch (err) {
expect(err).toBeInstanceOf(DriveTemplateError);
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.KEY_GRANT_REJECTED
);
expect((err as DriveTemplateError).message).toContain(
"synthetic verify failure"
);
}
});
it("verifyUploadToken surfaces a thrown verifier as CDN_BAD_SIGNATURE", {
timeout: 5000,
}, () => {
const keys = makeKeys();
const tok = signUploadToken(
{
fileId: "f".repeat(64),
chunkId: 0,
chunkHash: "blake3:abc",
uploaderPubkey: "uploader",
expiresAt: NOW + 30_000,
},
keys.secret,
{ clock: () => NOW }
);
try {
verifyUploadToken(tok, keys.publicHex, () => NOW, throwingVerify);
throw new Error("expected CDN_BAD_SIGNATURE");
} catch (err) {
expect(err).toBeInstanceOf(DriveTemplateError);
expect((err as DriveTemplateError).code).toBe(
DTP_CODES.CDN_BAD_SIGNATURE
);
expect((err as DriveTemplateError).message).toContain(
"synthetic verify failure"
);
}
});
});