@rightkit/release 0.2.82 → 0.2.83
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build-release.mjs +12 -1
- package/macos-signing-contract.mjs +1 -0
- package/notarize-macos.mjs +57 -0
- package/package.json +1 -1
- package/release.mjs +78 -1
- package/rightkit-versions.json +7 -4
- package/sign-macos.mjs +66 -0
- package/sign-release-manifest.mjs +0 -1
- package/sign-windows.mjs +0 -1
package/build-release.mjs
CHANGED
|
@@ -42,7 +42,7 @@ import { cancelAndReapManagedRequest, firstManagedRequestId, releaseProgressLimi
|
|
|
42
42
|
// These four files are the ones whose behaviour the cached target directory can
|
|
43
43
|
// outlive.
|
|
44
44
|
|
|
45
|
-
const PIPELINE_FINGERPRINT_SOURCES = ["release.mjs", "sign-windows.mjs", "tauri-bundle-marker.mjs", "nsis-payload.mjs"];
|
|
45
|
+
const PIPELINE_FINGERPRINT_SOURCES = ["release.mjs", "sign-windows.mjs", "sign-macos.mjs", "notarize-macos.mjs", "tauri-bundle-marker.mjs", "nsis-payload.mjs"];
|
|
46
46
|
const PIPELINE_FINGERPRINT = createHash("sha256")
|
|
47
47
|
.update(PIPELINE_FINGERPRINT_SOURCES.map((file) => `${file}:${createHash("sha256").update(readFileSync(path.join(path.dirname(fileURLToPath(import.meta.url)), file))).digest("hex")}`).join("\n"))
|
|
48
48
|
.digest("hex")
|
|
@@ -177,6 +177,17 @@ try {
|
|
|
177
177
|
const configured = phase === "raw-exe" ? target.sign?.receipt : phase === "installer" ? target.sign?.installerReceipt : undefined;
|
|
178
178
|
return configured ? path.resolve(appRoot, configured) : path.join(receiptRoot, `windows-${phase}.json`);
|
|
179
179
|
}),
|
|
180
|
+
} : platform === "mac" ? {
|
|
181
|
+
contract: target.signingContract ?? "<missing>",
|
|
182
|
+
pipeline: PIPELINE_FINGERPRINT,
|
|
183
|
+
configSha256: hashFile(configPath),
|
|
184
|
+
// Mac portable releases produce one Developer ID receipt before packaging
|
|
185
|
+
// and one Accepted notarization receipt after. Both must be part of cache
|
|
186
|
+
// identity and seal evidence, including configured receipt overrides.
|
|
187
|
+
receiptInputs: [
|
|
188
|
+
target.sign?.receipt ? path.resolve(appRoot, target.sign.receipt) : path.join(receiptRoot, "macos-signing.json"),
|
|
189
|
+
target.notarize?.receipt ? path.resolve(appRoot, target.notarize.receipt) : path.join(receiptRoot, "macos-notarization.json"),
|
|
190
|
+
],
|
|
180
191
|
} : null;
|
|
181
192
|
if (signingIdentity) inputHashes[".right-release/signing-identity.json"] = hashFileText(JSON.stringify(signingIdentity));
|
|
182
193
|
const cargoLock = nativeLayout.lockPath;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MACOS_PORTABLE_SIGNING_CONTRACT = "macos-developer-id-notarized-portable-v1";
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { notarytoolAuthArgs } from "./notary-auth.mjs";
|
|
7
|
+
import { MACOS_PORTABLE_SIGNING_CONTRACT } from "./macos-signing-contract.mjs";
|
|
8
|
+
|
|
9
|
+
const args = process.argv.slice(2);
|
|
10
|
+
const dryRun = args.includes("--dry-run");
|
|
11
|
+
const receiptIndex = args.indexOf("--receipt");
|
|
12
|
+
const profileIndex = args.indexOf("--profile");
|
|
13
|
+
const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
|
|
14
|
+
const profile = profileIndex >= 0 ? args[profileIndex + 1] : undefined;
|
|
15
|
+
const archiveArgs = args.filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && arg !== "--profile" && (receiptIndex < 0 || index !== receiptIndex + 1) && (profileIndex < 0 || index !== profileIndex + 1));
|
|
16
|
+
if (archiveArgs.length !== 1) fail("usage: node notarize-macos.mjs [--dry-run] --receipt <path> [--profile <name>] <archive>");
|
|
17
|
+
if (!receiptPath) fail("--receipt requires a path");
|
|
18
|
+
const archive = path.resolve(archiveArgs[0]);
|
|
19
|
+
if (dryRun) {
|
|
20
|
+
console.log(`dry-run: notarize ${archive}`);
|
|
21
|
+
process.exit(0);
|
|
22
|
+
}
|
|
23
|
+
if (process.platform !== "darwin") fail("macOS notarization must run on macOS");
|
|
24
|
+
if (!existsSync(archive)) fail(`missing archive: ${archive}`);
|
|
25
|
+
|
|
26
|
+
let authArgs;
|
|
27
|
+
try { authArgs = notarytoolAuthArgs({ profile }); } catch (error) { fail(error.message); }
|
|
28
|
+
const output = run("xcrun", ["notarytool", "submit", archive, "--wait", "--output-format", "json", ...authArgs]);
|
|
29
|
+
let result;
|
|
30
|
+
try { result = JSON.parse(output); } catch { fail(`notarytool did not return JSON for ${archive}`); }
|
|
31
|
+
if (result.status !== "Accepted") fail(`notarization was not accepted for ${archive}: ${result.status ?? "missing status"}`);
|
|
32
|
+
|
|
33
|
+
const absoluteReceipt = path.resolve(receiptPath);
|
|
34
|
+
mkdirSync(path.dirname(absoluteReceipt), { recursive: true });
|
|
35
|
+
writeFileSync(absoluteReceipt, `${JSON.stringify({
|
|
36
|
+
schema: 1,
|
|
37
|
+
signingContract: MACOS_PORTABLE_SIGNING_CONTRACT,
|
|
38
|
+
archive: { file: archive, ...fileEvidence(archive) },
|
|
39
|
+
notarization: { status: result.status, id: result.id ?? result.submissionId ?? null },
|
|
40
|
+
}, null, 2)}\n`);
|
|
41
|
+
|
|
42
|
+
function fileEvidence(file) {
|
|
43
|
+
const bytes = readFileSync(file);
|
|
44
|
+
return { sha256: createHash("sha256").update(bytes).digest("hex"), sizeBytes: statSync(file).size };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function run(cmd, runArgs) {
|
|
48
|
+
const result = spawnSync(cmd, runArgs, { encoding: "utf8" });
|
|
49
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`.trim();
|
|
50
|
+
if (result.status !== 0) fail(`${cmd} ${runArgs.join(" ")} failed: ${output}`);
|
|
51
|
+
return output;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function fail(message) {
|
|
55
|
+
console.error(`right-notarize-macos: ${message}`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rightkit/release",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.83",
|
|
4
4
|
"description": "Portable Right Suite release CLI/SDK: signed native archives, direct bootstrap transactions, immutable GitHub Release upload, R2 bootstrap publication, and add-on adoption.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"type": "module",
|
package/release.mjs
CHANGED
|
@@ -22,11 +22,14 @@ import { patchTauriBundleType } from "./tauri-bundle-marker.mjs";
|
|
|
22
22
|
import { verifyNsisEmbeddedBinary } from "./nsis-payload.mjs";
|
|
23
23
|
import { terminateProcessTree } from "./heavy-command.mjs";
|
|
24
24
|
import { materializeHardeningEvidence } from "./hardening-evidence.mjs";
|
|
25
|
+
import { MACOS_PORTABLE_SIGNING_CONTRACT } from "./macos-signing-contract.mjs";
|
|
25
26
|
|
|
26
27
|
const TOOL_ROOT = path.dirname(fileURLToPath(import.meta.url));
|
|
27
28
|
const HARDENING_SCAN = path.resolve(TOOL_ROOT, "hardeningscan.mjs");
|
|
28
29
|
const UPLOAD_LARGE = path.resolve(TOOL_ROOT, "upload-large.mjs");
|
|
29
30
|
const SIGN_WINDOWS = path.resolve(TOOL_ROOT, "sign-windows.mjs");
|
|
31
|
+
const SIGN_MACOS = path.resolve(TOOL_ROOT, "sign-macos.mjs");
|
|
32
|
+
const NOTARIZE_MACOS = path.resolve(TOOL_ROOT, "notarize-macos.mjs");
|
|
30
33
|
const SIGN_UPDATER = path.resolve(TOOL_ROOT, "sign-updater.mjs");
|
|
31
34
|
const RIGHTKIT_VERSIONS = JSON.parse(readFileSync(path.resolve(TOOL_ROOT, "rightkit-versions.json"), "utf8"));
|
|
32
35
|
const VERSION = RIGHTKIT_VERSIONS.stagedNpm?.["@rightkit/release"] ?? RIGHTKIT_VERSIONS.npm["@rightkit/release"];
|
|
@@ -124,6 +127,12 @@ if (opts.platform === "win") {
|
|
|
124
127
|
if (isNsis && target.sign.prePackageFiles.some((file) => target.sign.files.includes(file))) fail(`${config.app ?? "app"} win raw EXE and installer signing files must be distinct`);
|
|
125
128
|
if (isPortable && target.sign?.files?.length) fail(`${config.app ?? "app"} win portable release must sign every executable through sign.prePackageFiles and omit sign.files`);
|
|
126
129
|
}
|
|
130
|
+
if (opts.platform === "mac") {
|
|
131
|
+
if (target.signingContract !== MACOS_PORTABLE_SIGNING_CONTRACT) fail(`${config.app ?? "app"} mac must declare signingContract: ${MACOS_PORTABLE_SIGNING_CONTRACT}`);
|
|
132
|
+
if (!target.prePackage?.cmd) fail(`${config.app ?? "app"} mac must declare prePackage to materialize Developer ID signing files`);
|
|
133
|
+
if (!target.sign?.prePackageFiles?.length) fail(`${config.app ?? "app"} mac must declare at least one sign.prePackageFiles`);
|
|
134
|
+
if (!target.notarize?.file) fail(`${config.app ?? "app"} mac must declare notarize.file`);
|
|
135
|
+
}
|
|
127
136
|
if (opts.upload && target.publishBlocked) fail(`${config.app ?? "app"} ${opts.platform} publish blocked: ${target.publishBlocked}`);
|
|
128
137
|
/**
|
|
129
138
|
* Assemble preflight inputs from the app's own files. Kept here (not in
|
|
@@ -240,6 +249,11 @@ if (opts.platform === "win") {
|
|
|
240
249
|
signedFiles: rawFiles,
|
|
241
250
|
});
|
|
242
251
|
}
|
|
252
|
+
} else if (opts.platform === "mac") {
|
|
253
|
+
const signingFiles = target.sign.prePackageFiles.map((p) => path.resolve(root, p));
|
|
254
|
+
await runCommand(target.prePackage, root);
|
|
255
|
+
for (const file of signingFiles) await mustExist(file, `missing macOS Developer ID signing artifact: ${file}`);
|
|
256
|
+
await signMacos(signingFiles, root);
|
|
243
257
|
} else if (nativeAssembly) {
|
|
244
258
|
if (nativeAssembly.packageHook) await runCommand(nativeAssembly.packageHook, root);
|
|
245
259
|
if (nativeFinalizer) {
|
|
@@ -279,6 +293,12 @@ if (target.postSign && opts.platform !== "win") {
|
|
|
279
293
|
await runCommand(target.postSign, root);
|
|
280
294
|
}
|
|
281
295
|
|
|
296
|
+
if (opts.platform === "mac") {
|
|
297
|
+
const archive = path.resolve(root, target.notarize.file);
|
|
298
|
+
await mustExist(archive, `missing macOS notarization archive: ${archive}`);
|
|
299
|
+
await notarizeMacos(archive, root);
|
|
300
|
+
}
|
|
301
|
+
|
|
282
302
|
{
|
|
283
303
|
const scanTargets = target.hardening ?? target.artifacts ?? [];
|
|
284
304
|
if (scanTargets.length) {
|
|
@@ -651,9 +671,14 @@ async function validateHostedWorkflows(root, appName, workflows, policy) {
|
|
|
651
671
|
if (!source.startsWith("# Managed by right-git")) {
|
|
652
672
|
fail(`${appName} ${workflow} is not marked as right-git managed`);
|
|
653
673
|
}
|
|
674
|
+
const requestedSecrets = [...source.matchAll(/\bsecrets\.([A-Z0-9_]+)/gi)].map((match) => match[1]);
|
|
675
|
+
const allowedMacosSecrets = new Set(["APPLE_CERTIFICATE_BASE64", "APPLE_CERTIFICATE_PASSWORD", "APPLE_KEYCHAIN_PASSWORD", "APPLE_API_KEY_BASE64", "APPLE_API_KEY", "APPLE_API_ISSUER"]);
|
|
676
|
+
const macosSigningAllowed = workflow === "release-candidate.yml" && manifest?.candidate?.signMacos === true;
|
|
654
677
|
if (/^\s*permissions:\s*write-all\s*$/mi.test(source)
|
|
655
678
|
|| /^\s*(?:actions|attestations|checks|contents|deployments|discussions|id-token|issues|models|packages|pages|pull-requests|security-events|statuses):\s*write\s*$/mi.test(source)
|
|
656
|
-
||
|
|
679
|
+
|| /pull_request_target\s*:/i.test(source)
|
|
680
|
+
|| /\bsecrets\s*\[/i.test(source)
|
|
681
|
+
|| requestedSecrets.some((name) => !macosSigningAllowed || !allowedMacosSecrets.has(name))) {
|
|
657
682
|
fail(`${appName} right-git managed ${workflow} requests release-capable permissions or secrets`);
|
|
658
683
|
}
|
|
659
684
|
}
|
|
@@ -705,6 +730,58 @@ function windowsReceiptPath(root, phase) {
|
|
|
705
730
|
return path.join(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(root, ".right-release", "receipts"), `windows-${phase}.json`);
|
|
706
731
|
}
|
|
707
732
|
|
|
733
|
+
async function signMacos(files, root) {
|
|
734
|
+
const receipt = macosReceiptPath(root, "signing");
|
|
735
|
+
const args = [SIGN_MACOS];
|
|
736
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
737
|
+
args.push("--receipt", receipt, ...files);
|
|
738
|
+
if (!opts.dryRun && existsSync(receipt)) unlinkSync(receipt);
|
|
739
|
+
console.log("right-release: signing macOS Developer ID files");
|
|
740
|
+
await run("node", args, root);
|
|
741
|
+
if (!opts.dryRun) verifyMacosSigningReceipt(receipt, files);
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
async function notarizeMacos(archive, root) {
|
|
745
|
+
const receipt = macosReceiptPath(root, "notarization");
|
|
746
|
+
const args = [NOTARIZE_MACOS];
|
|
747
|
+
if (opts.dryRun) args.push("--dry-run");
|
|
748
|
+
args.push("--receipt", receipt);
|
|
749
|
+
if (target.notarize.profile) args.push("--profile", target.notarize.profile);
|
|
750
|
+
args.push(archive);
|
|
751
|
+
if (!opts.dryRun && existsSync(receipt)) unlinkSync(receipt);
|
|
752
|
+
console.log("right-release: notarizing macOS archive");
|
|
753
|
+
await run("node", args, root);
|
|
754
|
+
if (!opts.dryRun) verifyMacosNotarizationReceipt(receipt, archive);
|
|
755
|
+
}
|
|
756
|
+
|
|
757
|
+
function macosReceiptPath(root, phase) {
|
|
758
|
+
const configured = phase === "signing" ? target.sign?.receipt : target.notarize?.receipt;
|
|
759
|
+
if (configured) return path.resolve(root, configured);
|
|
760
|
+
return path.join(process.env.RIGHT_RELEASE_STATE_ROOT ?? path.join(root, ".right-release", "receipts"), `macos-${phase}.json`);
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
function verifyMacosSigningReceipt(receipt, files) {
|
|
764
|
+
if (!existsSync(receipt)) fail(`missing macOS Developer ID signing receipt: ${receipt}`);
|
|
765
|
+
let evidence;
|
|
766
|
+
try { evidence = JSON.parse(readFileSync(receipt, "utf8")); } catch { fail(`invalid macOS Developer ID signing receipt: ${receipt}`); }
|
|
767
|
+
const expectedIdentity = process.env.APPLE_DEVELOPER_ID?.trim();
|
|
768
|
+
const expected = files.map((file) => path.resolve(file)).sort();
|
|
769
|
+
const actual = Array.isArray(evidence.files) ? evidence.files.map((item) => path.resolve(item.file)).sort() : [];
|
|
770
|
+
const hashes = new Map(files.map((file) => [path.resolve(file), createHash("sha256").update(readFileSync(file)).digest("hex")]));
|
|
771
|
+
if (evidence.schema !== 1 || evidence.signingContract !== MACOS_PORTABLE_SIGNING_CONTRACT || !expectedIdentity || evidence.identity !== expectedIdentity
|
|
772
|
+
|| JSON.stringify(expected) !== JSON.stringify(actual)
|
|
773
|
+
|| evidence.files.some((item) => item.codesign !== "strict-valid" || item.identity !== expectedIdentity || item.timestampPresent !== true || item.after?.sha256 !== hashes.get(path.resolve(item.file)))) fail(`invalid macOS Developer ID signing receipt evidence: ${receipt}`);
|
|
774
|
+
}
|
|
775
|
+
|
|
776
|
+
function verifyMacosNotarizationReceipt(receipt, archive) {
|
|
777
|
+
if (!existsSync(receipt)) fail(`missing macOS notarization receipt: ${receipt}`);
|
|
778
|
+
let evidence;
|
|
779
|
+
try { evidence = JSON.parse(readFileSync(receipt, "utf8")); } catch { fail(`invalid macOS notarization receipt: ${receipt}`); }
|
|
780
|
+
const sha256 = createHash("sha256").update(readFileSync(archive)).digest("hex");
|
|
781
|
+
if (evidence.schema !== 1 || evidence.signingContract !== MACOS_PORTABLE_SIGNING_CONTRACT || path.resolve(evidence.archive?.file ?? "") !== archive
|
|
782
|
+
|| evidence.archive?.sha256 !== sha256 || evidence.notarization?.status !== "Accepted") fail(`invalid macOS notarization receipt evidence: ${receipt}`);
|
|
783
|
+
}
|
|
784
|
+
|
|
708
785
|
/**
|
|
709
786
|
* Close the gap between "the raw EXE was signed" and "the installer ships that
|
|
710
787
|
* EXE". Three independent facts, each able to fail on its own:
|
package/rightkit-versions.json
CHANGED
|
@@ -14,7 +14,7 @@
|
|
|
14
14
|
},
|
|
15
15
|
"stagedNpm": {
|
|
16
16
|
"@rightkit/ax": "0.2.1",
|
|
17
|
-
"@rightkit/git": "0.2.
|
|
17
|
+
"@rightkit/git": "0.2.7",
|
|
18
18
|
"@rightkit/hooks": "0.1.1",
|
|
19
19
|
"@rightkit/legal": "0.3.1",
|
|
20
20
|
"@rightkit/legal-ui": "0.1.1",
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"@rightkit/logs": "0.1.4",
|
|
23
23
|
"@rightkit/platform-ui": "0.1.1",
|
|
24
24
|
"@rightkit/qa": "0.2.1",
|
|
25
|
-
"@rightkit/release": "0.2.
|
|
25
|
+
"@rightkit/release": "0.2.83",
|
|
26
26
|
"@rightkit/tauri": "0.1.1",
|
|
27
27
|
"@rightkit/updates": "0.2.4"
|
|
28
28
|
},
|
|
@@ -34,7 +34,9 @@
|
|
|
34
34
|
"0.2.1",
|
|
35
35
|
"0.2.2",
|
|
36
36
|
"0.2.3",
|
|
37
|
-
"0.2.4"
|
|
37
|
+
"0.2.4",
|
|
38
|
+
"0.2.5",
|
|
39
|
+
"0.2.6"
|
|
38
40
|
],
|
|
39
41
|
"@rightkit/legal-ui": [
|
|
40
42
|
"0.1.0"
|
|
@@ -81,7 +83,8 @@
|
|
|
81
83
|
"0.2.73",
|
|
82
84
|
"0.2.74",
|
|
83
85
|
"0.2.75",
|
|
84
|
-
"0.2.76"
|
|
86
|
+
"0.2.76",
|
|
87
|
+
"0.2.82"
|
|
85
88
|
],
|
|
86
89
|
"@rightkit/qa": [
|
|
87
90
|
"0.1.0",
|
package/sign-macos.mjs
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createHash } from "node:crypto";
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from "node:fs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { spawnSync } from "node:child_process";
|
|
6
|
+
import { MACOS_PORTABLE_SIGNING_CONTRACT } from "./macos-signing-contract.mjs";
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(2);
|
|
9
|
+
const dryRun = args.includes("--dry-run");
|
|
10
|
+
const receiptIndex = args.indexOf("--receipt");
|
|
11
|
+
const receiptPath = receiptIndex >= 0 ? args[receiptIndex + 1] : null;
|
|
12
|
+
const files = args
|
|
13
|
+
.filter((arg, index) => arg !== "--dry-run" && arg !== "--receipt" && (receiptIndex < 0 || index !== receiptIndex + 1))
|
|
14
|
+
.map((arg) => path.resolve(arg));
|
|
15
|
+
|
|
16
|
+
if (!files.length) fail("usage: node sign-macos.mjs [--dry-run] --receipt <path> <file>...");
|
|
17
|
+
if (!receiptPath) fail("--receipt requires a path");
|
|
18
|
+
if (dryRun) {
|
|
19
|
+
console.log(`dry-run: Developer ID signing ${files.join(", ")}`);
|
|
20
|
+
process.exit(0);
|
|
21
|
+
}
|
|
22
|
+
if (process.platform !== "darwin") fail("macOS signing must run on macOS");
|
|
23
|
+
const identity = process.env.APPLE_DEVELOPER_ID?.trim();
|
|
24
|
+
if (!identity) fail("set APPLE_DEVELOPER_ID to an installed Developer ID Application signing identity");
|
|
25
|
+
for (const file of files) if (!existsSync(file)) fail(`missing file: ${file}`);
|
|
26
|
+
|
|
27
|
+
const evidence = [];
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
const before = fileEvidence(file);
|
|
30
|
+
run("codesign", ["--force", "--options", "runtime", "--timestamp", "--sign", identity, file]);
|
|
31
|
+
const verification = run("codesign", ["--verify", "--strict", "--verbose=4", file]);
|
|
32
|
+
const details = run("codesign", ["-dvv", file]);
|
|
33
|
+
if (!/valid on disk/i.test(verification) || !/satisfies its designated requirement/i.test(verification)) {
|
|
34
|
+
fail(`strict codesign verification failed for ${file}`);
|
|
35
|
+
}
|
|
36
|
+
if (!new RegExp(`^Authority=${escapeRegExp(identity)}$`, "m").test(details) && !details.includes(`Authority=${identity}`)) {
|
|
37
|
+
fail(`codesign identity mismatch for ${file}: expected ${identity}`);
|
|
38
|
+
}
|
|
39
|
+
evidence.push({ file, before, after: fileEvidence(file), codesign: "strict-valid", identity, timestampPresent: /(?:Timestamp|Signed Time)[=:]/i.test(details) });
|
|
40
|
+
if (!evidence.at(-1).timestampPresent) fail(`Developer ID timestamp is missing for ${file}`);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const absoluteReceipt = path.resolve(receiptPath);
|
|
44
|
+
mkdirSync(path.dirname(absoluteReceipt), { recursive: true });
|
|
45
|
+
writeFileSync(absoluteReceipt, `${JSON.stringify({ schema: 1, signingContract: MACOS_PORTABLE_SIGNING_CONTRACT, identity, files: evidence }, null, 2)}\n`);
|
|
46
|
+
|
|
47
|
+
function fileEvidence(file) {
|
|
48
|
+
const bytes = readFileSync(file);
|
|
49
|
+
return { sha256: createHash("sha256").update(bytes).digest("hex"), sizeBytes: statSync(file).size };
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function run(cmd, runArgs) {
|
|
53
|
+
const result = spawnSync(cmd, runArgs, { encoding: "utf8" });
|
|
54
|
+
const output = `${result.stdout ?? ""}\n${result.stderr ?? ""}`;
|
|
55
|
+
if (result.status !== 0) fail(`${cmd} ${runArgs.join(" ")} failed: ${output.trim()}`);
|
|
56
|
+
return output;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function escapeRegExp(value) {
|
|
60
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function fail(message) {
|
|
64
|
+
console.error(`right-sign-macos: ${message}`);
|
|
65
|
+
process.exit(1);
|
|
66
|
+
}
|
package/sign-windows.mjs
CHANGED