/** * 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 { const base: Omit = { 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 = { 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); }); });