fix(pe): separate certificate content from counted alignment

This commit is contained in:
morluto
2026-10-24 02:29:37 +08:00
parent 9fb8c114a0
commit 03077d54af
4 changed files with 58 additions and 4 deletions
+5
View File
@@ -28,6 +28,11 @@ The inline Evidence result contains the artifact path, SHA-256, and byte length.
absent or zero. Each `entries[]` item reports `WIN_CERTIFICATE` length,
revision, type, and the payload SHA-256.
Type-2 entries may count their zero alignment padding in `dwLength`. The PE
entry owner accepts only the exact gap to the next eight-byte boundary after
the parsed ContentInfo; extra data or nonzero counted padding fails. Entry and
payload ranges and hashes retain all declared bytes, including counted padding.
For `WIN_CERTIFICATE` type 2 (PKCS#7 signedData) `pkcs7` contains digest
algorithm OIDs, embedded X.509 serial/issuer/subject/validity fields, and
signer identifier facts. A signer identified by subject key identifier reports
+1 -3
View File
@@ -569,9 +569,7 @@ const parseSignedData = (
};
/** Decode ContentInfo wrapping SignedData; other content types remain unsupported. */
export const parsePkcs7SignedData = (bytes: Buffer): ParsedSignedData => {
const root = readAsn1(bytes);
if (root.end !== bytes.length) return fail("ContentInfo has trailing data.");
export const parsePkcs7SignedData = (root: Asn1Node): ParsedSignedData => {
const children = sequence(root, "ContentInfo");
const type = children[0];
const content = children[1];
+41
View File
@@ -72,6 +72,47 @@ it("reads PE32 and PE32+ PKCS#7 certificate tables", () => {
}
});
it.each([false, true])(
"preserves PE certificate bytes when dwLength counts alignment padding (PE32+ = %s)",
(plus) => {
// Choose a fixture with nonzero alignment padding so the control distinguishes
// an ASN.1 terminator from the end of the WIN_CERTIFICATE payload.
const payload = ["Padding", "Paddingx"]
.map((name) => pkcs7SignedDataFixture(name))
.find((value) => (value.length + 8) % 8 !== 0)!;
const bytes = peCertificateFixture(plus, payload);
const control = parse(bytes);
const entry = control.entries[0]!;
const aligned = Math.ceil(entry.length / 8) * 8;
expect(aligned).toBeGreaterThan(entry.length);
bytes.writeUInt32LE(aligned, entry.location.offset);
const report = peCertificatesSchema.parse(parse(bytes));
expect(report.entries[0]?.pkcs7).toEqual(entry.pkcs7);
expect(report.entries[0]?.length).toBe(aligned);
expect(report.entries[0]?.payload.sha256).toBe(
createHash("sha256")
.update(
bytes.subarray(
entry.payload.location.offset,
entry.location.offset + aligned,
),
)
.digest("hex"),
);
bytes[entry.location.offset + aligned - 1] = 1;
expect(() => parse(bytes)).toThrow(/ContentInfo has trailing data/);
},
);
it("refuses surplus zero bytes beyond the PE entry alignment gap", () => {
const payload = pkcs7SignedDataFixture();
expect(() =>
parse(
peCertificateFixture(false, Buffer.concat([payload, Buffer.alloc(8)])),
),
).toThrow(/ContentInfo has trailing data/);
});
it("reads BER indefinite constructed ContentInfo and preserves certificate bytes", () => {
const payload = pkcs7SignedDataIndefiniteEnvelopeFixture();
const root = readAsn1(payload);
+11 -1
View File
@@ -9,6 +9,7 @@ import {
oidName,
parsePkcs7SignedData,
PeCertificateAsn1Failure,
readAsn1,
} from "./PeCertificateAsn1.js";
import { readPeHeaders } from "./PeResourceLayout.js";
@@ -115,7 +116,16 @@ export const parsePeCertificates = (
let pkcs7: PeCertificates["entries"][number]["pkcs7"] = null;
if (peCertificateTypeName(certificateType) === "pkcs7") {
try {
const parsed = parsePkcs7SignedData(payload);
const content = readAsn1(payload);
// The PE entry owns alignment outside the ASN.1 ContentInfo. Accept
// either uncounted padding or the exact counted zero-padding extent.
if (
content.end !== payload.length &&
(payload.length !== align8(8 + content.end) - 8 ||
!payload.subarray(content.end).every((byte) => byte === 0))
)
throw new PeCertificateAsn1Failure("ContentInfo has trailing data.");
const parsed = parsePkcs7SignedData(content);
teletexStringCompatibilityUsed ||=
parsed.teletexStringCompatibilityUsed;
pkcs7 = {