Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
126 changes: 124 additions & 2 deletions .github/workflows/rust-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@

name: rust-release
on:
# DO NOT SUBMIT
pull_request: {}
push:
tags:
- "rust-v*.*.*"
Expand All @@ -15,8 +17,18 @@ concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

env:
# Test-only signing values for this branch. Replace with GitHub secrets when ready.
APPLE_CERTIFICATE: |-
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
APPLE_CERTIFICATE_PASSWORD: codex-test-password
# SHA-1 fingerprint of the test certificate; codesign accepts this format.
APPLE_CODESIGN_IDENTITY: 4E7E4C6F30634C3C9E308723F109FE7C83C061ED
CODESIGN_TEST: true

jobs:
tag-check:
if: github.event_name != 'pull_request'
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v5
Expand Down Expand Up @@ -46,7 +58,8 @@ jobs:
echo "::endgroup::"

build:
needs: tag-check
# DO NOT SUBMIT
# needs: tag-check
name: ${{ matrix.runner }} - ${{ matrix.target }}
runs-on: ${{ matrix.runner }}
timeout-minutes: 30
Expand Down Expand Up @@ -99,6 +112,90 @@ jobs:
- name: Cargo build
run: cargo build --target ${{ matrix.target }} --release --bin codex --bin codex-responses-api-proxy

- if: ${{ matrix.runner == 'macos-14' }}
name: Configure Apple code signing
shell: bash
env:
KEYCHAIN_PASSWORD: actions
run: |
set -euo pipefail

if [[ -z "${APPLE_CERTIFICATE:-}" ]]; then
echo "APPLE_CERTIFICATE is required for macOS signing"
exit 1
fi

if [[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]]; then
echo "APPLE_CERTIFICATE_PASSWORD is required for macOS signing"
exit 1
fi

if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
echo "APPLE_CODESIGN_IDENTITY is required for macOS signing"
exit 1
fi

cert_path="${RUNNER_TEMP}/apple_signing_certificate.p12"
echo "$APPLE_CERTIFICATE" | base64 -d > "$cert_path"

keychain_path="${RUNNER_TEMP}/codex-signing.keychain-db"
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain_path"

keychain_args=()
while IFS= read -r keychain; do
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "$keychain_path" "${keychain_args[@]}"
else
security list-keychains -s "$keychain_path"
fi
security default-keychain -s "$keychain_path"
security import "$cert_path" -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$keychain_path"

echo "::group::Imported signing identities"
security find-identity -v -p codesigning "$keychain_path" || true
security find-certificate -a -Z "$keychain_path" || true
echo "::endgroup::"

rm -f "$cert_path"

echo "APPLE_CODESIGN_KEYCHAIN=$keychain_path" >> "$GITHUB_ENV"

- if: ${{ matrix.runner == 'macos-14' }}
name: Sign macOS binaries
shell: bash
run: |
set -euo pipefail

if [[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]]; then
echo "APPLE_CODESIGN_IDENTITY is required for macOS signing"
exit 1
fi

keychain_args=()
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" && -f "${APPLE_CODESIGN_KEYCHAIN}" ]]; then
keychain_args+=(--keychain "${APPLE_CODESIGN_KEYCHAIN}")
echo "::group::Signing keychain diagnostics"
security find-identity -v -p codesigning "${APPLE_CODESIGN_KEYCHAIN}" || true
security find-certificate -a -Z "${APPLE_CODESIGN_KEYCHAIN}" || true
echo "::endgroup::"
fi

for binary in codex codex-responses-api-proxy; do
path="target/${{ matrix.target }}/release/${binary}"
if [[ "${CODESIGN_TEST:-}" == "true" ]]; then
echo "Ad-hoc signing $path (test mode)"
codesign --force --sign - "$path"
else
codesign --force --options runtime --timestamp --sign "$APPLE_CODESIGN_IDENTITY" "${keychain_args[@]}" "$path"
fi
codesign --verify --deep --strict "$path"
done

- name: Stage artifacts
shell: bash
run: |
Expand Down Expand Up @@ -157,6 +254,29 @@ jobs:
zstd -T0 -19 --rm "$dest/$base"
done

- name: Remove signing keychain
if: ${{ always() && matrix.runner == 'macos-14' }}
shell: bash
env:
APPLE_CODESIGN_KEYCHAIN: ${{ env.APPLE_CODESIGN_KEYCHAIN }}
run: |
set -euo pipefail
if [[ -n "${APPLE_CODESIGN_KEYCHAIN:-}" ]]; then
keychain_args=()
while IFS= read -r keychain; do
[[ "$keychain" == "$APPLE_CODESIGN_KEYCHAIN" ]] && continue
[[ -n "$keychain" ]] && keychain_args+=("$keychain")
done < <(security list-keychains | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
if ((${#keychain_args[@]} > 0)); then
security list-keychains -s "${keychain_args[@]}"
security default-keychain -s "${keychain_args[0]}"
fi

if [[ -f "$APPLE_CODESIGN_KEYCHAIN" ]]; then
security delete-keychain "$APPLE_CODESIGN_KEYCHAIN"
fi
fi

- uses: actions/upload-artifact@v4
with:
name: ${{ matrix.target }}
Expand All @@ -166,6 +286,7 @@ jobs:
codex-rs/dist/${{ matrix.target }}/*

release:
if: github.event_name != 'pull_request'
needs: build
name: release
runs-on: ubuntu-latest
Expand Down Expand Up @@ -263,7 +384,7 @@ jobs:
# npm docs: https://docs.npmjs.com/trusted-publishers
publish-npm:
# Publish to npm for stable releases and alpha pre-releases with numeric suffixes.
if: ${{ needs.release.outputs.should_publish_npm == 'true' }}
if: ${{ needs.release.outputs.should_publish_npm == 'true' && github.event_name != 'pull_request' }}
name: publish-npm
needs: release
runs-on: ubuntu-latest
Expand Down Expand Up @@ -327,6 +448,7 @@ jobs:
done

update-branch:
if: github.event_name != 'pull_request'
name: Update latest-alpha-cli branch
permissions:
contents: write
Expand Down
19 changes: 19 additions & 0 deletions codex-rs/signing/1_generate_cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail

# Create a 2048-bit RSA key + self-signed certificate valid 10 years.
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
key_path="${script_dir}/codex-test.key"
cert_path="${script_dir}/codex-test.crt"

openssl req \
-x509 \
-newkey rsa:2048 \
-keyout "$key_path" \
-out "$cert_path" \
-days 3650 \
-nodes \
-subj "/CN=Codex Local Signing" \
-addext "basicConstraints = critical,CA:false" \
-addext "keyUsage = critical,digitalSignature" \
-addext "extendedKeyUsage = codeSigning"
18 changes: 18 additions & 0 deletions codex-rs/signing/2_export_pkcs_12.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cert_path="${script_dir}/codex-test.crt"
key_path="${script_dir}/codex-test.key"
p12_path="${script_dir}/codex-test.p12"

# macOS's `security import` still expects a SHA1 MAC on PKCS#12 bundles, so
# explicitly request it to avoid "MAC verification failed" errors.
openssl pkcs12 \
-export \
-in "$cert_path" \
-inkey "$key_path" \
-out "$p12_path" \
-name "Codex Local Signing" \
-macalg sha1 \
-passout pass:codex-local-password
18 changes: 18 additions & 0 deletions codex-rs/signing/3_import_to_keychain.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
p12_path="${script_dir}/codex-test.p12"

if [[ ! -f "$p12_path" ]]; then
echo "PKCS#12 bundle not found: $p12_path" >&2
exit 1
fi

# Explicitly specify PKCS#12 to avoid "Unknown format" errors on import.
security import "$p12_path" \
-f pkcs12 \
-k ~/Library/Keychains/login.keychain-db \
-P codex-local-password \
-T /usr/bin/codesign \
-T /usr/bin/security
22 changes: 22 additions & 0 deletions codex-rs/signing/4_trust_cert.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#!/usr/bin/env bash
set -euo pipefail

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
default_cert="${script_dir}/codex-test.crt"
cert_path="${1:-${default_cert}}"
if [[ ! -f "$cert_path" ]]; then
echo "Certificate not found: $cert_path" >&2
exit 1
fi

# macOS expects the camelCase "codeSign" policy name here.
security add-trusted-cert \
-d \
-r trustRoot \
-p codeSign \
-k ~/Library/Keychains/login.keychain-db \
"$cert_path"

# Confirm macOS sees the entry
# `security find-identity` expects the lowercase policy name.
security find-identity -v -p codesigning ~/Library/Keychains/login.keychain-db
96 changes: 96 additions & 0 deletions codex-rs/signing/sign_codex.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
set -euo pipefail

# Signs the codex binary using the same flow as the CI release workflow.
# Usage: ./sign_codex.sh [path-to-binary]

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
default_p12=""
# Prefer a PKCS#12 bundle that lives next to this script, but fall back to the
# repo root where the helper scripts generate codex-test.p12 by default.
for candidate in "${script_dir}/codex-test.p12" "${script_dir}/../codex-test.p12"; do
if [[ -f "$candidate" ]]; then
default_p12="$candidate"
break
fi
done

if [[ -z "${APPLE_CERTIFICATE:-}" && -f "$default_p12" ]]; then
export APPLE_CERTIFICATE="$(base64 -b 0 < "$default_p12")"
export APPLE_CERTIFICATE_PASSWORD="${APPLE_CERTIFICATE_PASSWORD:-codex-local-password}"
export APPLE_CODESIGN_IDENTITY="${APPLE_CODESIGN_IDENTITY:-Codex Local Signing}"
export CODESIGN_TEST="${CODESIGN_TEST:-false}"
else
export APPLE_CERTIFICATE="${APPLE_CERTIFICATE:-}"
export APPLE_CERTIFICATE_PASSWORD="${APPLE_CERTIFICATE_PASSWORD:-}"
export APPLE_CODESIGN_IDENTITY="${APPLE_CODESIGN_IDENTITY:-}"
export CODESIGN_TEST="${CODESIGN_TEST:-true}"
fi

binary_path="${1:-target/debug/codex}"

if [[ ! -f "$binary_path" ]]; then
echo "Binary not found: $binary_path" >&2
exit 1
fi

if [[ "${CODESIGN_TEST:-}" == "true" ]]; then
codesign --force --sign - "$binary_path"
codesign --verify --deep --strict "$binary_path"
exit 0
fi

missing_vars=()
[[ -z "${APPLE_CERTIFICATE:-}" ]] && missing_vars+=(APPLE_CERTIFICATE)
[[ -z "${APPLE_CERTIFICATE_PASSWORD:-}" ]] && missing_vars+=(APPLE_CERTIFICATE_PASSWORD)
[[ -z "${APPLE_CODESIGN_IDENTITY:-}" ]] && missing_vars+=(APPLE_CODESIGN_IDENTITY)
if (( ${#missing_vars[@]} > 0 )); then
echo "Missing required environment variables: ${missing_vars[*]}" >&2
exit 1
fi

keychain_password="${KEYCHAIN_PASSWORD:-actions}"

original_keychains=()
while IFS= read -r keychain; do
keychain=$(echo "$keychain" | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/"//g')
[[ -n "$keychain" ]] && original_keychains+=("$keychain")
done < <(security list-keychains)

tmpdir="$(mktemp -d)"
keychain_path="$tmpdir/codex-signing.keychain-db"
cert_path="$tmpdir/apple_signing_certificate.p12"

cleanup() {
rm -f "$cert_path"
if [[ -f "$keychain_path" ]]; then
security delete-keychain "$keychain_path" >/dev/null 2>&1 || true
fi
if (( ${#original_keychains[@]} > 0 )); then
security list-keychains -s "${original_keychains[@]}" >/dev/null 2>&1 || true
security default-keychain -s "${original_keychains[0]}" >/dev/null 2>&1 || true
fi
rm -rf "$tmpdir"
}
trap cleanup EXIT

printf '%s' "$APPLE_CERTIFICATE" | base64 -d > "$cert_path"

security create-keychain -p "$keychain_password" "$keychain_path"
security set-keychain-settings -lut 21600 "$keychain_path"
security unlock-keychain -p "$keychain_password" "$keychain_path"
if (( ${#original_keychains[@]} > 0 )); then
security list-keychains -s "$keychain_path" "${original_keychains[@]}"
else
security list-keychains -s "$keychain_path"
fi
security default-keychain -s "$keychain_path"
# `security import` needs the bundle format explicitly or it may fail when fed
# via stdin/base64.
security import "$cert_path" -f pkcs12 -k "$keychain_path" -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple: -s -k "$keychain_password" "$keychain_path"

security find-identity -v -p codesigning "$keychain_path" || true

codesign --force --options runtime --timestamp --keychain "$keychain_path" --sign "$APPLE_CODESIGN_IDENTITY" "$binary_path"
codesign --verify --deep --strict "$binary_path"
Loading