fix(pe): validate SignedData version INTEGER content (#10054)

fix(pe): validate SignedData version integer encoding
This commit is contained in:
morluto authored and GitHub committed 2026-11-11 17:13:40 +08:00
1 parent 4a02ca95de
commit 8c40ba78de
2 files changed
+56 -5

No files matched your search

+10 -5
View File
@@ -230,11 +230,7 @@ const decodeDirectoryString = (
// X.690 8.3.1 and 8.3.2 hold for BER as well as DER: an INTEGER has at least
// one content octet, and its first nine bits are never all zeros or all ones.
// Padded encodings would otherwise collapse onto the minimal serial's hex.
const integerHex = (
content: Buffer,
budget: DecodedNameBudget,
label: string,
): string => {
const requireIntegerContent = (content: Buffer, label: string): void => {
if (content.length === 0) return fail(`${label} INTEGER is empty.`);
const second = content[1];
if (
@@ -243,6 +239,14 @@ const integerHex = (
(content[0] === 0xff && second >= 0x80))
)
return fail(`${label} INTEGER is not minimally encoded.`);
};
const integerHex = (
content: Buffer,
budget: DecodedNameBudget,
label: string,
): string => {
requireIntegerContent(content, label);
let start = 0;
while (start < content.length - 1 && content[start] === 0) start += 1;
chargeDecodedName(budget, (content.length - start) * 2);
@@ -716,6 +720,7 @@ const parseSignedData = (
const [version, digestSet, encapContentInfo] = children;
if (version === undefined || version.tag !== 0x02)
return fail("SignedData is missing version.");
requireIntegerContent(version.content, "SignedData version");
if (digestSet === undefined || digestSet.tag !== 0x31)
return fail("SignedData is missing digestAlgorithms.");
if (
+46
View File
@@ -1274,3 +1274,49 @@ it("rejects malformed and repeated certificate extensions", () => {
).subject.text,
).toBe("commonName=Fixture");
});
it.each([[], [0x00, 0x01], [0xff, 0xff]].map((version) => ({ version })))(
"rejects malformed SignedData version INTEGER content $version",
({ version }) => {
const original = pkcs7SignedDataFixture();
const signed = readAsn1(original).children[1]!.children[0]!;
const payload = contentInfo(
derSeq(
der(0x02, Buffer.from(version)),
...signed.children
.slice(1)
.map((node) => original.subarray(node.start, node.end)),
),
);
expect(() => parse(peCertificateFixture(false, payload))).toThrow(
/SignedData version INTEGER/u,
);
},
);
it.each([
{ name: "short form", version: Buffer.from([0x02, 0x01, 0x01]) },
{ name: "BER long form", version: Buffer.from([0x02, 0x81, 0x01, 0x01]) },
])(
"retains a valid SignedData version with $name length encoding",
({ version }) => {
const original = pkcs7SignedDataFixture();
const signed = readAsn1(original).children[1]!.children[0]!;
const payload = contentInfo(
derSeq(
version,
...signed.children
.slice(1)
.map((node) => original.subarray(node.start, node.end)),
),
);
const report = parse(peCertificateFixture(false, payload));
expect(report.coverage).toMatchObject({
status: "complete",
certificates: 1,
});
expect(report.entries[0]?.pkcs7?.signers[0]).toMatchObject({
serial_hex: "11223344",
});
},
);