153 lines
5.3 KiB
TypeScript
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
|
|
);
|
|
}
|
|
});
|
|
});
|