Files
codex/.github/scripts/macos-signing/notarize_macos_binary_with_rcodesign.sh
T
Eric Burke ad2012d645 ci: sign macOS release artifacts with Azure Key Vault (#26252)
## Why

The public Codex release workflow needs to sign and notarize macOS
binaries and DMGs without placing the Developer ID private key in
GitHub. This moves the private-key operation behind the protected
`codesigning` environment and uses GitHub OIDC with Azure Key Vault
PKCS#11, while preserving the existing external `build_unsigned` /
`promote_signed` fallback.

## What changed

- Add a reusable AKV PKCS11 setup action that authenticates to Azure
with OIDC, downloads pinned signing tools, verifies their SHA-256
digests, and loads the public signing certificate from Key Vault.
- Replace the legacy macOS signing action with scripts that support
AKV-backed `rcodesign`, notarize signed binaries and DMGs, and staple
DMG notarization tickets.
- Restructure `rust-release.yml` so macOS builds produce unsigned
artifacts first, protected jobs perform signing and notarization, macOS
runners package and verify the results, and release publishing waits for
verified artifacts.
- Preserve the manual external-signing handoff flow and make manual-mode
conditions explicit.
- Move the Codex entitlements file alongside the signing scripts and
update CODEOWNERS for the new signing surfaces.

## Verification

- [Live protected signing workflow
run](https://github.com/openai/codex/actions/runs/26903610631) completed
successfully for both macOS architectures, including binary
signing/notarization, DMG signing/notarization, and final artifact
verification.
- Downloaded both signed DMGs and independently verified their checksums
and strict signatures.
- Confirmed `xcrun stapler validate` succeeds and Gatekeeper accepts
both DMGs as `Notarized Developer ID`.
- Mounted both DMGs and confirmed the contained `codex` and
`codex-responses-api-proxy` binaries have valid Developer ID signatures
for the expected architectures.

---------

Co-authored-by: shijie-openai <shijie.rao@openai.com>
2026-06-03 20:34:51 -07:00

132 lines
3.4 KiB
Bash
Executable File

#!/usr/bin/env bash
# Submits a signed standalone macOS binary to Apple notarization through
# rcodesign. Standalone binaries cannot carry a stapled ticket, so the binary
# is submitted in a ZIP and the successful notarization log is retained.
set -euo pipefail
usage() {
cat >&2 <<'EOF'
Usage: notarize_macos_binary_with_rcodesign.sh --binary PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
Options:
--binary PATH Signed standalone macOS binary to notarize.
--report-dir PATH Directory for notarization logs.
--max-wait-seconds SECONDS Maximum rcodesign notarization wait time.
EOF
}
binary_path=""
report_dir="${RUNNER_TEMP:-/tmp}/macos-binary-notarization-verification"
max_wait_seconds="600"
while [[ $# -gt 0 ]]; do
case "$1" in
--binary)
binary_path="${2:-}"
shift 2
;;
--report-dir)
report_dir="${2:-}"
shift 2
;;
--max-wait-seconds)
max_wait_seconds="${2:-}"
shift 2
;;
-h|--help)
usage
exit 0
;;
*)
echo "Unknown notarization argument: $1" >&2
usage
exit 2
;;
esac
done
if [[ -z "$binary_path" ]]; then
echo "--binary is required." >&2
usage
exit 2
fi
if [[ ! -f "$binary_path" ]]; then
echo "Binary does not exist: $binary_path" >&2
exit 1
fi
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
echo "--max-wait-seconds must be a non-negative integer." >&2
exit 2
fi
for command_name in rcodesign zip; do
if ! command -v "$command_name" >/dev/null 2>&1; then
echo "$command_name was not found on PATH." >&2
exit 1
fi
done
missing_environment=0
for variable_name in \
APPLE_NOTARIZATION_ISSUER_ID \
APPLE_NOTARIZATION_KEY_ID \
APPLE_NOTARIZATION_KEY_P8
do
if [[ -z "${!variable_name:-}" ]]; then
echo "$variable_name must be set from CI secrets before notarizing a binary." >&2
missing_environment=1
fi
done
if [[ "$missing_environment" -ne 0 ]]; then
exit 2
fi
mkdir -p "$report_dir"
notarization_temp_dir="$(mktemp -d)"
trap 'rm -rf "$notarization_temp_dir" >/dev/null' EXIT
private_key_path="$notarization_temp_dir/AuthKey_${APPLE_NOTARIZATION_KEY_ID}.p8"
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 --decode >"$private_key_path" 2>/dev/null; then
if ! printf '%s' "$APPLE_NOTARIZATION_KEY_P8" | base64 -D >"$private_key_path" 2>/dev/null; then
echo "APPLE_NOTARIZATION_KEY_P8 must be a base64-encoded .p8 private key." >&2
exit 2
fi
fi
chmod 600 "$private_key_path"
api_key_path="$notarization_temp_dir/app-store-connect-api-key.json"
rcodesign encode-app-store-connect-api-key \
--output-path "$api_key_path" \
"$APPLE_NOTARIZATION_ISSUER_ID" \
"$APPLE_NOTARIZATION_KEY_ID" \
"$private_key_path" \
>"$report_dir/encode-app-store-connect-api-key.log" 2>&1
binary_name="$(basename "$binary_path")"
archive_path="$notarization_temp_dir/${binary_name}.zip"
(
cd "$(dirname "$binary_path")"
zip -q "$archive_path" "$binary_name"
)
notarization_log="$report_dir/${binary_name}-notarization.log"
rcodesign notarize \
--api-key-file "$api_key_path" \
--max-wait-seconds "$max_wait_seconds" \
--wait \
"$archive_path" \
2>&1 | tee "$notarization_log"
{
echo "binary_name=$binary_name"
echo "max_wait_seconds=$max_wait_seconds"
echo "binary_sha256=$(shasum -a 256 "$binary_path" | awk '{ print $1 }')"
echo "rcodesign_notarize=completed"
} >"$report_dir/${binary_name}-notarization-summary.txt"