mirror of
https://github.com/morluto/rea.git
synced 2026-11-10 17:30:32 -10:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
78d4e2a1ba |
No files matched your search
@@ -369,7 +369,10 @@ export class TsharkCaptureProvider implements PacketCapturePort {
|
||||
resourcePath = "path" in input ? input.path : command;
|
||||
// TShark resolves `-e` aliases before emitting canonical field names;
|
||||
// the command arguments, not output names, own field selection.
|
||||
const matchingPackets = parsePacketOutput(output.stdout.text);
|
||||
const matchingPackets = parsePacketOutput(
|
||||
output.stdout.text,
|
||||
input.packet_limit,
|
||||
);
|
||||
const start = Math.min(input.match_offset, matchingPackets.length);
|
||||
const end =
|
||||
input.match_limit === null
|
||||
|
||||
@@ -6,7 +6,7 @@ import { packetFieldsSchema } from "../domain/captures/packetCapture.js";
|
||||
const producerSchema = z.array(
|
||||
z.object({ _source: z.object({ layers: packetFieldsSchema }) }),
|
||||
);
|
||||
export const parsePacketOutput = (text: string) => {
|
||||
export const parsePacketOutput = (text: string, packetLimit: number | null) => {
|
||||
const decoded: unknown = JSON.parse(text);
|
||||
const packets = producerSchema
|
||||
.parse(decoded)
|
||||
@@ -19,6 +19,11 @@ export const parsePacketOutput = (text: string) => {
|
||||
"inspect_packet_capture",
|
||||
"TShark returned unordered frames.",
|
||||
);
|
||||
if (packetLimit !== null && frame > BigInt(packetLimit))
|
||||
throw new AnalysisOutputError(
|
||||
"inspect_packet_capture",
|
||||
"TShark returned a frame beyond the selected input packet limit.",
|
||||
);
|
||||
previous = frame;
|
||||
}
|
||||
return packets;
|
||||
|
||||
@@ -202,9 +202,8 @@ describe("v1 signature block (PKCS#7 SignedData)", () => {
|
||||
let nested: Uint8Array = Uint8Array.from([0x05, 0x00]);
|
||||
for (let i = 0; i < 64; i += 1) nested = indefinite(0x30, nested);
|
||||
expect(errorOf(nested)).toEqual({
|
||||
kind: "budget-exceeded",
|
||||
resource: "asn1-depth",
|
||||
limit: 32,
|
||||
kind: "malformed-pkcs7",
|
||||
detail: "ASN.1 nesting exceeds 32 levels.",
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -412,42 +411,3 @@ 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,10 +27,7 @@ export type ApkV1BlockError =
|
||||
readonly resource:
|
||||
| "v1-block-certificates"
|
||||
| "v1-signer-infos"
|
||||
| "asn1-elements"
|
||||
| "asn1-depth"
|
||||
| "oid-bytes"
|
||||
| "oid-arc-bytes";
|
||||
| "asn1-elements";
|
||||
readonly limit: number;
|
||||
};
|
||||
|
||||
@@ -101,14 +98,11 @@ class ApkBerReader {
|
||||
return this.#reader.read(at, limit, depth);
|
||||
} catch (cause: unknown) {
|
||||
if (!(cause instanceof Asn1BerFailure)) throw cause;
|
||||
if (cause.constraint !== null)
|
||||
if (cause.kind === "budget-exceeded")
|
||||
throw new BlockFailure({
|
||||
kind: "budget-exceeded",
|
||||
resource:
|
||||
cause.constraint.resource === "nodes"
|
||||
? "asn1-elements"
|
||||
: "asn1-depth",
|
||||
limit: cause.constraint.limit,
|
||||
resource: "asn1-elements",
|
||||
limit: MAX_ASN1_ELEMENTS,
|
||||
});
|
||||
throw malformed(cause.message);
|
||||
}
|
||||
@@ -161,13 +155,6 @@ const expectOctetString = (
|
||||
const oidText = (content: Uint8Array): string => {
|
||||
const decoded = decodeAsn1Oid(content, { bytes: 64, arcBytes: 64 });
|
||||
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")
|
||||
throw malformed("object identifier is truncated");
|
||||
throw malformed(`object identifier of ${String(content.length)} bytes`);
|
||||
@@ -201,13 +188,6 @@ const integerIdentityContents = (bytes: Uint8Array): Uint8Array => {
|
||||
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
|
||||
* comparison profile admits ASCII PrintableString/UTF8String for common
|
||||
@@ -257,7 +237,8 @@ const directoryNameKey = (name: Asn1BerNode): string | null => {
|
||||
}
|
||||
return JSON.stringify(rdns);
|
||||
} catch (cause) {
|
||||
if (unavailableIdentityInterpretation(cause)) return null;
|
||||
if (cause instanceof BlockFailure && cause.error.kind === "malformed-pkcs7")
|
||||
return null;
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
@@ -317,7 +298,8 @@ const certificateIdentity = (
|
||||
subjectKeyIdentifier,
|
||||
};
|
||||
} catch (cause: unknown) {
|
||||
if (unavailableIdentityInterpretation(cause)) return null;
|
||||
if (cause instanceof BlockFailure && cause.error.kind === "malformed-pkcs7")
|
||||
return null;
|
||||
throw cause;
|
||||
}
|
||||
};
|
||||
|
||||
+8
-14
@@ -11,16 +11,11 @@ export interface 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 {
|
||||
constructor(
|
||||
message: string,
|
||||
readonly constraint: Asn1BerConstraint | null = null,
|
||||
readonly kind: "malformed" | "budget-exceeded" = "malformed",
|
||||
readonly limit: number | null = null,
|
||||
) {
|
||||
super(message);
|
||||
this.name = "Asn1BerFailure";
|
||||
@@ -33,9 +28,10 @@ interface Budget {
|
||||
|
||||
const fail = (
|
||||
message: string,
|
||||
constraint: Asn1BerConstraint | null = null,
|
||||
kind: Asn1BerFailure["kind"] = "malformed",
|
||||
limit: number | null = null,
|
||||
): never => {
|
||||
throw new Asn1BerFailure(message, constraint);
|
||||
throw new Asn1BerFailure(message, kind, limit);
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -55,14 +51,12 @@ export class Asn1BerReader {
|
||||
|
||||
read(offset = 0, end = this.bytes.length, depth = 0): Asn1BerNode {
|
||||
if (depth > this.maxDepth)
|
||||
return fail(`ASN.1 nesting exceeds ${String(this.maxDepth)} levels.`, {
|
||||
resource: "depth",
|
||||
limit: this.maxDepth,
|
||||
});
|
||||
return fail(`ASN.1 nesting exceeds ${String(this.maxDepth)} levels.`);
|
||||
if (this.#budget.remaining <= 0)
|
||||
return fail(
|
||||
`ASN.1 value count exceeds the ${String(this.maxNodes)}-node budget.`,
|
||||
{ resource: "nodes", limit: this.maxNodes },
|
||||
"budget-exceeded",
|
||||
this.maxNodes,
|
||||
);
|
||||
this.#budget.remaining -= 1;
|
||||
if (offset >= end || offset + 2 > end)
|
||||
|
||||
@@ -28,12 +28,7 @@ const OID_NAMES: Readonly<Record<string, string>> = {
|
||||
|
||||
/** A fixed per-payload decoder allowance and its limit. */
|
||||
export interface PeCertificateDecodeAllowance {
|
||||
readonly allowance:
|
||||
| "asn1-nodes"
|
||||
| "asn1-depth"
|
||||
| "decoded-text"
|
||||
| "oid-bytes"
|
||||
| "oid-arc-bytes";
|
||||
readonly allowance: "asn1-nodes" | "decoded-text";
|
||||
readonly limit: number;
|
||||
}
|
||||
|
||||
@@ -67,15 +62,9 @@ export const readAsn1 = (bytes: Buffer): Asn1BerNode => {
|
||||
if (cause instanceof Asn1BerFailure)
|
||||
throw new PeCertificateAsn1Failure(
|
||||
cause.message,
|
||||
cause.constraint === null
|
||||
? null
|
||||
: {
|
||||
allowance:
|
||||
cause.constraint.resource === "nodes"
|
||||
? "asn1-nodes"
|
||||
: "asn1-depth",
|
||||
limit: cause.constraint.limit,
|
||||
},
|
||||
cause.kind === "budget-exceeded"
|
||||
? { allowance: "asn1-nodes", limit: cause.limit ?? ASN1_NODE_BUDGET }
|
||||
: null,
|
||||
);
|
||||
throw cause;
|
||||
}
|
||||
@@ -123,14 +112,12 @@ export const decodeOid = (content: Buffer): string => {
|
||||
case "empty":
|
||||
return fail("Empty OBJECT IDENTIFIER.");
|
||||
case "byte-limit":
|
||||
throw new PeCertificateAsn1Failure(
|
||||
return fail(
|
||||
`OBJECT IDENTIFIER exceeds ${String(decoded.error.limit)} bytes.`,
|
||||
{ allowance: "oid-bytes", limit: decoded.error.limit },
|
||||
);
|
||||
case "arc-limit":
|
||||
throw new PeCertificateAsn1Failure(
|
||||
return fail(
|
||||
`OBJECT IDENTIFIER arc exceeds ${String(decoded.error.limit)} bytes.`,
|
||||
{ allowance: "oid-arc-bytes", limit: decoded.error.limit },
|
||||
);
|
||||
case "non-minimal":
|
||||
return fail("Non-minimal OBJECT IDENTIFIER component.");
|
||||
|
||||
@@ -1,132 +0,0 @@
|
||||
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,40 +105,24 @@ const inputBudgetRemediation = (
|
||||
const decoderAllowanceFailure = (
|
||||
cause: PeCertificateAsn1Failure,
|
||||
{ allowance, limit }: PeCertificateDecodeAllowance,
|
||||
) => {
|
||||
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(
|
||||
) =>
|
||||
new AnalysisResourceConstraintError(
|
||||
OPERATION,
|
||||
"memory",
|
||||
`PKCS#7 signedData exceeds a fixed decoder allowance: ${cause.message}`,
|
||||
selected.limits,
|
||||
allowance === "asn1-nodes"
|
||||
? { max_asn1_values: limit }
|
||||
: { max_decoded_text_bytes: limit },
|
||||
{
|
||||
cause,
|
||||
...fixedAllowanceOptions(selected.description, "parser"),
|
||||
...fixedAllowanceOptions(
|
||||
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 = (
|
||||
facts: ArtifactFilePrimaryFacts,
|
||||
|
||||
@@ -247,6 +247,26 @@ it("preserves TShark canonical output for a caller-selected field alias", async
|
||||
expect(report.tool.arguments).toContain("bootp.type");
|
||||
});
|
||||
|
||||
it("refuses producer frames outside the selected input read scope before paging", async () => {
|
||||
const f = await fixture("many");
|
||||
const request = { path: f.path, fields: ["ip.addr"], match_limit: 1 };
|
||||
expect(
|
||||
await f.service.inspect({ ...request, packet_limit: 10 }),
|
||||
).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisOutputError", operation: "inspect_packet_capture" },
|
||||
});
|
||||
const admitted = await f.service.inspect({ ...request, packet_limit: 90 });
|
||||
if (!admitted.ok) throw admitted.error;
|
||||
const report = packetCaptureSchema.parse(admitted.value.normalized_result);
|
||||
expect(report.output_window).toEqual({
|
||||
matching_packets: 90,
|
||||
returned_packets: 1,
|
||||
has_more: true,
|
||||
});
|
||||
expect(report.packets[0]?.["frame.number"]).toEqual(["1"]);
|
||||
});
|
||||
|
||||
it("returns canonical alias output through the public packet-capture CLI", async () => {
|
||||
const f = await fixture("alias-canonical");
|
||||
const cli = Cli.create("capture-alias-test", { sync: false });
|
||||
|
||||
@@ -17,7 +17,7 @@ it("preserves repeated field values and missing fields while discarding export i
|
||||
},
|
||||
{ _source: { layers: { "frame.number": ["7"] } } },
|
||||
]);
|
||||
expect(parsePacketOutput(output)).toEqual([
|
||||
expect(parsePacketOutput(output, 7)).toEqual([
|
||||
{ "frame.number": ["2"], "ip.addr": ["192.0.2.1", "192.0.2.2"] },
|
||||
{ "frame.number": ["7"] },
|
||||
]);
|
||||
@@ -49,7 +49,7 @@ it.each([
|
||||
])("rejects malformed producer fields: $layers", ({ layers, issue }) => {
|
||||
let failure: unknown;
|
||||
try {
|
||||
parsePacketOutput(JSON.stringify([{ _source: { layers } }]));
|
||||
parsePacketOutput(JSON.stringify([{ _source: { layers } }]), null);
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
}
|
||||
@@ -72,6 +72,7 @@ it.each([
|
||||
_source: { layers: { "frame.number": frame } },
|
||||
})),
|
||||
),
|
||||
null,
|
||||
);
|
||||
} catch (error) {
|
||||
failure = error;
|
||||
@@ -88,7 +89,18 @@ it("preserves canonical field names emitted for a selected alias", () => {
|
||||
},
|
||||
},
|
||||
]);
|
||||
expect(parsePacketOutput(output)).toEqual([
|
||||
expect(parsePacketOutput(output, null)).toEqual([
|
||||
{ "frame.number": ["1"], "dhcp.type": ["1"] },
|
||||
]);
|
||||
});
|
||||
|
||||
it("validates every frame against the input read limit while allowing filtered gaps", () => {
|
||||
const output = JSON.stringify(
|
||||
[2, 7].map((frame) => ({
|
||||
_source: { layers: { "frame.number": [String(frame)] } },
|
||||
})),
|
||||
);
|
||||
expect(parsePacketOutput(output, 7)).toHaveLength(2);
|
||||
expect(() => parsePacketOutput(output, 6)).toThrow(AnalysisOutputError);
|
||||
expect(parsePacketOutput(output, null)).toHaveLength(2);
|
||||
});
|
||||
+3
-14
@@ -230,15 +230,12 @@ export interface SignerInfoSpec {
|
||||
* from the certificate named by `names`.
|
||||
*/
|
||||
readonly serial?: Uint8Array;
|
||||
/** Complete digest algorithm OID TLV, including bounded-parser fixtures. */
|
||||
readonly digestAlgorithmOid?: Uint8Array;
|
||||
}
|
||||
|
||||
export const signerInfo = ({
|
||||
names,
|
||||
signature,
|
||||
serial,
|
||||
digestAlgorithmOid,
|
||||
}: SignerInfoSpec): Uint8Array =>
|
||||
tlv(
|
||||
0x30,
|
||||
@@ -247,7 +244,7 @@ export const signerInfo = ({
|
||||
names,
|
||||
serial === undefined ? undefined : tlv(0x02, serial),
|
||||
),
|
||||
tlv(0x30, digestAlgorithmOid ?? oid(OID_SHA256)),
|
||||
algorithm(OID_SHA256),
|
||||
algorithm(OID_ECDSA_SHA256),
|
||||
signature ?? tlv(0x04, Uint8Array.from([1, 2, 3])),
|
||||
);
|
||||
@@ -257,8 +254,6 @@ export const signedDataBlock = (options: {
|
||||
readonly certificates: readonly Uint8Array[];
|
||||
readonly signerInfos: readonly Uint8Array[];
|
||||
readonly contentType?: string;
|
||||
readonly encapsulatedContent?: Uint8Array;
|
||||
readonly digestAlgorithmOid?: Uint8Array;
|
||||
}): Uint8Array =>
|
||||
tlv(
|
||||
0x30,
|
||||
@@ -268,14 +263,8 @@ export const signedDataBlock = (options: {
|
||||
tlv(
|
||||
0x30,
|
||||
integer(1),
|
||||
tlv(0x31, tlv(0x30, options.digestAlgorithmOid ?? oid(OID_SHA256))),
|
||||
tlv(
|
||||
0x30,
|
||||
oid("1.2.840.113549.1.7.1"),
|
||||
...(options.encapsulatedContent === undefined
|
||||
? []
|
||||
: [tlv(0xa0, options.encapsulatedContent)]),
|
||||
),
|
||||
tlv(0x31, algorithm(OID_SHA256)),
|
||||
tlv(0x30, oid("1.2.840.113549.1.7.1")),
|
||||
tlv(0xa0, ...options.certificates),
|
||||
tlv(0x31, ...options.signerInfos),
|
||||
),
|
||||
|
||||
Reference in new issue
Block a user