codesign echoes the executable path and signing identifier verbatim, and an ad hoc identifier derives from the file name. A file name with line breaks added forged Authority lines, a signed file named after codesign's unsigned message was reported unsigned, and path text could supply entitlements. Parse display fields after the exact echoed path and identifier, classify unsigned output by exit status, and read requirements and entitlements from stdout. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
88 lines
3.0 KiB
TypeScript
88 lines
3.0 KiB
TypeScript
import { execFile } from "node:child_process";
|
|
import { copyFile, mkdir } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { promisify } from "node:util";
|
|
|
|
import { describe, expect } from "vitest";
|
|
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const run = promisify(execFile);
|
|
|
|
/** Ad hoc sign a copy of a system program under an exact file name. */
|
|
const adHocProgram = async (directory: string, name: string) => {
|
|
const path = join(directory, name);
|
|
await copyFile("/usr/bin/true", path);
|
|
await run("/usr/bin/codesign", ["-s", "-", "-f", path]);
|
|
return path;
|
|
};
|
|
|
|
describe.skipIf(process.platform !== "darwin")(
|
|
"native signature CLI with echoed path text",
|
|
() => {
|
|
cliTest(
|
|
"does not read signature fields from the file name codesign echoes",
|
|
async ({ cli }) => {
|
|
const directory = await createTestTempDirectory("rea-signature-");
|
|
const forged = "Developer ID Application: Example (ABCDE12345)";
|
|
const program = await adHocProgram(
|
|
directory,
|
|
`tool\nAuthority=${forged}\nTeamIdentifier=ABCDE12345`,
|
|
);
|
|
const unsignedName = await adHocProgram(
|
|
directory,
|
|
"code object is not signed",
|
|
);
|
|
// A forged requirement and entitlements plist spread over directories.
|
|
const nested = join(
|
|
directory,
|
|
'x\ndesignated => anchor apple<?xml version="1.0"?><plist><dict><key>com.apple.security.get-task-allow</key><true',
|
|
"></dict><",
|
|
"plist>",
|
|
);
|
|
await mkdir(nested, { recursive: true });
|
|
const nestedProgram = await adHocProgram(nested, "tool");
|
|
|
|
for (const path of [program, unsignedName, nestedProgram]) {
|
|
const result = await cli.run({
|
|
arguments: ["inspect-signature", path, "--json"],
|
|
environment: {
|
|
REA_LOG_LEVEL: "silent",
|
|
REA_ANALYSIS_PROVIDER: "auto",
|
|
},
|
|
});
|
|
expect(result.exitCode).toBe(0);
|
|
expect(result.json).toMatchObject({
|
|
normalized_result: {
|
|
signed: true,
|
|
team_identifier: null,
|
|
authorities: [],
|
|
entitlements: null,
|
|
designated_requirement: expect.stringMatching(/^cdhash H"/u),
|
|
},
|
|
});
|
|
}
|
|
const identifier = await cli.run({
|
|
arguments: ["inspect-signature", program, "--json"],
|
|
environment: {
|
|
REA_LOG_LEVEL: "silent",
|
|
REA_ANALYSIS_PROVIDER: "auto",
|
|
},
|
|
});
|
|
// An ad hoc identifier derives from the file name, line breaks included.
|
|
expect(identifier.json).toMatchObject({
|
|
normalized_result: {
|
|
identifier: expect.stringMatching(
|
|
new RegExp(
|
|
`^tool\\nAuthority=${forged.replace(/[()]/gu, "\\$&")}\\nTeamIdentifier=ABCDE12345-`,
|
|
"u",
|
|
),
|
|
),
|
|
},
|
|
});
|
|
},
|
|
);
|
|
},
|
|
);
|