fix(asn1): decode four-byte BER lengths unsigned

Accumulate length octets arithmetically so a four-octet length with bit 31 set remains a positive unsigned value and is rejected by the containing-range check rather than becoming a negative length.
This commit is contained in:
morluto
2026-10-23 18:46:54 +08:00
parent ef05713d86
commit 3a2074b633
4 changed files with 42 additions and 2 deletions
@@ -244,3 +244,24 @@ it("rejects non-OCTET children inside a constructed signature", () => {
).kind,
).toBe("malformed-pkcs7");
});
it.each([
["primitive", 0x04],
["constructed", 0x24],
] as const)(
"rejects a high-bit four-octet BER length on a %s signature field",
(_kind, tag) => {
const signature = Uint8Array.of(tag, 0x84, 0x80, 0, 0, 0);
expect(
errorOf(
signedDataBlock({
certificates: [LEAF_CERTIFICATE],
signerInfos: [signerInfo({ names: LEAF_CERTIFICATE, signature })],
}),
),
).toEqual({
kind: "malformed-pkcs7",
detail: "ASN.1 value exceeds its parent.",
});
},
);
+2 -1
View File
@@ -90,7 +90,8 @@ export class Asn1BerReader {
for (let index = 0; index < width; index += 1) {
const byte = this.bytes[cursor];
if (byte === undefined) return fail("Truncated ASN.1 length.");
length = (length << 8) | byte;
// Bitwise shifts sign-extend a four-octet length with bit 31 set.
length = length * 256 + byte;
cursor += 1;
}
}
+17
View File
@@ -94,6 +94,23 @@ it("reads BER indefinite constructed ContentInfo and preserves certificate bytes
);
});
it.each([
["primitive", 0x04],
["constructed", 0x24],
] as const)(
"rejects a high-bit four-octet BER length on a %s SignerInfo signature",
(_kind, tag) => {
const payload = pkcs7SignedDataFixture(
"REA Fixture",
"REA Tests",
Buffer.from([tag, 0x84, 0x80, 0, 0, 0]),
);
expect(() => parse(peCertificateFixture(false, payload))).toThrow(
/PKCS#7 signedData is malformed: ASN\.1 value exceeds its parent\./u,
);
},
);
it("matches nested indefinite constructed values and rejects invalid EOC forms", () => {
const nested = readAsn1(
Buffer.from([0x30, 0x80, 0x30, 0x80, 0x05, 0x00, 0x00, 0x00, 0x00, 0x00]),
+2 -1
View File
@@ -66,6 +66,7 @@ const algorithm = (identifier: Buffer): Buffer => seq(identifier, nul());
export const pkcs7SignedDataFixture = (
commonName = "REA Fixture",
organization = "REA Tests",
signature = octet(Buffer.from("sig")),
): Buffer => {
const serial = Buffer.from([0x11, 0x22, 0x33, 0x44]);
const subject = name(commonName, organization);
@@ -92,7 +93,7 @@ export const pkcs7SignedDataFixture = (
seq(subject, integer(serial)),
algorithm(SHA256),
algorithm(SHA256_RSA),
octet(Buffer.from("sig")),
signature,
);
const signedData = seq(
integer(1),