Initial Commit
This commit is contained in:
@@ -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);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user