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

153 lines
5.3 KiB
TypeScript

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