Files
suaveplan/packages/networking/drive/drive-template/tests/key-grant-validator.test.ts
2026-05-27 23:47:53 +01:00

229 lines
7.2 KiB
TypeScript

/**
* 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);
});
});