mirror of
https://github.com/openai/codex.git
synced 2026-10-28 18:58:46 +00:00
## What changed - Build and bundle the standalone executor in `bin/` for both package variants, and advertise its path through `execServer` in `codex-package.json`. - Include the executor in Cargo and Bazel release packaging, macOS signing and notarization, and Windows signing verification. Preserve prebuilt signed binaries during packaging. - Make installers detect a missing advertised executor while continuing to accept older packages without it. ## Testing Add coverage for executor archive contents and executable permissions, signed binary preservation, and macOS entitlement verification. Add package smoke tests for initialization, version reporting, filesystem and process sandbox enforcement, and startup error parity with `codex exec-server`. GitOrigin-RevId: 5c0027fc19f16cf9c2a5bb52dbd065a9776bc2af
192 lines
7.4 KiB
Python
192 lines
7.4 KiB
Python
"""Sign and verify macOS CLI packages using the shared AKV and native tools."""
|
|
|
|
import argparse
|
|
import os
|
|
import plistlib
|
|
import subprocess
|
|
import sys
|
|
from pathlib import Path
|
|
|
|
import provisioned_macos_cli_package as bundle
|
|
|
|
|
|
def main(configuration: bundle.ProfileConfiguration | None = None):
|
|
parser = argparse.ArgumentParser(description=__doc__)
|
|
parser.add_argument("operation", choices=("sign", "verify"))
|
|
parser.add_argument("package", type=Path)
|
|
args, configuration = bundle.parse_profile_args(parser, configuration)
|
|
package = args.package.resolve(strict=True)
|
|
if not package.is_dir():
|
|
parser.error("package must be an extracted CLI package directory")
|
|
repo_root = os.environ.get("CODEX_REPO_ROOT")
|
|
if not repo_root:
|
|
parser.error("CODEX_REPO_ROOT must be set")
|
|
reports = Path(repo_root) / "signing-verification"
|
|
reports.mkdir(parents=True, exist_ok=True)
|
|
provisioned = os.environ.get("PROVISIONED_MACOS", "false") == "true"
|
|
if provisioned:
|
|
if args.operation == "sign":
|
|
configuration.validate_signing_certificate(
|
|
Path(os.environ["OAI_AKV_SIGNING_CERTIFICATE_PEM"])
|
|
)
|
|
bundle.prepare(package, reports, configuration)
|
|
else:
|
|
bundle.verify(package, reports, os.environ["TARGET"], configuration)
|
|
|
|
for relative in ("bin/codex", *bundle.HELPERS):
|
|
binary = package / relative
|
|
name = binary.name
|
|
target = binary
|
|
identifier = name
|
|
entitlements = bundle.SIGNING / f"{name}.entitlements.plist"
|
|
if provisioned and relative == "bin/codex":
|
|
binary = package / bundle.EXECUTABLE
|
|
target = package / bundle.APP
|
|
identifier = bundle.CODE_SIGNING_ID
|
|
entitlements = reports / "codex-provisioned-entitlements.plist"
|
|
if args.operation == "sign":
|
|
command = [
|
|
"bash",
|
|
str(bundle.SIGNING / "sign_macos_code.sh"),
|
|
"--target",
|
|
str(target),
|
|
"--identity",
|
|
"unused",
|
|
"--deep",
|
|
"false",
|
|
"--options",
|
|
"runtime",
|
|
"--timestamp",
|
|
"true",
|
|
]
|
|
if relative.startswith("bin/"):
|
|
command.extend(
|
|
["--identifier", identifier, "--entitlements", str(entitlements)]
|
|
)
|
|
else:
|
|
command.extend(["--identifier", f"com.openai.codex.{name}"])
|
|
subprocess.run(command, check=True)
|
|
with (reports / f"{name}-signature.yaml").open("wb") as output:
|
|
subprocess.run(
|
|
["rcodesign", "print-signature-info", str(binary)],
|
|
stdout=output,
|
|
check=True,
|
|
)
|
|
if not provisioned:
|
|
subprocess.run(
|
|
[
|
|
"bash",
|
|
str(bundle.SIGNING / "notarize_macos_binary_with_akv.sh"),
|
|
"--binary",
|
|
str(binary),
|
|
"--report-dir",
|
|
str(reports / name),
|
|
],
|
|
check=True,
|
|
)
|
|
else:
|
|
target_triple = os.environ["TARGET"]
|
|
architectures = {
|
|
"aarch64-apple-darwin": "arm64",
|
|
"x86_64-apple-darwin": "x86_64",
|
|
}
|
|
if target_triple not in architectures:
|
|
raise ValueError(f"Unexpected macOS target: {target_triple}")
|
|
subprocess.run(
|
|
["lipo", str(binary), "-verify_arch", architectures[target_triple]],
|
|
check=True,
|
|
)
|
|
# Preserve the CLI's existing login-keychain identity while checking
|
|
# its bundle/App ID and provisioning independently in bundle.verify.
|
|
requirement = (
|
|
"=anchor apple generic"
|
|
" and certificate 1[field.1.2.840.113635.100.6.2.6] exists"
|
|
" and certificate leaf[field.1.2.840.113635.100.6.1.13] exists"
|
|
)
|
|
if provisioned and relative == "bin/codex":
|
|
requirement += (
|
|
f' and identifier "{bundle.CODE_SIGNING_ID}"'
|
|
f' and certificate leaf[subject.OU] = "{configuration.team_id}"'
|
|
)
|
|
subprocess.run(
|
|
[
|
|
"codesign",
|
|
"--verify",
|
|
"--strict",
|
|
"--verbose=2",
|
|
"--test-requirement",
|
|
requirement,
|
|
str(target),
|
|
],
|
|
check=True,
|
|
)
|
|
signature = reports / f"{name}-signature.txt"
|
|
with signature.open("wb") as output:
|
|
subprocess.run(
|
|
["codesign", "-d", "--verbose=4", str(target)],
|
|
stderr=output,
|
|
check=True,
|
|
)
|
|
actual = reports / f"{name}-entitlements.plist"
|
|
with actual.open("wb") as output:
|
|
subprocess.run(
|
|
["codesign", "-d", "--entitlements", ":-", str(target)],
|
|
stdout=output,
|
|
check=True,
|
|
)
|
|
if relative.startswith("bin/"):
|
|
# Signing tools may omit the blob when no entitlements are requested.
|
|
if (
|
|
actual.stat().st_size == 0
|
|
and plistlib.loads(entitlements.read_bytes()) == {}
|
|
):
|
|
actual.write_bytes(entitlements.read_bytes())
|
|
expected = reports / f"{name}-expected.plist"
|
|
subprocess.run(
|
|
[
|
|
"plutil",
|
|
"-convert",
|
|
"xml1",
|
|
"-o",
|
|
str(expected),
|
|
str(entitlements),
|
|
],
|
|
check=True,
|
|
)
|
|
subprocess.run(["plutil", "-convert", "xml1", str(actual)], check=True)
|
|
subprocess.run(["diff", "-u", str(expected), str(actual)], check=True)
|
|
elif actual.stat().st_size:
|
|
raise ValueError(
|
|
f"Bundled helper {name} must not have signing entitlements"
|
|
)
|
|
|
|
if provisioned and args.operation == "sign":
|
|
archive = Path(os.environ["RUNNER_TEMP"]) / "provisioned-cli.zip"
|
|
subprocess.run(["zip", "-q", "-r", str(archive), "."], cwd=package, check=True)
|
|
command = [
|
|
sys.executable,
|
|
str(bundle.SIGNING / "notarize_with_akv.py"),
|
|
"--file",
|
|
str(archive),
|
|
"--report-log",
|
|
str(reports / "notarization-developer-log.json"),
|
|
"--max-wait-seconds",
|
|
"1200",
|
|
]
|
|
with (
|
|
(reports / "notarization.log").open("w") as output,
|
|
subprocess.Popen(
|
|
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
|
) as process,
|
|
):
|
|
assert process.stdout is not None
|
|
for line in process.stdout:
|
|
print(line, end="", flush=True)
|
|
output.write(line)
|
|
if returncode := process.wait():
|
|
raise subprocess.CalledProcessError(returncode, command)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|