diff --git a/.github/scripts/macos-signing/provisioned_macos_cli_package.py b/.github/scripts/macos-signing/provisioned_macos_cli_package.py index 5266feabff..e1d79eca83 100644 --- a/.github/scripts/macos-signing/provisioned_macos_cli_package.py +++ b/.github/scripts/macos-signing/provisioned_macos_cli_package.py @@ -188,15 +188,20 @@ def verify(package, reports, expected_target, configuration: ProfileConfiguratio binary = package / relative target = package / APP if relative == EXECUTABLE else binary prefix = reports / f"{binary.name}-cert-" - subprocess.run( - ["codesign", "-d", f"--extract-certificates={prefix}", str(target)], - check=True, - ) - if ( - hashlib.sha256(Path(f"{prefix}0").read_bytes()).hexdigest() - != configuration.certificate_sha256 - ): - raise ValueError(f"Unexpected signing certificate: {relative}") + verify_certificate(target, prefix, configuration) + + +def verify_certificate(target, prefix, configuration: ProfileConfiguration): + """Require the independently approved leaf certificate for each signed executable.""" + subprocess.run( + ["codesign", "-d", f"--extract-certificates={prefix}", str(target)], + check=True, + ) + if ( + hashlib.sha256(Path(f"{prefix}0").read_bytes()).hexdigest() + != configuration.certificate_sha256 + ): + raise ValueError(f"Unexpected signing certificate: {target}") def parse_profile_args(parser, configuration: ProfileConfiguration | None): diff --git a/.github/scripts/macos-signing/sign_macos_cli_package.py b/.github/scripts/macos-signing/sign_macos_cli_package.py index 8e3ef6726f..a9f96dedb0 100644 --- a/.github/scripts/macos-signing/sign_macos_cli_package.py +++ b/.github/scripts/macos-signing/sign_macos_cli_package.py @@ -12,7 +12,7 @@ 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("operation", choices=("sign", "sign-only", "verify")) parser.add_argument("package", type=Path) args, configuration = bundle.parse_profile_args(parser, configuration) package = args.package.resolve(strict=True) @@ -24,11 +24,13 @@ def main(configuration: bundle.ProfileConfiguration | None = None): reports = Path(repo_root) / "signing-verification" reports.mkdir(parents=True, exist_ok=True) provisioned = os.environ.get("PROVISIONED_MACOS", "false") == "true" + signing = args.operation in ("sign", "sign-only") + if signing: + configuration.validate_signing_certificate( + Path(os.environ["OAI_AKV_SIGNING_CERTIFICATE_PEM"]) + ) if provisioned: - if args.operation == "sign": - configuration.validate_signing_certificate( - Path(os.environ["OAI_AKV_SIGNING_CERTIFICATE_PEM"]) - ) + if signing: bundle.prepare(package, reports, configuration) else: bundle.verify(package, reports, os.environ["TARGET"], configuration) @@ -44,7 +46,7 @@ def main(configuration: bundle.ProfileConfiguration | None = None): target = package / bundle.APP identifier = bundle.CODE_SIGNING_ID entitlements = reports / "codex-provisioned-entitlements.plist" - if args.operation == "sign": + if signing: command = [ "bash", str(bundle.SIGNING / "sign_macos_code.sh"), @@ -72,7 +74,7 @@ def main(configuration: bundle.ProfileConfiguration | None = None): stdout=output, check=True, ) - if not provisioned: + if args.operation == "sign" and not provisioned: subprocess.run( [ "bash", @@ -103,11 +105,11 @@ def main(configuration: bundle.ProfileConfiguration | None = None): " 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" ) + requirement += ( + f' and certificate leaf[subject.OU] = "{configuration.team_id}"' + ) if provisioned and relative == "bin/codex": - requirement += ( - f' and identifier "{bundle.CODE_SIGNING_ID}"' - f' and certificate leaf[subject.OU] = "{configuration.team_id}"' - ) + requirement += f' and identifier "{bundle.CODE_SIGNING_ID}"' subprocess.run( [ "codesign", @@ -120,6 +122,10 @@ def main(configuration: bundle.ProfileConfiguration | None = None): ], check=True, ) + if not provisioned: + bundle.verify_certificate( + target, reports / f"{name}-cert-", configuration + ) signature = reports / f"{name}-signature.txt" with signature.open("wb") as output: subprocess.run( diff --git a/.github/scripts/macos-signing/test_sign_macos_cli_package.py b/.github/scripts/macos-signing/test_sign_macos_cli_package.py index 5ed7349397..8c8fafe6d9 100644 --- a/.github/scripts/macos-signing/test_sign_macos_cli_package.py +++ b/.github/scripts/macos-signing/test_sign_macos_cli_package.py @@ -26,7 +26,7 @@ with open(os.environ["CALL_LOG"], "a") as log: if name == "codesign": for arg in args: if arg.startswith("--extract-certificates="): - Path(arg.split("=", 1)[1] + "0").write_bytes(Path(os.environ["MOCK_CERTIFICATE"]).read_bytes()) + Path(arg.split("=", 1)[1] + "0").write_bytes(b"wrong certificate" if os.environ.get("MOCK_WRONG_CERTIFICATE") else Path(os.environ["MOCK_CERTIFICATE"]).read_bytes()) if "--test-requirement" in args and os.environ.get("MOCK_WRONG_SIGNING_IDENTITY"): print("code failed to satisfy specified code requirement(s)", file=sys.stderr) sys.exit(1) @@ -149,6 +149,26 @@ class MacosSigningTests(ProvisioningTestCase): check=False, ) + def test_sign_only_signs_standard_package_without_notarization(self): + with tempfile.TemporaryDirectory() as directory: + root = Path(directory).resolve() + package, driver, env = self.fixture(root, False, "aarch64-apple-darwin") + result = self.run_driver(driver, "sign-only", package, env) + self.assertEqual(result.returncode, 0, result.stderr) + calls = [ + json.loads(line) + for line in (root / "calls.jsonl").read_text().splitlines() + ] + self.assertEqual( + [ + call[call.index("--target") + 1] + for call in calls + if call[0] == "sign_macos_code.sh" + ], + [str(package / path) for path in ("bin/codex", *bundle.HELPERS)], + ) + self.assertFalse(any(call[0].startswith("notarize") for call in calls)) + def test_standard_and_provisioned_signing_and_verification(self): for provisioned, target, omit_empty in itertools.product( (False, True), @@ -333,6 +353,7 @@ class MacosSigningTests(ProvisioningTestCase): ( "MOCK_WRONG_SIGNING_IDENTITY", "MOCK_WRONG_ENTITLEMENTS", + "MOCK_WRONG_CERTIFICATE", "MOCK_EXECUTOR_ENTITLEMENTS", ), ): @@ -353,6 +374,8 @@ class MacosSigningTests(ProvisioningTestCase): "code failed to satisfy specified code requirement(s)", result.stderr, ) + elif failure == "MOCK_WRONG_CERTIFICATE": + self.assertIn("Unexpected signing certificate", result.stderr) else: self.assertIn("unexpected", result.stdout)