fix(pe): reject empty and non-minimal certificate serial INTEGERs (#8836)
This commit is contained in:
2 files changed
+112
-3
No files matched your search
@@ -214,7 +214,22 @@ const decodeDirectoryString = (
|
||||
}
|
||||
};
|
||||
|
||||
const integerHex = (content: Buffer, budget: DecodedNameBudget): string => {
|
||||
// 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 => {
|
||||
if (content.length === 0) return fail(`${label} INTEGER is empty.`);
|
||||
const second = content[1];
|
||||
if (
|
||||
second !== undefined &&
|
||||
((content[0] === 0x00 && second < 0x80) ||
|
||||
(content[0] === 0xff && second >= 0x80))
|
||||
)
|
||||
return fail(`${label} INTEGER is not minimally encoded.`);
|
||||
let start = 0;
|
||||
while (start < content.length - 1 && content[start] === 0) start += 1;
|
||||
chargeDecodedName(budget, (content.length - start) * 2);
|
||||
@@ -444,7 +459,11 @@ export const parseCertificate = (
|
||||
return {
|
||||
start: node.start,
|
||||
end: node.end,
|
||||
serial_hex: integerHex(serial.content, names),
|
||||
serial_hex: integerHex(
|
||||
serial.content,
|
||||
names,
|
||||
"TBSCertificate serialNumber",
|
||||
),
|
||||
issuer: decodeName(issuer, names),
|
||||
subject: decodeName(subject, names),
|
||||
not_before: validityTime(notBefore, names),
|
||||
@@ -484,7 +503,11 @@ const signerIdentifier = (
|
||||
return fail("SignerInfo issuerAndSerialNumber is truncated.");
|
||||
return {
|
||||
issuer: decodeName(name, names),
|
||||
serial_hex: integerHex(serial.content, names),
|
||||
serial_hex: integerHex(
|
||||
serial.content,
|
||||
names,
|
||||
"SignerInfo issuerAndSerialNumber serialNumber",
|
||||
),
|
||||
subject_key_identifier_hex: null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -515,6 +515,92 @@ it("keeps subjectKeyIdentifier bytes on the signer", () => {
|
||||
).toThrow(/decoded budget/u);
|
||||
});
|
||||
|
||||
// X.690 8.3.1 and 8.3.2 apply under BER as well as DER.
|
||||
const malformedSerials = [
|
||||
[],
|
||||
[0x00, 0x00],
|
||||
[0x00, 0x7f],
|
||||
[0x00, 0x00, 0x80],
|
||||
[0xff, 0x80],
|
||||
[0xff, 0xff],
|
||||
] as const;
|
||||
const minimalSerials = [
|
||||
[[0x00], "00"],
|
||||
[[0x7f], "7f"],
|
||||
[[0x00, 0x80], "80"],
|
||||
[[0xff], "ff"],
|
||||
[[0xff, 0x7f], "ff7f"],
|
||||
[[0x11, 0x22, 0x33, 0x44], "11223344"],
|
||||
] as const;
|
||||
const serialName = derSeq(
|
||||
derSet(
|
||||
derSeq(
|
||||
der(0x06, Buffer.from([0x55, 0x04, 0x03])),
|
||||
der(0x0c, Buffer.from("Fixture")),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
it("rejects empty and non-minimal certificate serialNumber INTEGERs", () => {
|
||||
const certificate = (serial: readonly number[]) =>
|
||||
readAsn1(
|
||||
derSeq(
|
||||
derSeq(
|
||||
der(0x02, Buffer.from(serial)),
|
||||
SHA256_ALG,
|
||||
serialName,
|
||||
derSeq(
|
||||
der(0x17, Buffer.from("250101000000Z")),
|
||||
der(0x17, Buffer.from("260101000000Z")),
|
||||
),
|
||||
serialName,
|
||||
derSeq(SHA256_ALG, der(0x03, Buffer.from([0]))),
|
||||
),
|
||||
SHA256_ALG,
|
||||
der(0x03, Buffer.from([0])),
|
||||
),
|
||||
);
|
||||
for (const serial of malformedSerials)
|
||||
expect(
|
||||
() => parseCertificate(certificate(serial)),
|
||||
JSON.stringify(serial),
|
||||
).toThrow(/TBSCertificate serialNumber INTEGER is (empty|not minimally)/u);
|
||||
for (const [serial, hex] of minimalSerials)
|
||||
expect(parseCertificate(certificate(serial)).serial_hex).toBe(hex);
|
||||
});
|
||||
|
||||
it("rejects empty and non-minimal SignerInfo serialNumber INTEGERs", () => {
|
||||
const payload = (serial: readonly number[]) =>
|
||||
contentInfo(
|
||||
derSeq(
|
||||
derInt(1),
|
||||
derSet(SHA256_ALG),
|
||||
derSeq(DATA_OID),
|
||||
derSet(
|
||||
derSeq(
|
||||
derInt(1),
|
||||
derSeq(serialName, der(0x02, Buffer.from(serial))),
|
||||
SHA256_ALG,
|
||||
SHA256_ALG,
|
||||
der(0x04, Buffer.from([1])),
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
for (const serial of malformedSerials)
|
||||
expect(
|
||||
() => parse(peCertificateFixture(false, payload(serial))),
|
||||
JSON.stringify(serial),
|
||||
).toThrow(
|
||||
/SignerInfo issuerAndSerialNumber serialNumber INTEGER is (empty|not minimally)/u,
|
||||
);
|
||||
for (const [serial, hex] of minimalSerials)
|
||||
expect(
|
||||
parse(peCertificateFixture(false, payload(serial))).entries[0]?.pkcs7
|
||||
?.signers[0]?.serial_hex,
|
||||
).toBe(hex);
|
||||
});
|
||||
|
||||
it("fails a truncated issuerAndSerialNumber instead of dropping the signer", () => {
|
||||
const signer = derSeq(
|
||||
derInt(1),
|
||||
|
||||
Reference in new issue
Block a user