mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32f72fdeda |
No files matched your search
@@ -202,8 +202,9 @@ describe("v1 signature block (PKCS#7 SignedData)", () => {
|
|||||||
let nested: Uint8Array = Uint8Array.from([0x05, 0x00]);
|
let nested: Uint8Array = Uint8Array.from([0x05, 0x00]);
|
||||||
for (let i = 0; i < 64; i += 1) nested = indefinite(0x30, nested);
|
for (let i = 0; i < 64; i += 1) nested = indefinite(0x30, nested);
|
||||||
expect(errorOf(nested)).toEqual({
|
expect(errorOf(nested)).toEqual({
|
||||||
kind: "malformed-pkcs7",
|
kind: "budget-exceeded",
|
||||||
detail: "ASN.1 nesting exceeds 32 levels.",
|
resource: "asn1-depth",
|
||||||
|
limit: 32,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -411,3 +412,42 @@ it.each([0, 1])(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
it("keeps a valid constructed CMS content depth refusal distinct from malformed input", () => {
|
||||||
|
let content = tlv(0x04, Uint8Array.of(1));
|
||||||
|
const block = () =>
|
||||||
|
signedDataBlock({
|
||||||
|
certificates: [LEAF_CERTIFICATE],
|
||||||
|
signerInfos: [signerInfo({ names: LEAF_CERTIFICATE })],
|
||||||
|
encapsulatedContent: content,
|
||||||
|
});
|
||||||
|
expect(decodeV1SignatureBlock(block()).ok).toBe(true);
|
||||||
|
for (let depth = 0; depth < 40; depth += 1)
|
||||||
|
content = indefinite(0x24, content);
|
||||||
|
expect(errorOf(block())).toEqual({
|
||||||
|
kind: "budget-exceeded",
|
||||||
|
resource: "asn1-depth",
|
||||||
|
limit: 32,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it("preserves the canonical OID byte allowance for a valid unknown digest algorithm", () => {
|
||||||
|
const algorithm = tlv(
|
||||||
|
0x06,
|
||||||
|
Uint8Array.from([0x2a, ...Array<number>(64).fill(0)]),
|
||||||
|
);
|
||||||
|
expect(
|
||||||
|
errorOf(
|
||||||
|
signedDataBlock({
|
||||||
|
certificates: [LEAF_CERTIFICATE],
|
||||||
|
signerInfos: [
|
||||||
|
signerInfo({
|
||||||
|
names: LEAF_CERTIFICATE,
|
||||||
|
digestAlgorithmOid: algorithm,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
digestAlgorithmOid: algorithm,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
).toEqual({ kind: "budget-exceeded", resource: "oid-bytes", limit: 64 });
|
||||||
|
});
|
||||||
@@ -27,7 +27,10 @@ export type ApkV1BlockError =
|
|||||||
readonly resource:
|
readonly resource:
|
||||||
| "v1-block-certificates"
|
| "v1-block-certificates"
|
||||||
| "v1-signer-infos"
|
| "v1-signer-infos"
|
||||||
| "asn1-elements";
|
| "asn1-elements"
|
||||||
|
| "asn1-depth"
|
||||||
|
| "oid-bytes"
|
||||||
|
| "oid-arc-bytes";
|
||||||
readonly limit: number;
|
readonly limit: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -98,11 +101,14 @@ class ApkBerReader {
|
|||||||
return this.#reader.read(at, limit, depth);
|
return this.#reader.read(at, limit, depth);
|
||||||
} catch (cause: unknown) {
|
} catch (cause: unknown) {
|
||||||
if (!(cause instanceof Asn1BerFailure)) throw cause;
|
if (!(cause instanceof Asn1BerFailure)) throw cause;
|
||||||
if (cause.kind === "budget-exceeded")
|
if (cause.constraint !== null)
|
||||||
throw new BlockFailure({
|
throw new BlockFailure({
|
||||||
kind: "budget-exceeded",
|
kind: "budget-exceeded",
|
||||||
resource: "asn1-elements",
|
resource:
|
||||||
limit: MAX_ASN1_ELEMENTS,
|
cause.constraint.resource === "nodes"
|
||||||
|
? "asn1-elements"
|
||||||
|
: "asn1-depth",
|
||||||
|
limit: cause.constraint.limit,
|
||||||
});
|
});
|
||||||
throw malformed(cause.message);
|
throw malformed(cause.message);
|
||||||
}
|
}
|
||||||
@@ -155,6 +161,13 @@ const expectOctetString = (
|
|||||||
const oidText = (content: Uint8Array): string => {
|
const oidText = (content: Uint8Array): string => {
|
||||||
const decoded = decodeAsn1Oid(content, { bytes: 64, arcBytes: 64 });
|
const decoded = decodeAsn1Oid(content, { bytes: 64, arcBytes: 64 });
|
||||||
if (decoded.ok) return decoded.value;
|
if (decoded.ok) return decoded.value;
|
||||||
|
if (decoded.error.kind === "byte-limit" || decoded.error.kind === "arc-limit")
|
||||||
|
throw new BlockFailure({
|
||||||
|
kind: "budget-exceeded",
|
||||||
|
resource:
|
||||||
|
decoded.error.kind === "byte-limit" ? "oid-bytes" : "oid-arc-bytes",
|
||||||
|
limit: decoded.error.limit,
|
||||||
|
});
|
||||||
if (decoded.error.kind === "truncated")
|
if (decoded.error.kind === "truncated")
|
||||||
throw malformed("object identifier is truncated");
|
throw malformed("object identifier is truncated");
|
||||||
throw malformed(`object identifier of ${String(content.length)} bytes`);
|
throw malformed(`object identifier of ${String(content.length)} bytes`);
|
||||||
@@ -188,6 +201,13 @@ const integerIdentityContents = (bytes: Uint8Array): Uint8Array => {
|
|||||||
return start === 0 ? bytes : bytes.subarray(start);
|
return start === 0 ? bytes : bytes.subarray(start);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/** Optional identity interpretation may remain unknown while raw certificate identity survives. */
|
||||||
|
const unavailableIdentityInterpretation = (cause: unknown): boolean =>
|
||||||
|
cause instanceof BlockFailure &&
|
||||||
|
(cause.error.kind === "malformed-pkcs7" ||
|
||||||
|
cause.error.resource === "oid-bytes" ||
|
||||||
|
cause.error.resource === "oid-arc-bytes");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* RFC 5280 §7.1: RDN order matters; attributes in one SET do not. This
|
* RFC 5280 §7.1: RDN order matters; attributes in one SET do not. This
|
||||||
* comparison profile admits ASCII PrintableString/UTF8String for common
|
* comparison profile admits ASCII PrintableString/UTF8String for common
|
||||||
@@ -237,8 +257,7 @@ const directoryNameKey = (name: Asn1BerNode): string | null => {
|
|||||||
}
|
}
|
||||||
return JSON.stringify(rdns);
|
return JSON.stringify(rdns);
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
if (cause instanceof BlockFailure && cause.error.kind === "malformed-pkcs7")
|
if (unavailableIdentityInterpretation(cause)) return null;
|
||||||
return null;
|
|
||||||
throw cause;
|
throw cause;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -298,8 +317,7 @@ const certificateIdentity = (
|
|||||||
subjectKeyIdentifier,
|
subjectKeyIdentifier,
|
||||||
};
|
};
|
||||||
} catch (cause: unknown) {
|
} catch (cause: unknown) {
|
||||||
if (cause instanceof BlockFailure && cause.error.kind === "malformed-pkcs7")
|
if (unavailableIdentityInterpretation(cause)) return null;
|
||||||
return null;
|
|
||||||
throw cause;
|
throw cause;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
+14
-8
@@ -11,11 +11,16 @@ export interface Asn1BerNode {
|
|||||||
readonly children: readonly Asn1BerNode[];
|
readonly children: readonly Asn1BerNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** The fixed parser allowance that prevented examining more BER structure. */
|
||||||
|
export interface Asn1BerConstraint {
|
||||||
|
readonly resource: "nodes" | "depth";
|
||||||
|
readonly limit: number;
|
||||||
|
}
|
||||||
|
|
||||||
export class Asn1BerFailure extends Error {
|
export class Asn1BerFailure extends Error {
|
||||||
constructor(
|
constructor(
|
||||||
message: string,
|
message: string,
|
||||||
readonly kind: "malformed" | "budget-exceeded" = "malformed",
|
readonly constraint: Asn1BerConstraint | null = null,
|
||||||
readonly limit: number | null = null,
|
|
||||||
) {
|
) {
|
||||||
super(message);
|
super(message);
|
||||||
this.name = "Asn1BerFailure";
|
this.name = "Asn1BerFailure";
|
||||||
@@ -28,10 +33,9 @@ interface Budget {
|
|||||||
|
|
||||||
const fail = (
|
const fail = (
|
||||||
message: string,
|
message: string,
|
||||||
kind: Asn1BerFailure["kind"] = "malformed",
|
constraint: Asn1BerConstraint | null = null,
|
||||||
limit: number | null = null,
|
|
||||||
): never => {
|
): never => {
|
||||||
throw new Asn1BerFailure(message, kind, limit);
|
throw new Asn1BerFailure(message, constraint);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -51,12 +55,14 @@ export class Asn1BerReader {
|
|||||||
|
|
||||||
read(offset = 0, end = this.bytes.length, depth = 0): Asn1BerNode {
|
read(offset = 0, end = this.bytes.length, depth = 0): Asn1BerNode {
|
||||||
if (depth > this.maxDepth)
|
if (depth > this.maxDepth)
|
||||||
return fail(`ASN.1 nesting exceeds ${String(this.maxDepth)} levels.`);
|
return fail(`ASN.1 nesting exceeds ${String(this.maxDepth)} levels.`, {
|
||||||
|
resource: "depth",
|
||||||
|
limit: this.maxDepth,
|
||||||
|
});
|
||||||
if (this.#budget.remaining <= 0)
|
if (this.#budget.remaining <= 0)
|
||||||
return fail(
|
return fail(
|
||||||
`ASN.1 value count exceeds the ${String(this.maxNodes)}-node budget.`,
|
`ASN.1 value count exceeds the ${String(this.maxNodes)}-node budget.`,
|
||||||
"budget-exceeded",
|
{ resource: "nodes", limit: this.maxNodes },
|
||||||
this.maxNodes,
|
|
||||||
);
|
);
|
||||||
this.#budget.remaining -= 1;
|
this.#budget.remaining -= 1;
|
||||||
if (offset >= end || offset + 2 > end)
|
if (offset >= end || offset + 2 > end)
|
||||||
|
|||||||
@@ -28,7 +28,12 @@ const OID_NAMES: Readonly<Record<string, string>> = {
|
|||||||
|
|
||||||
/** A fixed per-payload decoder allowance and its limit. */
|
/** A fixed per-payload decoder allowance and its limit. */
|
||||||
export interface PeCertificateDecodeAllowance {
|
export interface PeCertificateDecodeAllowance {
|
||||||
readonly allowance: "asn1-nodes" | "decoded-text";
|
readonly allowance:
|
||||||
|
| "asn1-nodes"
|
||||||
|
| "asn1-depth"
|
||||||
|
| "decoded-text"
|
||||||
|
| "oid-bytes"
|
||||||
|
| "oid-arc-bytes";
|
||||||
readonly limit: number;
|
readonly limit: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,9 +67,15 @@ export const readAsn1 = (bytes: Buffer): Asn1BerNode => {
|
|||||||
if (cause instanceof Asn1BerFailure)
|
if (cause instanceof Asn1BerFailure)
|
||||||
throw new PeCertificateAsn1Failure(
|
throw new PeCertificateAsn1Failure(
|
||||||
cause.message,
|
cause.message,
|
||||||
cause.kind === "budget-exceeded"
|
cause.constraint === null
|
||||||
? { allowance: "asn1-nodes", limit: cause.limit ?? ASN1_NODE_BUDGET }
|
? null
|
||||||
: null,
|
: {
|
||||||
|
allowance:
|
||||||
|
cause.constraint.resource === "nodes"
|
||||||
|
? "asn1-nodes"
|
||||||
|
: "asn1-depth",
|
||||||
|
limit: cause.constraint.limit,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
throw cause;
|
throw cause;
|
||||||
}
|
}
|
||||||
@@ -112,12 +123,14 @@ export const decodeOid = (content: Buffer): string => {
|
|||||||
case "empty":
|
case "empty":
|
||||||
return fail("Empty OBJECT IDENTIFIER.");
|
return fail("Empty OBJECT IDENTIFIER.");
|
||||||
case "byte-limit":
|
case "byte-limit":
|
||||||
return fail(
|
throw new PeCertificateAsn1Failure(
|
||||||
`OBJECT IDENTIFIER exceeds ${String(decoded.error.limit)} bytes.`,
|
`OBJECT IDENTIFIER exceeds ${String(decoded.error.limit)} bytes.`,
|
||||||
|
{ allowance: "oid-bytes", limit: decoded.error.limit },
|
||||||
);
|
);
|
||||||
case "arc-limit":
|
case "arc-limit":
|
||||||
return fail(
|
throw new PeCertificateAsn1Failure(
|
||||||
`OBJECT IDENTIFIER arc exceeds ${String(decoded.error.limit)} bytes.`,
|
`OBJECT IDENTIFIER arc exceeds ${String(decoded.error.limit)} bytes.`,
|
||||||
|
{ allowance: "oid-arc-bytes", limit: decoded.error.limit },
|
||||||
);
|
);
|
||||||
case "non-minimal":
|
case "non-minimal":
|
||||||
return fail("Non-minimal OBJECT IDENTIFIER component.");
|
return fail("Non-minimal OBJECT IDENTIFIER component.");
|
||||||
|
|||||||
@@ -0,0 +1,132 @@
|
|||||||
|
import { writeFile } from "node:fs/promises";
|
||||||
|
import { join } from "node:path";
|
||||||
|
import { expect, it } from "vitest";
|
||||||
|
import { createTestTempDirectory } from "../../../tests/fixtures/temporaryDirectory.js";
|
||||||
|
import { repeatedCertificateSignedData } from "../../../tests/fixtures/peCertificateAllowance.js";
|
||||||
|
import {
|
||||||
|
indefinite,
|
||||||
|
tlv,
|
||||||
|
signedDataBlock,
|
||||||
|
signerInfo,
|
||||||
|
LEAF_CERTIFICATE,
|
||||||
|
} from "../../../tests/fixtures/apkSignature/v1SignatureBlock.js";
|
||||||
|
import { inspectPeCertificatesInputSchema } from "../../domain/native/peCertificates.js";
|
||||||
|
import {
|
||||||
|
AnalysisInputError,
|
||||||
|
AnalysisResourceConstraintError,
|
||||||
|
} from "../../domain/analysisErrorCore.js";
|
||||||
|
import { peCertificateFixture } from "./PeCertificates.fixture.js";
|
||||||
|
import { PeCertificatesProvider } from "./PeCertificatesProvider.js";
|
||||||
|
|
||||||
|
const encapsulated = (depth: number): Buffer => {
|
||||||
|
let content = tlv(0x04, Uint8Array.of(1));
|
||||||
|
for (let level = 0; level < depth; level += 1)
|
||||||
|
content = indefinite(0x24, content);
|
||||||
|
return Buffer.from(
|
||||||
|
signedDataBlock({
|
||||||
|
certificates: [LEAF_CERTIFICATE],
|
||||||
|
signerInfos: [signerInfo({ names: LEAF_CERTIFICATE })],
|
||||||
|
encapsulatedContent: content,
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const inspect = async (payload: Buffer) => {
|
||||||
|
const path = join(
|
||||||
|
await createTestTempDirectory("rea-pe-decoder-allowance-"),
|
||||||
|
"inert.exe",
|
||||||
|
);
|
||||||
|
await writeFile(path, peCertificateFixture(false, payload));
|
||||||
|
const provider = new PeCertificatesProvider();
|
||||||
|
try {
|
||||||
|
return await provider.inspect(
|
||||||
|
inspectPeCertificatesInputSchema.parse({ path }),
|
||||||
|
);
|
||||||
|
} finally {
|
||||||
|
await provider.close();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
name: "depth",
|
||||||
|
payload: () => encapsulated(40),
|
||||||
|
limits: { max_asn1_depth: 32 },
|
||||||
|
description: "32 nesting levels",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "nodes",
|
||||||
|
payload: () => repeatedCertificateSignedData(600),
|
||||||
|
limits: { max_asn1_values: 16_384 },
|
||||||
|
description: "16384 values",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "text",
|
||||||
|
payload: () => repeatedCertificateSignedData(350),
|
||||||
|
limits: { max_decoded_text_bytes: 65_536 },
|
||||||
|
description: "65536 bytes",
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"reports the fixed $name allowance with its own resource and recovery",
|
||||||
|
async ({ payload, limits, description }) => {
|
||||||
|
const result = await inspect(payload());
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) throw new Error("Expected decoder refusal");
|
||||||
|
expect(result.error).toBeInstanceOf(AnalysisResourceConstraintError);
|
||||||
|
if (!(result.error instanceof AnalysisResourceConstraintError))
|
||||||
|
throw result.error;
|
||||||
|
expect(result.error.reportedLimits).toEqual(limits);
|
||||||
|
expect(result.error.retryable).toBe(false);
|
||||||
|
expect(result.error.fixedAllowance?.name).toContain(description);
|
||||||
|
expect(result.error.fixedAllowance?.name).toContain("no runtime override");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
it("accepts shallower valid BER and keeps malformed BER an input failure", async () => {
|
||||||
|
expect((await inspect(encapsulated(1))).ok).toBe(true);
|
||||||
|
const malformed = await inspect(Buffer.from([0x30, 0x03, 0x05]));
|
||||||
|
expect(malformed.ok).toBe(false);
|
||||||
|
if (malformed.ok) throw new Error("Expected malformed input refusal");
|
||||||
|
expect(malformed.error).toBeInstanceOf(AnalysisInputError);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.each([
|
||||||
|
{
|
||||||
|
name: "bytes",
|
||||||
|
content: Buffer.from([0x2a, ...Array<number>(256).fill(0)]),
|
||||||
|
limits: { max_oid_bytes: 256 },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "arc bytes",
|
||||||
|
content: Buffer.from([0x2a, 0x81, ...Array<number>(31).fill(0x80), 0]),
|
||||||
|
limits: { max_oid_arc_bytes: 32 },
|
||||||
|
},
|
||||||
|
])(
|
||||||
|
"preserves the canonical OID $name allowance in a valid CMS algorithm",
|
||||||
|
async ({ content, limits }) => {
|
||||||
|
const algorithm = tlv(0x06, content);
|
||||||
|
const result = await inspect(
|
||||||
|
Buffer.from(
|
||||||
|
signedDataBlock({
|
||||||
|
certificates: [LEAF_CERTIFICATE],
|
||||||
|
signerInfos: [
|
||||||
|
signerInfo({
|
||||||
|
names: LEAF_CERTIFICATE,
|
||||||
|
digestAlgorithmOid: algorithm,
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
digestAlgorithmOid: algorithm,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
expect(result.ok).toBe(false);
|
||||||
|
if (result.ok) throw new Error("Expected OID allowance refusal");
|
||||||
|
expect(result.error).toBeInstanceOf(AnalysisResourceConstraintError);
|
||||||
|
if (!(result.error instanceof AnalysisResourceConstraintError))
|
||||||
|
throw result.error;
|
||||||
|
expect(result.error.reportedLimits).toEqual(limits);
|
||||||
|
expect(result.error.retryable).toBe(false);
|
||||||
|
expect(result.error.fixedAllowance?.name).toContain("OBJECT IDENTIFIER");
|
||||||
|
expect(result.error.fixedAllowance?.name).toContain("no runtime override");
|
||||||
|
},
|
||||||
|
);
|
||||||
@@ -105,24 +105,40 @@ const inputBudgetRemediation = (
|
|||||||
const decoderAllowanceFailure = (
|
const decoderAllowanceFailure = (
|
||||||
cause: PeCertificateAsn1Failure,
|
cause: PeCertificateAsn1Failure,
|
||||||
{ allowance, limit }: PeCertificateDecodeAllowance,
|
{ allowance, limit }: PeCertificateDecodeAllowance,
|
||||||
) =>
|
) => {
|
||||||
new AnalysisResourceConstraintError(
|
const selected = {
|
||||||
|
"asn1-nodes": {
|
||||||
|
limits: { max_asn1_values: limit },
|
||||||
|
description: `The PE certificate ASN.1 allowance of ${String(limit)} values per PKCS#7 payload (this release has no runtime override)`,
|
||||||
|
},
|
||||||
|
"asn1-depth": {
|
||||||
|
limits: { max_asn1_depth: limit },
|
||||||
|
description: `The PE certificate ASN.1 allowance of ${String(limit)} nesting levels per PKCS#7 payload (this release has no runtime override)`,
|
||||||
|
},
|
||||||
|
"decoded-text": {
|
||||||
|
limits: { max_decoded_text_bytes: limit },
|
||||||
|
description: `The PE certificate decoded-text allowance of ${String(limit)} bytes per PKCS#7 payload (this release has no runtime override)`,
|
||||||
|
},
|
||||||
|
"oid-bytes": {
|
||||||
|
limits: { max_oid_bytes: limit },
|
||||||
|
description: `The PE certificate OBJECT IDENTIFIER allowance of ${String(limit)} bytes per OID (this release has no runtime override)`,
|
||||||
|
},
|
||||||
|
"oid-arc-bytes": {
|
||||||
|
limits: { max_oid_arc_bytes: limit },
|
||||||
|
description: `The PE certificate OBJECT IDENTIFIER allowance of ${String(limit)} bytes per base-128 arc (this release has no runtime override)`,
|
||||||
|
},
|
||||||
|
}[allowance];
|
||||||
|
return new AnalysisResourceConstraintError(
|
||||||
OPERATION,
|
OPERATION,
|
||||||
"memory",
|
"memory",
|
||||||
`PKCS#7 signedData exceeds a fixed decoder allowance: ${cause.message}`,
|
`PKCS#7 signedData exceeds a fixed decoder allowance: ${cause.message}`,
|
||||||
allowance === "asn1-nodes"
|
selected.limits,
|
||||||
? { max_asn1_values: limit }
|
|
||||||
: { max_decoded_text_bytes: limit },
|
|
||||||
{
|
{
|
||||||
cause,
|
cause,
|
||||||
...fixedAllowanceOptions(
|
...fixedAllowanceOptions(selected.description, "parser"),
|
||||||
allowance === "asn1-nodes"
|
|
||||||
? `The PE certificate ASN.1 allowance of ${String(limit)} values per PKCS#7 payload (this release has no runtime override)`
|
|
||||||
: `The PE certificate decoded-text allowance of ${String(limit)} bytes per PKCS#7 payload (this release has no runtime override)`,
|
|
||||||
"parser",
|
|
||||||
),
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
};
|
||||||
|
|
||||||
const peCertificatesFailurePrimary = (
|
const peCertificatesFailurePrimary = (
|
||||||
facts: ArtifactFilePrimaryFacts,
|
facts: ArtifactFilePrimaryFacts,
|
||||||
|
|||||||
+14
-3
@@ -230,12 +230,15 @@ export interface SignerInfoSpec {
|
|||||||
* from the certificate named by `names`.
|
* from the certificate named by `names`.
|
||||||
*/
|
*/
|
||||||
readonly serial?: Uint8Array;
|
readonly serial?: Uint8Array;
|
||||||
|
/** Complete digest algorithm OID TLV, including bounded-parser fixtures. */
|
||||||
|
readonly digestAlgorithmOid?: Uint8Array;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const signerInfo = ({
|
export const signerInfo = ({
|
||||||
names,
|
names,
|
||||||
signature,
|
signature,
|
||||||
serial,
|
serial,
|
||||||
|
digestAlgorithmOid,
|
||||||
}: SignerInfoSpec): Uint8Array =>
|
}: SignerInfoSpec): Uint8Array =>
|
||||||
tlv(
|
tlv(
|
||||||
0x30,
|
0x30,
|
||||||
@@ -244,7 +247,7 @@ export const signerInfo = ({
|
|||||||
names,
|
names,
|
||||||
serial === undefined ? undefined : tlv(0x02, serial),
|
serial === undefined ? undefined : tlv(0x02, serial),
|
||||||
),
|
),
|
||||||
algorithm(OID_SHA256),
|
tlv(0x30, digestAlgorithmOid ?? oid(OID_SHA256)),
|
||||||
algorithm(OID_ECDSA_SHA256),
|
algorithm(OID_ECDSA_SHA256),
|
||||||
signature ?? tlv(0x04, Uint8Array.from([1, 2, 3])),
|
signature ?? tlv(0x04, Uint8Array.from([1, 2, 3])),
|
||||||
);
|
);
|
||||||
@@ -254,6 +257,8 @@ export const signedDataBlock = (options: {
|
|||||||
readonly certificates: readonly Uint8Array[];
|
readonly certificates: readonly Uint8Array[];
|
||||||
readonly signerInfos: readonly Uint8Array[];
|
readonly signerInfos: readonly Uint8Array[];
|
||||||
readonly contentType?: string;
|
readonly contentType?: string;
|
||||||
|
readonly encapsulatedContent?: Uint8Array;
|
||||||
|
readonly digestAlgorithmOid?: Uint8Array;
|
||||||
}): Uint8Array =>
|
}): Uint8Array =>
|
||||||
tlv(
|
tlv(
|
||||||
0x30,
|
0x30,
|
||||||
@@ -263,8 +268,14 @@ export const signedDataBlock = (options: {
|
|||||||
tlv(
|
tlv(
|
||||||
0x30,
|
0x30,
|
||||||
integer(1),
|
integer(1),
|
||||||
tlv(0x31, algorithm(OID_SHA256)),
|
tlv(0x31, tlv(0x30, options.digestAlgorithmOid ?? oid(OID_SHA256))),
|
||||||
tlv(0x30, oid("1.2.840.113549.1.7.1")),
|
tlv(
|
||||||
|
0x30,
|
||||||
|
oid("1.2.840.113549.1.7.1"),
|
||||||
|
...(options.encapsulatedContent === undefined
|
||||||
|
? []
|
||||||
|
: [tlv(0xa0, options.encapsulatedContent)]),
|
||||||
|
),
|
||||||
tlv(0xa0, ...options.certificates),
|
tlv(0xa0, ...options.certificates),
|
||||||
tlv(0x31, ...options.signerInfos),
|
tlv(0x31, ...options.signerInfos),
|
||||||
),
|
),
|
||||||
|
|||||||
Reference in new issue
Block a user