Initial Commit
This commit is contained in:
@@ -0,0 +1,152 @@
|
||||
/**
|
||||
* 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
|
||||
);
|
||||
}
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user