Add files using upload-large-folder tool
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .github/actions/publish-dotslash/action.yml +52 -0
- .github/actions/publish-dotslash/requirements.txt +111 -0
- .github/actions/run-argument-comment-lint/action.yml +53 -0
- .github/actions/setup-akv-pkcs11-codesigning/action.yaml +349 -0
- .github/actions/setup-ci/action.yml +107 -0
- .github/actions/setup-msvc-env/action.yml +20 -0
- .github/actions/setup-msvc-env/setup-msvc-env.ps1 +257 -0
- .github/actions/setup-rusty-v8/action.yml +64 -0
- .github/actions/windows-code-sign/action.yml +73 -0
- .github/codex/labels/codex-triage.md +7 -0
- .github/scripts/macos-signing/codex-app-server.entitlements.plist +10 -0
- .github/scripts/macos-signing/codex-code-mode-host.entitlements.plist +10 -0
- .github/scripts/macos-signing/codex-responses-api-proxy.entitlements.plist +8 -0
- .github/scripts/macos-signing/codex-voice-host.entitlements.plist +8 -0
- .github/scripts/macos-signing/codex.entitlements.plist +10 -0
- .github/scripts/macos-signing/notarize_macos_binary_with_akv.sh +110 -0
- .github/scripts/macos-signing/notarize_macos_dmg_with_akv.sh +100 -0
- .github/scripts/macos-signing/notarize_with_akv.py +519 -0
- .github/scripts/macos-signing/provisioned_macos_cli_package.py +229 -0
- .github/scripts/macos-signing/sign_macos_cli_package.py +177 -0
- .github/scripts/macos-signing/sign_macos_code.sh +245 -0
- .github/scripts/macos-signing/test_notarize_with_akv.py +257 -0
- .github/scripts/macos-signing/test_provisioned_macos_cli_package.py +217 -0
- .github/scripts/macos-signing/test_sign_macos_cli_package.py +293 -0
- docs/CLA.md +49 -0
- docs/agents_md.md +3 -0
- docs/authentication.md +3 -0
- docs/config.md +15 -0
- docs/contributing.md +39 -0
- docs/example-config.md +3 -0
- docs/exec.md +3 -0
- docs/execpolicy.md +3 -0
- docs/getting-started.md +3 -0
- docs/install.md +65 -0
- docs/license.md +3 -0
- docs/open-source-fund.md +8 -0
- docs/sandbox.md +3 -0
- docs/skills.md +3 -0
- docs/slash_commands.md +3 -0
- patches/BUILD.bazel +32 -0
- patches/abseil_windows_gnullvm_thread_identity.patch +16 -0
- patches/bzip2_windows_stack_args.patch +23 -0
- patches/llvm_rusty_v8_custom_libcxx.patch +108 -0
- patches/llvm_windows_arm64_powl.patch +25 -0
- patches/llvm_windows_mingw_compat.patch +50 -0
- patches/ring_windows_msvc_include_dirs.patch +112 -0
- patches/rules_cc_rusty_v8_custom_libcxx.patch +44 -0
- patches/rules_foreign_cc_make_cppflags.patch +15 -0
- patches/rules_foreign_cc_make_msvc_stdint.patch +13 -0
- patches/rules_foreign_cc_make_xcompile.patch +10 -0
.github/actions/publish-dotslash/action.yml
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Publish DotSlash manifests
|
| 2 |
+
description: Run the pinned upstream publisher directly on an Ubuntu runner.
|
| 3 |
+
|
| 4 |
+
inputs:
|
| 5 |
+
tag:
|
| 6 |
+
description: Release tag to publish manifests for.
|
| 7 |
+
required: true
|
| 8 |
+
configs:
|
| 9 |
+
description: Newline-separated paths to DotSlash configuration files.
|
| 10 |
+
required: true
|
| 11 |
+
|
| 12 |
+
runs:
|
| 13 |
+
using: composite
|
| 14 |
+
steps:
|
| 15 |
+
- name: Checkout DotSlash publisher
|
| 16 |
+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
| 17 |
+
with:
|
| 18 |
+
repository: facebook/dotslash-publish-release
|
| 19 |
+
ref: 9c9ec027515c34db9282a09a25a9cab5880b2c52 # v2
|
| 20 |
+
path: .dotslash-publish-release
|
| 21 |
+
persist-credentials: false
|
| 22 |
+
|
| 23 |
+
- name: Install uv
|
| 24 |
+
uses: astral-sh/setup-uv@08807647e7069bb48b6ef5acd8ec9567f424441b # v8.1.0
|
| 25 |
+
with:
|
| 26 |
+
version: "0.12.13"
|
| 27 |
+
checksum: "745765a3b6e360ad76743599ae5c42e9278c7edf8bbff9fc76d05bf2623a04dd"
|
| 28 |
+
github-token: ""
|
| 29 |
+
enable-cache: false
|
| 30 |
+
|
| 31 |
+
- name: Publish DotSlash manifests
|
| 32 |
+
shell: bash
|
| 33 |
+
env:
|
| 34 |
+
ACTION_PATH: ${{ github.action_path }}
|
| 35 |
+
RELEASE_TAG: ${{ inputs.tag }}
|
| 36 |
+
DOTSLASH_CONFIGS: ${{ inputs.configs }}
|
| 37 |
+
run: |
|
| 38 |
+
set -euo pipefail
|
| 39 |
+
|
| 40 |
+
# Ubuntu runners already provide Python and gh. Avoid rebuilding the
|
| 41 |
+
# upstream Docker image and downloading its apt dependencies per release.
|
| 42 |
+
uv venv --python "$(command -v python3)" "${RUNNER_TEMP}/dotslash-publish-release-venv"
|
| 43 |
+
python="${RUNNER_TEMP}/dotslash-publish-release-venv/bin/python"
|
| 44 |
+
uv pip install --python "$python" \
|
| 45 |
+
--require-hashes --only-binary=:all: -r "${ACTION_PATH}/requirements.txt"
|
| 46 |
+
|
| 47 |
+
while IFS= read -r config; do
|
| 48 |
+
if [[ -n "$config" ]]; then
|
| 49 |
+
"$python" .dotslash-publish-release/process_config.py \
|
| 50 |
+
--config "$config" --tag "$RELEASE_TAG" --upload
|
| 51 |
+
fi
|
| 52 |
+
done <<< "$DOTSLASH_CONFIGS"
|
.github/actions/publish-dotslash/requirements.txt
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Regenerate: uv pip compile --generate-hashes --no-header --no-annotate requirements.txt
|
| 2 |
+
blake3==1.0.8 \
|
| 3 |
+
--hash=sha256:003f61e8c41dd9931edddf1cc6a1bb680fb2ac0ad15493ef4a1df9adc59ce9df \
|
| 4 |
+
--hash=sha256:0171e85d56dec1219abdae5f49a0ed12cb3f86a454c29160a64fd8a8166bba37 \
|
| 5 |
+
--hash=sha256:04b95bcd6c1b61480923adc571ffab377618d170f528f6cf4f6a6fff94e3c309 \
|
| 6 |
+
--hash=sha256:0c492630d384fc4400e9860b16e09e0dbe79e960e95b237b9a1ea3f95ea3125d \
|
| 7 |
+
--hash=sha256:0eab3318ec02f8e16fe549244791ace2ada2c259332f0c77ab22cf94dfff7130 \
|
| 8 |
+
--hash=sha256:119829e13eca177f5626c43f753acc4813eb8ae414c2d70ef7571078177163c7 \
|
| 9 |
+
--hash=sha256:1257be19f2d381c868a34cc822fc7f12f817ddc49681b6d1a2790bfbda1a9865 \
|
| 10 |
+
--hash=sha256:19fc6f2b7edab8acff6895fc6e38c19bd79f4c089e21153020c75dfc7397d52d \
|
| 11 |
+
--hash=sha256:1af0e5a29aa56d4fba904452ae784740997440afd477a15e583c38338e641f41 \
|
| 12 |
+
--hash=sha256:23e181318f5a62e4e6ce7be0875b4f65543abe47fe48cc4f4e9ab8f2bf9e01c5 \
|
| 13 |
+
--hash=sha256:269f255b110840e52b6ce9db02217e39660ebad3e34ddd5bca8b8d378a77e4e1 \
|
| 14 |
+
--hash=sha256:2bacf9757e93ac376c612dc0d45931a591ea0caf5583ab93d99dd32a2905ec43 \
|
| 15 |
+
--hash=sha256:2c33dac2c6112bc23f961a7ca305c7e34702c8177040eb98d0389d13a347b9e1 \
|
| 16 |
+
--hash=sha256:2d78f06f3fb838b34c330e2987090376145cbe5944d8608a0c4779c779618f7b \
|
| 17 |
+
--hash=sha256:3168c457255b5d2a2fc356ba696996fcaff5d38284f968210d54376312107662 \
|
| 18 |
+
--hash=sha256:38209b10482c97e151681ea3e91cc7141f56adbbf4820a7d701a923124b41e6a \
|
| 19 |
+
--hash=sha256:3b5573b052777142b2cecc453d022c3f21aa4aba75011258410bb98f41c1a727 \
|
| 20 |
+
--hash=sha256:3bafdec95dfffa3f6571e529644744e280337df15ddd9728f224ba70c5779b23 \
|
| 21 |
+
--hash=sha256:3cec94ed5676821cf371e9c9d25a41b4f3ebdb5724719b31b2749653b7cc1dfa \
|
| 22 |
+
--hash=sha256:3fb83532f7456ddeb68dae1b36e1f7c52f9cb72852ac01159bbcb1a12b0f8be0 \
|
| 23 |
+
--hash=sha256:411f61cb817eb25547b91944301ccfa624ccdc69d70822866d8ad0b0e9e3ca64 \
|
| 24 |
+
--hash=sha256:421b99cdf1ff2d1bf703bc56c454f4b286fce68454dd8711abbcb5a0df90c19a \
|
| 25 |
+
--hash=sha256:444215a1e5201f8fa4e5c7352e938a7070cd33d66aeb1dd9b1103a64b6920f9e \
|
| 26 |
+
--hash=sha256:44c2815d4616fad7e2d757d121c0a11780f70ffc817547b3059b5c7e224031a7 \
|
| 27 |
+
--hash=sha256:46dc20976bd6c235959ef0246ec73420d1063c3da2839a9c87ca395cf1fd7943 \
|
| 28 |
+
--hash=sha256:497ef8096ec4ac1ffba9a66152cee3992337cebf8ea434331d8fd9ce5423d227 \
|
| 29 |
+
--hash=sha256:4b0543c57eb9d6dac9d4bced63e9f7f7b546886ac04cec8da3c3d9c8f30cbbb7 \
|
| 30 |
+
--hash=sha256:4bacaae75e98dee3b7da6c5ee3b81ee21a3352dd2477d6f1d1dbfd38cdbf158a \
|
| 31 |
+
--hash=sha256:4e02a3c499e35bf51fc15b2738aca1a76410804c877bcd914752cac4f71f052a \
|
| 32 |
+
--hash=sha256:4f54cff7f15d91dc78a63a2dd02a3dccdc932946f271e2adb4130e0b4cf608ba \
|
| 33 |
+
--hash=sha256:504d1399b7fb91dfe5c25722d2807990493185faa1917456455480c36867adb5 \
|
| 34 |
+
--hash=sha256:506a47897a11ebe8f3cdeb52f1365d6a2f83959e98ccb0c830f8f73277d4d358 \
|
| 35 |
+
--hash=sha256:511133bab85ff60ed143424ce484d08c60894ff7323f685d7a6095f43f0c85c3 \
|
| 36 |
+
--hash=sha256:513cc7f0f5a7c035812604c2c852a0c1468311345573de647e310aca4ab165ba \
|
| 37 |
+
--hash=sha256:53304b6df3f8d826850c1d977d9a69edbf3e48476b95f7cb6175ec650856a3c5 \
|
| 38 |
+
--hash=sha256:58ce8d45a5bb5326482de72ea1969a378634236186a970fef63058a5b7b8b435 \
|
| 39 |
+
--hash=sha256:5eb25bca3cee2e0dd746a214784fb36be6a43640c01c55b6b4e26196e72d076c \
|
| 40 |
+
--hash=sha256:63379be58438878eeb76ebe4f0efbeaabf42b79f2cff23b6126b7991588ced67 \
|
| 41 |
+
--hash=sha256:66ca28a673025c40db3eba21a9cac52f559f83637efa675b3f6bd8683f0415f3 \
|
| 42 |
+
--hash=sha256:67d9c42c42eb1c7aedcf901591c743266009fcf48babf6d6f8450f567cb94a84 \
|
| 43 |
+
--hash=sha256:67f1bc11bf59464ef092488c707b13dd4e872db36e25c453dfb6e0c7498df9f1 \
|
| 44 |
+
--hash=sha256:6a6eafc29e4f478d365a87d2f25782a521870c8514bb43734ac85ae9be71caf7 \
|
| 45 |
+
--hash=sha256:6ae7754c7d96e92a70a52e07c732d594cf9924d780f49fffd3a1e9235e0f5ba7 \
|
| 46 |
+
--hash=sha256:725c52c4d393c7bd1a10682df322d480734002a1389b320366c660568708846b \
|
| 47 |
+
--hash=sha256:73e0b4fa25f6e3078526a592fb38fca85ef204fd02eced6731e1cdd9396552d4 \
|
| 48 |
+
--hash=sha256:78731ce7fca46f776ae45fb5271a2a76c4a92c9687dd4337e84b2ae9a174b28f \
|
| 49 |
+
--hash=sha256:7adbbee5dd0c302218eb8acdfd82b7006930eb5798f56f79f9cca89f6f192662 \
|
| 50 |
+
--hash=sha256:7fcfe81b3ae3fb5d2e88be0d3259603ff95f0d5ed69f655c28fdaef31e49a470 \
|
| 51 |
+
--hash=sha256:83347ef28d774e84442c71fc5aa8817498753d47831a533536ead1a7ccd44a41 \
|
| 52 |
+
--hash=sha256:83605dbf43f581d8b7175b7f3bfe5388bad5a7c6ac175c9c11d669da31133f4b \
|
| 53 |
+
--hash=sha256:859cd57bac097a2cd63cb36d64c2f6f16c9edece5590f929e70157478e46dc9e \
|
| 54 |
+
--hash=sha256:88d527c247f9609dc1d45a08fd243e39f0d5300d54c57e048de24d4fa9240ebb \
|
| 55 |
+
--hash=sha256:8956bb9aec47b6c37ccce935a943588f1f5e6e2e85d43bb7cb76a574238f8a9b \
|
| 56 |
+
--hash=sha256:8f2ef8527a7a8afd99b16997d015851ccc0fe2a409082cebb980af2554e5c74c \
|
| 57 |
+
--hash=sha256:9456c829601d72852d8ba0af8dae0610f7def1d59f5942efde1e2ef93e8a8b57 \
|
| 58 |
+
--hash=sha256:94b2d4922c75174083988b8b0d2c78e72be7376ac59ab19c3e5d17a7c9c1cc0f \
|
| 59 |
+
--hash=sha256:97c076d58ee37eb5b2d8d91bb9db59c5a008fd59c71845dc57fe438aeeabaf10 \
|
| 60 |
+
--hash=sha256:98915260c378d4c45088f1e227b053834ed95aeca89336184f192489ad0dd5d4 \
|
| 61 |
+
--hash=sha256:9a8946cb6b1d2b2096daaaa89856f39887bce2b78503fa31b78173e3a86fa281 \
|
| 62 |
+
--hash=sha256:9ab5998e2abd9754819753bc2f1cf3edf82d95402bff46aeef45ed392a5468bf \
|
| 63 |
+
--hash=sha256:9c9fbdacfdeb68f7ca53bb5a7a5a593ec996eaf21155ad5b08d35e6f97e60877 \
|
| 64 |
+
--hash=sha256:9e1d70bf76c02846d0868a3d413eb6c430b76a315e12f1b2e59b5cf56c1f62a3 \
|
| 65 |
+
--hash=sha256:a2336d5b2a801a7256da21150348f41610a6c21dae885a3acb1ebbd7333d88d8 \
|
| 66 |
+
--hash=sha256:a25db3d36b55f5ed6a86470155cc749fc9c5b91c949b8d14f48658f9d960d9ec \
|
| 67 |
+
--hash=sha256:a33b9a1fb6d1d559a8e0d04b041e99419a6bb771311c774f6ff57ed7119c70ed \
|
| 68 |
+
--hash=sha256:a47939f04b89c5c6ff1e51e883e5efab1ea1bf01a02f4d208d216dddd63d0dd8 \
|
| 69 |
+
--hash=sha256:a585357d5d8774aad9ffc12435de457f9e35cde55e0dc8bc43ab590a6929e59f \
|
| 70 |
+
--hash=sha256:a5b9196a4768e5475475bdf2010dc76b2792f95805731a645196d180a7e12570 \
|
| 71 |
+
--hash=sha256:ab4e1dea4fa857944944db78e8f20d99ee2e16b2dea5a14f514fb0607753ac83 \
|
| 72 |
+
--hash=sha256:adccc3a139207e02bb7d7bb0715fe0b87069685aad5f3afff820b2f829467904 \
|
| 73 |
+
--hash=sha256:add39ffa9e5074264f12ab409cdf059bc967817644b455e3fad46139001cc108 \
|
| 74 |
+
--hash=sha256:af18817da1eb5bd65d068fb9c18235edc328a45b09025346fa65fe52b1b0c9b6 \
|
| 75 |
+
--hash=sha256:af394b50c6aa0b1b957a99453d1ee440ef67cd2d1b5669c731647dc723de8a3a \
|
| 76 |
+
--hash=sha256:b201961c6dcc7123e69fff6019298d9e4394c7e585a300e999664bca8070d83c \
|
| 77 |
+
--hash=sha256:b37f63c5952b32b5064096141b394ed17569d318637a06a2cae9bfedd852f0ef \
|
| 78 |
+
--hash=sha256:b4d672c24dc15ec617d212a338a4ca14b449829b6072d09c96c63b6e6b621aed \
|
| 79 |
+
--hash=sha256:bcb04966537777af56c1f399b35525aa70a1225816e121ff95071c33c0f7abca \
|
| 80 |
+
--hash=sha256:c2682ba10fb3b36fe8a6823be50910f1145a8a5158bc555145fadcc4afb984c3 \
|
| 81 |
+
--hash=sha256:c445eff665d21c3b3b44f864f849a2225b1164c08654beb23224a02f087b7ff1 \
|
| 82 |
+
--hash=sha256:c5bfe1a1ada3ed2c715f692ce1a3b221d7f6b4d5e6001d24774ef07de013192c \
|
| 83 |
+
--hash=sha256:c63ece266a43014cf29e772a82857cd8e90315ae3ed53e3c5204851596edd5f2 \
|
| 84 |
+
--hash=sha256:c65e373c8b47174b969ee61a89ee56922f722972eb650192845c8546df8d9db9 \
|
| 85 |
+
--hash=sha256:c7780666dc6be809b49442d6d5ce06fdbe33024a87560b58471103ec17644682 \
|
| 86 |
+
--hash=sha256:c84af132aa09abeadf9a0118c8fb26f4528f3f42c10ef8be0fcf31c478774ec4 \
|
| 87 |
+
--hash=sha256:c9ab5ef61aaa6df2f769548a071fddcacbb0f56102a9c48f40878b10ae1a2657 \
|
| 88 |
+
--hash=sha256:ca8bc7e816370118262da11783e1fdf8992f250789ae08e8182fb7c26e14ded0 \
|
| 89 |
+
--hash=sha256:d17eb6382634b3a5bc0c0e0454d5265b0becaeeadb6801ed25150b39a999d0cc \
|
| 90 |
+
--hash=sha256:d2c3151955efb09ba58cd3e1263521e15e9e3866a40d6bd3556d86fc968e8f95 \
|
| 91 |
+
--hash=sha256:d3fe26f145fcb82931d1820b55c0279f72f8f8e49450dd9d74efbfd409b28423 \
|
| 92 |
+
--hash=sha256:d8da4233984d51471bd4e4366feda1d90d781e712e0a504ea54b1f2b3577557b \
|
| 93 |
+
--hash=sha256:db54946792d2b8c6fa4be73e6e334519f13c1b52e7ff346b3e2ec8ad3eb59401 \
|
| 94 |
+
--hash=sha256:dd03ff08d1b6e4fdda1cd03826f971ae8966ef6f683a8c68aa27fb21904b5aa9 \
|
| 95 |
+
--hash=sha256:ddfc59b0176fb31168f08d5dd536e69b1f4f13b5a0f4b0c3be1003efd47f9308 \
|
| 96 |
+
--hash=sha256:e0fee93d5adcd44378b008c147e84f181f23715307a64f7b3db432394bbfce8b \
|
| 97 |
+
--hash=sha256:e171b169cb7ea618e362a4dddb7a4d4c173bbc08b9ba41ea3086dd1265530d4f \
|
| 98 |
+
--hash=sha256:e2df12f295f95a804338bd300e8fad4a6f54fd49bd4d9c5893855a230b5188a8 \
|
| 99 |
+
--hash=sha256:e4072196547484c95a5a09adbb952e9bb501949f03f9e2a85e7249ef85faaba8 \
|
| 100 |
+
--hash=sha256:e5122a61b3b004bbbd979bdf83a3aaab432da3e2a842d7ddf1c273f2503b4884 \
|
| 101 |
+
--hash=sha256:e5b5da177d62cc4b7edf0cea08fe4dec960c9ac27f916131efa890a01f747b93 \
|
| 102 |
+
--hash=sha256:e6cb2316949c614e64f6b7796b9eca28b1b57406d27debc99398a746e9b426c7 \
|
| 103 |
+
--hash=sha256:e7e12a777f6b798eb8d06f875d6e108e3008bd658d274d8c676dcf98e0f10537 \
|
| 104 |
+
--hash=sha256:e8ae3689f0c7bfa6ce6ae45cab110e4c3442125c4c23b28f1f097856de26e4d1 \
|
| 105 |
+
--hash=sha256:ed972ebd553c0c25363459e9fc71a38c045d8419e365b59acd8cd791eff13981 \
|
| 106 |
+
--hash=sha256:ef153c5860d5bf1cc71aece69b28097d2a392913eb323d6b52555c875d0439fc \
|
| 107 |
+
--hash=sha256:efb1ab89fa47b5acb36f1530c4eb83ae20aaec9ac17808ff014b2650fbc3c7ba \
|
| 108 |
+
--hash=sha256:f13c25c215c284adca0c07cffae1518cafcac3900f1d45fff51c7e6ab98efec1 \
|
| 109 |
+
--hash=sha256:fc9fc4a0a1a13991b23a589fb6fbb8290bd94a72c130e69e27c9769612516424 \
|
| 110 |
+
--hash=sha256:fe1b02ab49bfd969ef50b9f17482a2011c77536654af21807ba5c2674e0bb2a0 \
|
| 111 |
+
--hash=sha256:fe61d52da1d102aabb0e315d3fc613c11c103376aa0a8510aeee159939612f76
|
.github/actions/run-argument-comment-lint/action.yml
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Run argument comment lint
|
| 2 |
+
description: Run argument-comment-lint on codex-rs via Bazel.
|
| 3 |
+
|
| 4 |
+
inputs:
|
| 5 |
+
target:
|
| 6 |
+
description: Runner target passed to setup-bazel-ci.
|
| 7 |
+
required: true
|
| 8 |
+
buildbuddy-api-key:
|
| 9 |
+
description: BuildBuddy API key used by Bazel CI.
|
| 10 |
+
required: false
|
| 11 |
+
default: ""
|
| 12 |
+
|
| 13 |
+
runs:
|
| 14 |
+
using: composite
|
| 15 |
+
steps:
|
| 16 |
+
- uses: ./.github/actions/setup-bazel-ci
|
| 17 |
+
with:
|
| 18 |
+
target: ${{ inputs.target }}
|
| 19 |
+
|
| 20 |
+
- name: Install Linux sandbox build dependencies
|
| 21 |
+
if: ${{ runner.os == 'Linux' }}
|
| 22 |
+
shell: bash
|
| 23 |
+
run: |
|
| 24 |
+
sudo DEBIAN_FRONTEND=noninteractive apt-get update
|
| 25 |
+
sudo DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends pkg-config libcap-dev
|
| 26 |
+
|
| 27 |
+
- name: Run argument comment lint on codex-rs via Bazel
|
| 28 |
+
if: ${{ runner.os != 'Windows' }}
|
| 29 |
+
env:
|
| 30 |
+
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }}
|
| 31 |
+
shell: bash
|
| 32 |
+
run: |
|
| 33 |
+
bazel_targets="$(./tools/argument-comment-lint/list-bazel-targets.sh)"
|
| 34 |
+
./.github/scripts/run-bazel-ci.sh \
|
| 35 |
+
-- \
|
| 36 |
+
build \
|
| 37 |
+
--config=argument-comment-lint \
|
| 38 |
+
--keep_going \
|
| 39 |
+
--build_metadata=COMMIT_SHA=${GITHUB_SHA} \
|
| 40 |
+
-- \
|
| 41 |
+
${bazel_targets}
|
| 42 |
+
|
| 43 |
+
- name: Run argument comment lint on codex-rs via Bazel
|
| 44 |
+
if: ${{ runner.os == 'Windows' }}
|
| 45 |
+
env:
|
| 46 |
+
BUILDBUDDY_API_KEY: ${{ inputs.buildbuddy-api-key }}
|
| 47 |
+
shell: bash
|
| 48 |
+
run: |
|
| 49 |
+
./.github/scripts/run-argument-comment-lint-bazel.sh \
|
| 50 |
+
--config=argument-comment-lint \
|
| 51 |
+
--platforms=//:local_windows \
|
| 52 |
+
--keep_going \
|
| 53 |
+
--build_metadata=COMMIT_SHA=${GITHUB_SHA}
|
.github/actions/setup-akv-pkcs11-codesigning/action.yaml
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: Set up AKV PKCS11 code signing
|
| 2 |
+
description: Download prebuilt rcodesign and Azure Key Vault PKCS11 provider artifacts, then export macOS signing environment.
|
| 3 |
+
|
| 4 |
+
inputs:
|
| 5 |
+
setup-mode:
|
| 6 |
+
description: signing configures Azure and exports signing env vars; tools-only only downloads signing tools.
|
| 7 |
+
required: false
|
| 8 |
+
default: signing
|
| 9 |
+
rcodesign-blob-uri:
|
| 10 |
+
description: Azure Blob URI for the prebuilt Linux/amd64 rcodesign binary.
|
| 11 |
+
required: true
|
| 12 |
+
rcodesign-sha256:
|
| 13 |
+
description: Expected SHA-256 digest for the prebuilt rcodesign binary.
|
| 14 |
+
required: true
|
| 15 |
+
akv-pkcs11-library-blob-uri:
|
| 16 |
+
description: Azure Blob URI for the prebuilt Linux/amd64 AKV PKCS11 provider library.
|
| 17 |
+
required: true
|
| 18 |
+
akv-pkcs11-library-sha256:
|
| 19 |
+
description: Expected SHA-256 digest for the prebuilt AKV PKCS11 provider library.
|
| 20 |
+
required: true
|
| 21 |
+
azure-client-id:
|
| 22 |
+
description: GitHub OIDC client ID for the Azure signer application.
|
| 23 |
+
required: true
|
| 24 |
+
azure-tenant-id:
|
| 25 |
+
description: Azure tenant ID for the signer application.
|
| 26 |
+
required: true
|
| 27 |
+
azure-subscription-id:
|
| 28 |
+
description: Azure subscription ID that owns the signing vault.
|
| 29 |
+
required: true
|
| 30 |
+
key-vault-name:
|
| 31 |
+
description: Azure Key Vault name containing the certificate-backed signing key.
|
| 32 |
+
required: true
|
| 33 |
+
key-name:
|
| 34 |
+
description: Key Vault certificate/key name used as the PKCS11 key label.
|
| 35 |
+
required: true
|
| 36 |
+
key-version:
|
| 37 |
+
description: Optional Key Vault key version to pin while signing.
|
| 38 |
+
required: false
|
| 39 |
+
default: ""
|
| 40 |
+
certificate-sha256:
|
| 41 |
+
description: Optional expected SHA-256 fingerprint for the downloaded public certificate.
|
| 42 |
+
required: false
|
| 43 |
+
default: ""
|
| 44 |
+
|
| 45 |
+
outputs:
|
| 46 |
+
pkcs11-library:
|
| 47 |
+
description: Path to the downloaded AKV PKCS11 provider library.
|
| 48 |
+
value: ${{ steps.paths.outputs.pkcs11_library }}
|
| 49 |
+
signing-certificate-pem:
|
| 50 |
+
description: Path to the downloaded public signing certificate.
|
| 51 |
+
value: ${{ steps.paths.outputs.signing_certificate_pem }}
|
| 52 |
+
rcodesign:
|
| 53 |
+
description: Path to the downloaded rcodesign binary.
|
| 54 |
+
value: ${{ steps.paths.outputs.rcodesign }}
|
| 55 |
+
|
| 56 |
+
runs:
|
| 57 |
+
using: composite
|
| 58 |
+
steps:
|
| 59 |
+
- name: Validate pinned signing artifacts
|
| 60 |
+
shell: bash
|
| 61 |
+
env:
|
| 62 |
+
SETUP_MODE: ${{ inputs.setup-mode }}
|
| 63 |
+
RCODESIGN_BLOB_URI: ${{ inputs.rcodesign-blob-uri }}
|
| 64 |
+
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
|
| 65 |
+
AKV_PKCS11_LIBRARY_BLOB_URI: ${{ inputs.akv-pkcs11-library-blob-uri }}
|
| 66 |
+
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
|
| 67 |
+
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
|
| 68 |
+
KEY_NAME: ${{ inputs.key-name }}
|
| 69 |
+
run: |
|
| 70 |
+
set -euo pipefail
|
| 71 |
+
|
| 72 |
+
case "$SETUP_MODE" in
|
| 73 |
+
signing|tools-only)
|
| 74 |
+
;;
|
| 75 |
+
*)
|
| 76 |
+
echo "setup-mode must be 'signing' or 'tools-only', got '$SETUP_MODE'." >&2
|
| 77 |
+
exit 1
|
| 78 |
+
;;
|
| 79 |
+
esac
|
| 80 |
+
|
| 81 |
+
for variable_name in RCODESIGN_SHA256 AKV_PKCS11_LIBRARY_SHA256; do
|
| 82 |
+
value="${!variable_name}"
|
| 83 |
+
if [[ ! "$value" =~ ^[0-9a-f]{64}$ ]]; then
|
| 84 |
+
echo "$variable_name must be a lowercase SHA-256 digest." >&2
|
| 85 |
+
exit 1
|
| 86 |
+
fi
|
| 87 |
+
done
|
| 88 |
+
|
| 89 |
+
for variable_name in RCODESIGN_BLOB_URI AKV_PKCS11_LIBRARY_BLOB_URI; do
|
| 90 |
+
value="${!variable_name}"
|
| 91 |
+
if [[ ! "$value" =~ ^az://[^/]+/[^/]+/.+ ]]; then
|
| 92 |
+
echo "$variable_name must use az://<account>/<container>/<blob>." >&2
|
| 93 |
+
exit 1
|
| 94 |
+
fi
|
| 95 |
+
done
|
| 96 |
+
|
| 97 |
+
if [[ "$SETUP_MODE" == "signing" ]]; then
|
| 98 |
+
for variable_name in \
|
| 99 |
+
KEY_VAULT_NAME \
|
| 100 |
+
KEY_NAME; do
|
| 101 |
+
if [[ -z "${!variable_name}" ]]; then
|
| 102 |
+
echo "$variable_name is required for AKV PKCS11 signing." >&2
|
| 103 |
+
exit 1
|
| 104 |
+
fi
|
| 105 |
+
done
|
| 106 |
+
fi
|
| 107 |
+
|
| 108 |
+
- name: Resolve signing tool paths
|
| 109 |
+
id: paths
|
| 110 |
+
shell: bash
|
| 111 |
+
run: |
|
| 112 |
+
set -euo pipefail
|
| 113 |
+
|
| 114 |
+
if [[ "${RUNNER_OS}" != "Linux" ]]; then
|
| 115 |
+
echo "Prebuilt AKV PKCS11 signing tools are only vendored for Linux runners, got ${RUNNER_OS}." >&2
|
| 116 |
+
exit 1
|
| 117 |
+
fi
|
| 118 |
+
|
| 119 |
+
if [[ "${RUNNER_ARCH}" != "X64" && "${RUNNER_ARCH}" != "AMD64" ]]; then
|
| 120 |
+
echo "Prebuilt AKV PKCS11 signing tools are only vendored for amd64 runners, got ${RUNNER_ARCH}." >&2
|
| 121 |
+
exit 1
|
| 122 |
+
fi
|
| 123 |
+
|
| 124 |
+
provider_root="${RUNNER_TEMP}/akv-pkcs11-provider"
|
| 125 |
+
rcodesign_root="${RUNNER_TEMP}/rcodesign-root"
|
| 126 |
+
signing_certificate_pem="${RUNNER_TEMP}/akv-signing-cert.pem"
|
| 127 |
+
library_name="libakv_pkcs_11.so"
|
| 128 |
+
|
| 129 |
+
mkdir -p "$provider_root" "$rcodesign_root/bin"
|
| 130 |
+
|
| 131 |
+
{
|
| 132 |
+
echo "pkcs11_library=$provider_root/$library_name"
|
| 133 |
+
echo "pkcs11_manifest=$provider_root/akv-pkcs11-provider.manifest"
|
| 134 |
+
echo "rcodesign_root=$rcodesign_root"
|
| 135 |
+
echo "rcodesign=$rcodesign_root/bin/rcodesign"
|
| 136 |
+
echo "signing_certificate_pem=$signing_certificate_pem"
|
| 137 |
+
} >> "$GITHUB_OUTPUT"
|
| 138 |
+
|
| 139 |
+
- name: Validate Azure credentials for private signing artifacts
|
| 140 |
+
shell: bash
|
| 141 |
+
env:
|
| 142 |
+
AZURE_CLIENT_ID: ${{ inputs.azure-client-id }}
|
| 143 |
+
AZURE_TENANT_ID: ${{ inputs.azure-tenant-id }}
|
| 144 |
+
AZURE_SUBSCRIPTION_ID: ${{ inputs.azure-subscription-id }}
|
| 145 |
+
run: |
|
| 146 |
+
set -euo pipefail
|
| 147 |
+
|
| 148 |
+
for variable_name in AZURE_CLIENT_ID AZURE_TENANT_ID AZURE_SUBSCRIPTION_ID; do
|
| 149 |
+
if [[ -z "${!variable_name}" ]]; then
|
| 150 |
+
echo "$variable_name is required for private AKV PKCS11 signing artifacts." >&2
|
| 151 |
+
exit 1
|
| 152 |
+
fi
|
| 153 |
+
done
|
| 154 |
+
|
| 155 |
+
- name: Log in to Azure with GitHub OIDC
|
| 156 |
+
uses: azure/login@532459ea530d8321f2fb9bb10d1e0bcf23869a43 # v3.0.0
|
| 157 |
+
with:
|
| 158 |
+
client-id: ${{ inputs.azure-client-id }}
|
| 159 |
+
tenant-id: ${{ inputs.azure-tenant-id }}
|
| 160 |
+
subscription-id: ${{ inputs.azure-subscription-id }}
|
| 161 |
+
|
| 162 |
+
- name: Install prebuilt signing tools
|
| 163 |
+
shell: bash
|
| 164 |
+
env:
|
| 165 |
+
RCODESIGN_BLOB_URI: ${{ inputs.rcodesign-blob-uri }}
|
| 166 |
+
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
|
| 167 |
+
RCODESIGN: ${{ steps.paths.outputs.rcodesign }}
|
| 168 |
+
AKV_PKCS11_LIBRARY_BLOB_URI: ${{ inputs.akv-pkcs11-library-blob-uri }}
|
| 169 |
+
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
|
| 170 |
+
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
|
| 171 |
+
PKCS11_MANIFEST: ${{ steps.paths.outputs.pkcs11_manifest }}
|
| 172 |
+
run: |
|
| 173 |
+
set -euo pipefail
|
| 174 |
+
|
| 175 |
+
download_az_blob_uri() {
|
| 176 |
+
local uri="$1"
|
| 177 |
+
local destination="$2"
|
| 178 |
+
local rest account container blob
|
| 179 |
+
|
| 180 |
+
rest="${uri#az://}"
|
| 181 |
+
account="${rest%%/*}"
|
| 182 |
+
rest="${rest#*/}"
|
| 183 |
+
container="${rest%%/*}"
|
| 184 |
+
blob="${rest#*/}"
|
| 185 |
+
|
| 186 |
+
if [[ -z "$account" || -z "$container" || -z "$blob" || "$blob" == "$rest" ]]; then
|
| 187 |
+
echo "Invalid Azure Blob URI. Expected az://<account>/<container>/<blob>." >&2
|
| 188 |
+
exit 1
|
| 189 |
+
fi
|
| 190 |
+
|
| 191 |
+
mkdir -p "$(dirname "$destination")"
|
| 192 |
+
rm -f "$destination"
|
| 193 |
+
if ! az storage blob download \
|
| 194 |
+
--account-name "$account" \
|
| 195 |
+
--container-name "$container" \
|
| 196 |
+
--name "$blob" \
|
| 197 |
+
--file "$destination" \
|
| 198 |
+
--auth-mode login \
|
| 199 |
+
--only-show-errors \
|
| 200 |
+
>/dev/null 2>&1; then
|
| 201 |
+
echo "Failed to download a private signing artifact from Azure Blob Storage." >&2
|
| 202 |
+
exit 1
|
| 203 |
+
fi
|
| 204 |
+
}
|
| 205 |
+
|
| 206 |
+
verify_sha256() {
|
| 207 |
+
local path="$1"
|
| 208 |
+
local expected="$2"
|
| 209 |
+
local actual
|
| 210 |
+
|
| 211 |
+
actual="$(shasum -a 256 "$path" | awk '{ print $1 }')"
|
| 212 |
+
if [[ "$actual" != "$expected" ]]; then
|
| 213 |
+
echo "SHA-256 verification failed for '$path'." >&2
|
| 214 |
+
exit 1
|
| 215 |
+
fi
|
| 216 |
+
}
|
| 217 |
+
|
| 218 |
+
echo "Downloading prebuilt rcodesign."
|
| 219 |
+
download_az_blob_uri "$RCODESIGN_BLOB_URI" "$RCODESIGN"
|
| 220 |
+
verify_sha256 "$RCODESIGN" "$RCODESIGN_SHA256"
|
| 221 |
+
chmod 0755 "$RCODESIGN"
|
| 222 |
+
|
| 223 |
+
echo "Downloading prebuilt AKV PKCS11 provider."
|
| 224 |
+
download_az_blob_uri "$AKV_PKCS11_LIBRARY_BLOB_URI" "$PKCS11_LIBRARY"
|
| 225 |
+
verify_sha256 "$PKCS11_LIBRARY" "$AKV_PKCS11_LIBRARY_SHA256"
|
| 226 |
+
chmod 0644 "$PKCS11_LIBRARY"
|
| 227 |
+
|
| 228 |
+
{
|
| 229 |
+
echo "runner_os=$RUNNER_OS"
|
| 230 |
+
echo "runner_arch=$RUNNER_ARCH"
|
| 231 |
+
echo "library_name=$(basename "$PKCS11_LIBRARY")"
|
| 232 |
+
} > "$PKCS11_MANIFEST"
|
| 233 |
+
|
| 234 |
+
- name: Verify downloaded signing tools
|
| 235 |
+
shell: bash
|
| 236 |
+
env:
|
| 237 |
+
RCODESIGN: ${{ steps.paths.outputs.rcodesign }}
|
| 238 |
+
RCODESIGN_SHA256: ${{ inputs.rcodesign-sha256 }}
|
| 239 |
+
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
|
| 240 |
+
AKV_PKCS11_LIBRARY_SHA256: ${{ inputs.akv-pkcs11-library-sha256 }}
|
| 241 |
+
PKCS11_MANIFEST: ${{ steps.paths.outputs.pkcs11_manifest }}
|
| 242 |
+
run: |
|
| 243 |
+
set -euo pipefail
|
| 244 |
+
|
| 245 |
+
verify_sha256() {
|
| 246 |
+
local path="$1"
|
| 247 |
+
local expected="$2"
|
| 248 |
+
local actual
|
| 249 |
+
|
| 250 |
+
actual="$(shasum -a 256 "$path" | awk '{ print $1 }')"
|
| 251 |
+
if [[ "$actual" != "$expected" ]]; then
|
| 252 |
+
echo "SHA-256 verification failed for '$path'." >&2
|
| 253 |
+
exit 1
|
| 254 |
+
fi
|
| 255 |
+
}
|
| 256 |
+
|
| 257 |
+
if [[ ! -x "$RCODESIGN" ]]; then
|
| 258 |
+
echo "rcodesign is missing or not executable at '$RCODESIGN'." >&2
|
| 259 |
+
exit 1
|
| 260 |
+
fi
|
| 261 |
+
|
| 262 |
+
if [[ ! -f "$PKCS11_LIBRARY" ]]; then
|
| 263 |
+
echo "AKV PKCS11 provider library is missing at '$PKCS11_LIBRARY'." >&2
|
| 264 |
+
exit 1
|
| 265 |
+
fi
|
| 266 |
+
|
| 267 |
+
verify_sha256 "$RCODESIGN" "$RCODESIGN_SHA256"
|
| 268 |
+
verify_sha256 "$PKCS11_LIBRARY" "$AKV_PKCS11_LIBRARY_SHA256"
|
| 269 |
+
|
| 270 |
+
"$RCODESIGN" --version
|
| 271 |
+
"$RCODESIGN" notarize --help > /dev/null
|
| 272 |
+
|
| 273 |
+
if [[ -f "$PKCS11_MANIFEST" ]]; then
|
| 274 |
+
echo "AKV PKCS11 provider artifact manifest is present."
|
| 275 |
+
else
|
| 276 |
+
echo "AKV PKCS11 provider artifact manifest is absent." >&2
|
| 277 |
+
exit 1
|
| 278 |
+
fi
|
| 279 |
+
|
| 280 |
+
- name: Download signing certificate from Key Vault
|
| 281 |
+
if: ${{ inputs.setup-mode == 'signing' }}
|
| 282 |
+
shell: bash
|
| 283 |
+
env:
|
| 284 |
+
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
|
| 285 |
+
KEY_NAME: ${{ inputs.key-name }}
|
| 286 |
+
KEY_VERSION: ${{ inputs.key-version }}
|
| 287 |
+
CERTIFICATE_SHA256: ${{ inputs.certificate-sha256 }}
|
| 288 |
+
SIGNING_CERTIFICATE_PEM: ${{ steps.paths.outputs.signing_certificate_pem }}
|
| 289 |
+
run: |
|
| 290 |
+
set -euo pipefail
|
| 291 |
+
|
| 292 |
+
certificate_version_args=()
|
| 293 |
+
if [[ -n "$KEY_VERSION" ]]; then
|
| 294 |
+
certificate_version_args+=(--version "$KEY_VERSION")
|
| 295 |
+
fi
|
| 296 |
+
|
| 297 |
+
if ! az keyvault certificate download \
|
| 298 |
+
--vault-name "$KEY_VAULT_NAME" \
|
| 299 |
+
--name "$KEY_NAME" \
|
| 300 |
+
"${certificate_version_args[@]}" \
|
| 301 |
+
--file "$SIGNING_CERTIFICATE_PEM" \
|
| 302 |
+
--encoding PEM \
|
| 303 |
+
--only-show-errors \
|
| 304 |
+
>/dev/null 2>&1; then
|
| 305 |
+
echo "Failed to download the public signing certificate from Azure Key Vault." >&2
|
| 306 |
+
exit 1
|
| 307 |
+
fi
|
| 308 |
+
|
| 309 |
+
if [[ -n "$CERTIFICATE_SHA256" ]]; then
|
| 310 |
+
actual_sha256="$(
|
| 311 |
+
openssl x509 -in "$SIGNING_CERTIFICATE_PEM" -noout -fingerprint -sha256 |
|
| 312 |
+
awk -F= '{ print toupper($2) }' |
|
| 313 |
+
tr -d ':\r\n'
|
| 314 |
+
)"
|
| 315 |
+
expected_sha256="$(printf '%s' "$CERTIFICATE_SHA256" | tr '[:lower:]' '[:upper:]' | tr -d ':\r\n ')"
|
| 316 |
+
if [[ "$actual_sha256" != "$expected_sha256" ]]; then
|
| 317 |
+
echo "Downloaded signing certificate SHA-256 did not match the expected fingerprint." >&2
|
| 318 |
+
exit 1
|
| 319 |
+
fi
|
| 320 |
+
fi
|
| 321 |
+
|
| 322 |
+
- name: Export AKV PKCS11 signing environment
|
| 323 |
+
if: ${{ inputs.setup-mode == 'signing' }}
|
| 324 |
+
shell: bash
|
| 325 |
+
env:
|
| 326 |
+
RCODESIGN_ROOT: ${{ steps.paths.outputs.rcodesign_root }}
|
| 327 |
+
PKCS11_LIBRARY: ${{ steps.paths.outputs.pkcs11_library }}
|
| 328 |
+
SIGNING_CERTIFICATE_PEM: ${{ steps.paths.outputs.signing_certificate_pem }}
|
| 329 |
+
KEY_VAULT_NAME: ${{ inputs.key-vault-name }}
|
| 330 |
+
KEY_NAME: ${{ inputs.key-name }}
|
| 331 |
+
KEY_VERSION: ${{ inputs.key-version }}
|
| 332 |
+
run: |
|
| 333 |
+
set -euo pipefail
|
| 334 |
+
|
| 335 |
+
{
|
| 336 |
+
echo "$RCODESIGN_ROOT/bin"
|
| 337 |
+
} >> "$GITHUB_PATH"
|
| 338 |
+
|
| 339 |
+
{
|
| 340 |
+
echo "OAI_CODESIGN_BACKEND=akv-pkcs11"
|
| 341 |
+
echo "OAI_AKV_PKCS11_LIBRARY=$PKCS11_LIBRARY"
|
| 342 |
+
echo "OAI_AKV_SIGNING_CERTIFICATE_PEM=$SIGNING_CERTIFICATE_PEM"
|
| 343 |
+
echo "OAI_AKV_KEY_LABEL=$KEY_NAME"
|
| 344 |
+
echo "AZURE_CREDENTIAL_KIND=azurecli"
|
| 345 |
+
echo "AZURE_KEYVAULT_NAME=$KEY_VAULT_NAME"
|
| 346 |
+
if [[ -n "$KEY_VERSION" ]]; then
|
| 347 |
+
echo "AZURE_KEYVAULT_KEY_VERSION=$KEY_VERSION"
|
| 348 |
+
fi
|
| 349 |
+
} >> "$GITHUB_ENV"
|
.github/actions/setup-ci/action.yml
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: setup-ci
|
| 2 |
+
description: Prepare common tools and environment shared by CI jobs.
|
| 3 |
+
outputs:
|
| 4 |
+
bazel-output-base:
|
| 5 |
+
description: Filesystem path used for Bazel's output base.
|
| 6 |
+
value: ${{ steps.configure_ci_build_paths.outputs.bazel-output-base }}
|
| 7 |
+
cargo-target-dir:
|
| 8 |
+
description: Filesystem path used for Cargo's target directory.
|
| 9 |
+
value: ${{ steps.configure_ci_build_paths.outputs.cargo-target-dir }}
|
| 10 |
+
|
| 11 |
+
runs:
|
| 12 |
+
using: composite
|
| 13 |
+
steps:
|
| 14 |
+
# setup-ci expects either this step or the Unix fallback below to define
|
| 15 |
+
# CI_BUILD_ROOT. Windows uses a Dev Drive because Cargo and Bazel
|
| 16 |
+
# spend significant time reading and writing build/cache trees.
|
| 17 |
+
- name: Configure Dev Drive (Windows)
|
| 18 |
+
if: runner.os == 'Windows'
|
| 19 |
+
shell: pwsh
|
| 20 |
+
run: ./.github/scripts/setup-dev-drive.ps1
|
| 21 |
+
|
| 22 |
+
- name: Configure CI build root (Unix)
|
| 23 |
+
if: runner.os != 'Windows'
|
| 24 |
+
shell: bash
|
| 25 |
+
run: echo "CI_BUILD_ROOT=$HOME/.cache/codex-ci" >> "$GITHUB_ENV"
|
| 26 |
+
|
| 27 |
+
- name: Enable unprivileged user namespaces (Linux)
|
| 28 |
+
if: runner.os == 'Linux'
|
| 29 |
+
shell: bash
|
| 30 |
+
run: |
|
| 31 |
+
sudo sysctl -w kernel.unprivileged_userns_clone=1
|
| 32 |
+
if sudo sysctl -a 2>/dev/null | grep -q '^kernel.apparmor_restrict_unprivileged_userns'; then
|
| 33 |
+
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
| 34 |
+
fi
|
| 35 |
+
|
| 36 |
+
- name: Configure CI build paths
|
| 37 |
+
id: configure_ci_build_paths
|
| 38 |
+
shell: bash
|
| 39 |
+
run: |
|
| 40 |
+
set -euo pipefail
|
| 41 |
+
# setup-bazel passes output_base explicitly, so keep both it and the
|
| 42 |
+
# user root under the shared build root. Keep these directory names tiny
|
| 43 |
+
# on every platform so Windows Bazel paths do not overflow argv or
|
| 44 |
+
# confuse test MANIFEST handling.
|
| 45 |
+
bazel_output_base="$CI_BUILD_ROOT/o"
|
| 46 |
+
bazel_output_user_root="$CI_BUILD_ROOT/b"
|
| 47 |
+
bazel_repository_cache="$CI_BUILD_ROOT/bazel-repository-cache"
|
| 48 |
+
bazel_repo_contents_cache="$CI_BUILD_ROOT/bazel-repo-contents-cache-$GITHUB_RUN_ID-$GITHUB_JOB"
|
| 49 |
+
cargo_target_dir="$CI_BUILD_ROOT/cargo-target"
|
| 50 |
+
tmp="$CI_BUILD_ROOT/tmp"
|
| 51 |
+
|
| 52 |
+
build_dirs=(
|
| 53 |
+
"$bazel_output_base"
|
| 54 |
+
"$bazel_output_user_root"
|
| 55 |
+
"$bazel_repository_cache"
|
| 56 |
+
"$bazel_repo_contents_cache"
|
| 57 |
+
"$cargo_target_dir"
|
| 58 |
+
"$tmp"
|
| 59 |
+
)
|
| 60 |
+
mkdir -p "${build_dirs[@]}"
|
| 61 |
+
echo "bazel-output-base=$bazel_output_base" >> "$GITHUB_OUTPUT"
|
| 62 |
+
echo "cargo-target-dir=$cargo_target_dir" >> "$GITHUB_OUTPUT"
|
| 63 |
+
|
| 64 |
+
{
|
| 65 |
+
echo "CODEX_REPO_ROOT=$GITHUB_WORKSPACE"
|
| 66 |
+
echo "BAZEL_OUTPUT_BASE=$bazel_output_base"
|
| 67 |
+
echo "BAZEL_OUTPUT_USER_ROOT=$bazel_output_user_root"
|
| 68 |
+
echo "BAZEL_REPOSITORY_CACHE=$bazel_repository_cache"
|
| 69 |
+
echo "BAZEL_REPO_CONTENTS_CACHE=$bazel_repo_contents_cache"
|
| 70 |
+
echo "CARGO_TARGET_DIR=$cargo_target_dir"
|
| 71 |
+
echo "TEMP=$tmp"
|
| 72 |
+
echo "TMP=$tmp"
|
| 73 |
+
} >> "$GITHUB_ENV"
|
| 74 |
+
|
| 75 |
+
- name: Prefer the Git CLI for Cargo git dependencies
|
| 76 |
+
shell: bash
|
| 77 |
+
run: echo "CARGO_NET_GIT_FETCH_WITH_CLI=true" >> "$GITHUB_ENV"
|
| 78 |
+
|
| 79 |
+
- name: Install DotSlash
|
| 80 |
+
uses: facebook/install-dotslash@1e4e7b3e07eaca387acb98f1d4720e0bee8dbb6a # v2
|
| 81 |
+
|
| 82 |
+
- name: Install just
|
| 83 |
+
uses: taiki-e/install-action@44c6d64aa62cd779e873306675c7a58e86d6d532 # v2.62.49
|
| 84 |
+
with:
|
| 85 |
+
tool: just@1.51.0
|
| 86 |
+
|
| 87 |
+
# Some integration tests spawn DotSlash from a stable system path rather
|
| 88 |
+
# than inheriting the action-local PATH entry.
|
| 89 |
+
- name: Make DotSlash available in PATH (Unix)
|
| 90 |
+
if: runner.os != 'Windows'
|
| 91 |
+
shell: bash
|
| 92 |
+
run: |
|
| 93 |
+
if [[ -w /usr/local/bin ]]; then
|
| 94 |
+
cp "$(which dotslash)" /usr/local/bin
|
| 95 |
+
else
|
| 96 |
+
sudo cp "$(which dotslash)" /usr/local/bin
|
| 97 |
+
fi
|
| 98 |
+
|
| 99 |
+
- name: Make DotSlash available in PATH (Windows)
|
| 100 |
+
if: runner.os == 'Windows'
|
| 101 |
+
shell: pwsh
|
| 102 |
+
run: Copy-Item (Get-Command dotslash).Source -Destination "$env:LOCALAPPDATA\Microsoft\WindowsApps\dotslash.exe"
|
| 103 |
+
|
| 104 |
+
- name: Enable Git long paths (Windows)
|
| 105 |
+
if: runner.os == 'Windows'
|
| 106 |
+
shell: pwsh
|
| 107 |
+
run: git config --global core.longpaths true
|
.github/actions/setup-msvc-env/action.yml
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: setup-msvc-env
|
| 2 |
+
description: Expose an MSVC developer environment for the requested Windows target.
|
| 3 |
+
inputs:
|
| 4 |
+
target:
|
| 5 |
+
description: Rust target triple that will be built on this Windows runner.
|
| 6 |
+
required: true
|
| 7 |
+
host-arch:
|
| 8 |
+
description: Optional Visual Studio host architecture override.
|
| 9 |
+
required: false
|
| 10 |
+
default: ""
|
| 11 |
+
|
| 12 |
+
runs:
|
| 13 |
+
using: composite
|
| 14 |
+
steps:
|
| 15 |
+
- name: Expose MSVC SDK environment
|
| 16 |
+
shell: pwsh
|
| 17 |
+
env:
|
| 18 |
+
HOST_ARCH: ${{ inputs.host-arch }}
|
| 19 |
+
TARGET: ${{ inputs.target }}
|
| 20 |
+
run: '& "$env:GITHUB_ACTION_PATH/setup-msvc-env.ps1" -Target "$env:TARGET" -HostArch "$env:HOST_ARCH"'
|
.github/actions/setup-msvc-env/setup-msvc-env.ps1
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
param(
|
| 2 |
+
[Parameter(Mandatory = $true)]
|
| 3 |
+
[string]$Target,
|
| 4 |
+
|
| 5 |
+
[string]$HostArch = ""
|
| 6 |
+
)
|
| 7 |
+
|
| 8 |
+
# Cargo can cross-compile the Rust code for Windows ARM64 on a Windows x64
|
| 9 |
+
# runner, but rustup alone does not expose the matching MSVC/UCRT include and
|
| 10 |
+
# library paths. Ask Visual Studio for the target-specific developer
|
| 11 |
+
# environment, then persist the relevant variables through GITHUB_ENV so the
|
| 12 |
+
# later Cargo step sees the same environment as a normal VsDevCmd shell.
|
| 13 |
+
switch ($Target) {
|
| 14 |
+
"x86_64-pc-windows-msvc" {
|
| 15 |
+
$TargetArch = "x64"
|
| 16 |
+
$RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.x86.x64"
|
| 17 |
+
}
|
| 18 |
+
"aarch64-pc-windows-msvc" {
|
| 19 |
+
$TargetArch = "arm64"
|
| 20 |
+
$RequiredComponent = "Microsoft.VisualStudio.Component.VC.Tools.ARM64"
|
| 21 |
+
}
|
| 22 |
+
default {
|
| 23 |
+
throw "Unsupported Windows MSVC target: $Target"
|
| 24 |
+
}
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
# VsDevCmd needs both sides of the cross compile: the architecture of the
|
| 28 |
+
# machine running the tools and the architecture of the binaries being linked.
|
| 29 |
+
# Infer the host from the runner unless a caller needs to override it.
|
| 30 |
+
if (-not $HostArch) {
|
| 31 |
+
$HostArch = if ($env:PROCESSOR_ARCHITEW6432 -eq "ARM64" -or $env:PROCESSOR_ARCHITECTURE -eq "ARM64") {
|
| 32 |
+
"arm64"
|
| 33 |
+
} else {
|
| 34 |
+
"x64"
|
| 35 |
+
}
|
| 36 |
+
}
|
| 37 |
+
|
| 38 |
+
$VsWhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe"
|
| 39 |
+
if (-not (Test-Path $VsWhere)) {
|
| 40 |
+
throw "vswhere.exe not found"
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
# Require the target VC tools component, not merely any Visual Studio install,
|
| 44 |
+
# so an x64 archive producer cannot silently link ARM64 tests with the wrong
|
| 45 |
+
# SDK/toolchain layout.
|
| 46 |
+
$InstallPath = & $VsWhere -latest -products * -requires $RequiredComponent -property installationPath 2>$null
|
| 47 |
+
if (-not $InstallPath) {
|
| 48 |
+
throw "Could not locate a Visual Studio installation with component $RequiredComponent"
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
$VsDevCmd = Join-Path $InstallPath "Common7\Tools\VsDevCmd.bat"
|
| 52 |
+
if (-not (Test-Path $VsDevCmd)) {
|
| 53 |
+
throw "VsDevCmd.bat not found at $VsDevCmd"
|
| 54 |
+
}
|
| 55 |
+
|
| 56 |
+
$VarsToExport = @(
|
| 57 |
+
"INCLUDE",
|
| 58 |
+
"LIB",
|
| 59 |
+
"LIBPATH",
|
| 60 |
+
"PATH",
|
| 61 |
+
"UCRTVersion",
|
| 62 |
+
"UniversalCRTSdkDir",
|
| 63 |
+
"VCINSTALLDIR",
|
| 64 |
+
"VCToolsInstallDir",
|
| 65 |
+
"WindowsLibPath",
|
| 66 |
+
"WindowsSdkBinPath",
|
| 67 |
+
"WindowsSdkDir",
|
| 68 |
+
"WindowsSDKLibVersion",
|
| 69 |
+
"WindowsSDKVersion"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
# Run VsDevCmd inside cmd.exe because it is a batch file, then copy just the
|
| 73 |
+
# variables Cargo/rustc need into the GitHub Actions environment file. PowerShell
|
| 74 |
+
# cannot mutate the parent composite-action environment directly.
|
| 75 |
+
$EnvLines = & cmd.exe /c ('"{0}" -no_logo -arch={1} -host_arch={2} >nul && set' -f $VsDevCmd, $TargetArch, $HostArch)
|
| 76 |
+
$VcToolsInstallDir = $null
|
| 77 |
+
foreach ($Line in $EnvLines) {
|
| 78 |
+
if ($Line -notmatch "^(.*?)=(.*)$") {
|
| 79 |
+
continue
|
| 80 |
+
}
|
| 81 |
+
|
| 82 |
+
$Name = $Matches[1]
|
| 83 |
+
$Value = $Matches[2]
|
| 84 |
+
if ($VarsToExport -contains $Name) {
|
| 85 |
+
if ($Name -ieq "Path") {
|
| 86 |
+
$Name = "PATH"
|
| 87 |
+
}
|
| 88 |
+
if ($Name -eq "VCToolsInstallDir") {
|
| 89 |
+
$VcToolsInstallDir = $Value
|
| 90 |
+
}
|
| 91 |
+
"$Name=$Value" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
| 92 |
+
}
|
| 93 |
+
}
|
| 94 |
+
|
| 95 |
+
if (-not $VcToolsInstallDir) {
|
| 96 |
+
throw "VCToolsInstallDir was not exported by VsDevCmd.bat"
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
# Prefer Rust's bundled linker when rustup provides one, then Visual Studio's
|
| 100 |
+
# LLVM linker, and finally MSVC link.exe. This keeps the cross-compile path close
|
| 101 |
+
# to Rust's normal Windows MSVC behavior while still working on runner images
|
| 102 |
+
# where one of those linkers is absent.
|
| 103 |
+
$Linker = $null
|
| 104 |
+
$Rustc = Get-Command rustc -ErrorAction SilentlyContinue
|
| 105 |
+
if ($Rustc) {
|
| 106 |
+
$Sysroot = (& rustc --print sysroot 2>$null).Trim()
|
| 107 |
+
$RustHost = & rustc -vV 2>$null | Select-String "^host: " | ForEach-Object { $_.Line.Substring(6) }
|
| 108 |
+
if ($RustHost) {
|
| 109 |
+
$RustHost = $RustHost.Trim()
|
| 110 |
+
}
|
| 111 |
+
if ($Sysroot -and $RustHost) {
|
| 112 |
+
$RustLld = Join-Path $Sysroot "lib\rustlib\$RustHost\bin\rust-lld.exe"
|
| 113 |
+
if (Test-Path $RustLld) {
|
| 114 |
+
$Linker = $RustLld
|
| 115 |
+
}
|
| 116 |
+
}
|
| 117 |
+
}
|
| 118 |
+
if (-not $Linker) {
|
| 119 |
+
$Linker = Join-Path $InstallPath "VC\Tools\Llvm\x64\bin\lld-link.exe"
|
| 120 |
+
}
|
| 121 |
+
if (-not (Test-Path $Linker)) {
|
| 122 |
+
$Linker = Join-Path $VcToolsInstallDir "bin\Host${HostArch}\${TargetArch}\link.exe"
|
| 123 |
+
}
|
| 124 |
+
if (-not (Test-Path $Linker)) {
|
| 125 |
+
throw "Windows linker not found at $Linker"
|
| 126 |
+
}
|
| 127 |
+
|
| 128 |
+
# rustc passes `/arm64hazardfree` for ARM64 MSVC links. The lld variants on our
|
| 129 |
+
# Windows x64 archive producers reject that flag, including when rustc places it
|
| 130 |
+
# inside a response file. Compile a tiny forwarding wrapper that strips only
|
| 131 |
+
# that unsupported flag, then delegate every other argument to the real linker.
|
| 132 |
+
if ($TargetArch -eq "arm64" -and (Split-Path -Leaf $Linker) -match "lld") {
|
| 133 |
+
$WrapperDir = Join-Path $env:RUNNER_TEMP "msvc-lld-wrapper"
|
| 134 |
+
New-Item -Path $WrapperDir -ItemType Directory -Force | Out-Null
|
| 135 |
+
$WrapperPath = Join-Path $WrapperDir "lld-link-wrapper.exe"
|
| 136 |
+
$WrapperSource = @'
|
| 137 |
+
using System;
|
| 138 |
+
using System.Collections.Generic;
|
| 139 |
+
using System.Diagnostics;
|
| 140 |
+
using System.IO;
|
| 141 |
+
using System.Text;
|
| 142 |
+
using System.Text.RegularExpressions;
|
| 143 |
+
|
| 144 |
+
internal static class Program
|
| 145 |
+
{
|
| 146 |
+
private static int Main(string[] args)
|
| 147 |
+
{
|
| 148 |
+
var linker = Environment.GetEnvironmentVariable("MSVC_REAL_LINKER");
|
| 149 |
+
if (string.IsNullOrEmpty(linker))
|
| 150 |
+
{
|
| 151 |
+
Console.Error.WriteLine("MSVC_REAL_LINKER is not set");
|
| 152 |
+
return 1;
|
| 153 |
+
}
|
| 154 |
+
|
| 155 |
+
var startInfo = new ProcessStartInfo(linker)
|
| 156 |
+
{
|
| 157 |
+
UseShellExecute = false,
|
| 158 |
+
};
|
| 159 |
+
var filteredArgs = new List<string> { "-flavor", "link", "/defaultlib:ucrt", "/nodefaultlib:libucrt" };
|
| 160 |
+
foreach (var arg in args)
|
| 161 |
+
{
|
| 162 |
+
if (!string.Equals(arg, "/arm64hazardfree", StringComparison.OrdinalIgnoreCase))
|
| 163 |
+
{
|
| 164 |
+
filteredArgs.Add(QuoteArgument(FilterResponseFile(arg)));
|
| 165 |
+
}
|
| 166 |
+
}
|
| 167 |
+
startInfo.Arguments = string.Join(" ", filteredArgs);
|
| 168 |
+
|
| 169 |
+
using var process = Process.Start(startInfo);
|
| 170 |
+
if (process is null)
|
| 171 |
+
{
|
| 172 |
+
Console.Error.WriteLine($"Failed to start linker: {linker}");
|
| 173 |
+
return 1;
|
| 174 |
+
}
|
| 175 |
+
|
| 176 |
+
process.WaitForExit();
|
| 177 |
+
return process.ExitCode;
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
private static string FilterResponseFile(string argument)
|
| 181 |
+
{
|
| 182 |
+
if (argument.Length < 2 || argument[0] != '@')
|
| 183 |
+
{
|
| 184 |
+
return argument;
|
| 185 |
+
}
|
| 186 |
+
|
| 187 |
+
var responsePath = argument.Substring(1);
|
| 188 |
+
if (!File.Exists(responsePath))
|
| 189 |
+
{
|
| 190 |
+
return argument;
|
| 191 |
+
}
|
| 192 |
+
|
| 193 |
+
var filteredResponsePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName() + ".rsp");
|
| 194 |
+
var responseContents = Regex.Replace(
|
| 195 |
+
File.ReadAllText(responsePath),
|
| 196 |
+
"/arm64hazardfree",
|
| 197 |
+
string.Empty,
|
| 198 |
+
RegexOptions.IgnoreCase);
|
| 199 |
+
File.WriteAllText(filteredResponsePath, responseContents);
|
| 200 |
+
return "@" + filteredResponsePath;
|
| 201 |
+
}
|
| 202 |
+
|
| 203 |
+
private static string QuoteArgument(string argument)
|
| 204 |
+
{
|
| 205 |
+
if (argument.Length == 0)
|
| 206 |
+
{
|
| 207 |
+
return "\"\"";
|
| 208 |
+
}
|
| 209 |
+
if (argument.IndexOfAny(new[] { ' ', '\t', '"' }) < 0)
|
| 210 |
+
{
|
| 211 |
+
return argument;
|
| 212 |
+
}
|
| 213 |
+
|
| 214 |
+
var quoted = new StringBuilder("\"");
|
| 215 |
+
var backslashes = 0;
|
| 216 |
+
foreach (var character in argument)
|
| 217 |
+
{
|
| 218 |
+
if (character == '\\')
|
| 219 |
+
{
|
| 220 |
+
backslashes++;
|
| 221 |
+
continue;
|
| 222 |
+
}
|
| 223 |
+
if (character == '"')
|
| 224 |
+
{
|
| 225 |
+
quoted.Append('\\', (backslashes * 2) + 1);
|
| 226 |
+
quoted.Append(character);
|
| 227 |
+
backslashes = 0;
|
| 228 |
+
continue;
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
quoted.Append('\\', backslashes);
|
| 232 |
+
backslashes = 0;
|
| 233 |
+
quoted.Append(character);
|
| 234 |
+
}
|
| 235 |
+
quoted.Append('\\', backslashes * 2);
|
| 236 |
+
quoted.Append('"');
|
| 237 |
+
return quoted.ToString();
|
| 238 |
+
}
|
| 239 |
+
}
|
| 240 |
+
'@
|
| 241 |
+
$WrapperSourcePath = Join-Path $WrapperDir "lld-link-wrapper.cs"
|
| 242 |
+
$WrapperSource | Out-File -FilePath $WrapperSourcePath -Encoding utf8
|
| 243 |
+
$Csc = Join-Path $InstallPath "MSBuild\Current\Bin\Roslyn\csc.exe"
|
| 244 |
+
if (-not (Test-Path $Csc)) {
|
| 245 |
+
throw "csc.exe not found at $Csc"
|
| 246 |
+
}
|
| 247 |
+
& $Csc /nologo /target:exe /out:$WrapperPath $WrapperSourcePath
|
| 248 |
+
if ($LASTEXITCODE -ne 0) {
|
| 249 |
+
throw "Failed to compile lld-link wrapper"
|
| 250 |
+
}
|
| 251 |
+
"MSVC_REAL_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
| 252 |
+
$Linker = $WrapperPath
|
| 253 |
+
}
|
| 254 |
+
|
| 255 |
+
Write-Output "Using Windows linker: $Linker"
|
| 256 |
+
$CargoTarget = $Target.ToUpperInvariant().Replace("-", "_")
|
| 257 |
+
"CARGO_TARGET_${CargoTarget}_LINKER=$Linker" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append
|
.github/actions/setup-rusty-v8/action.yml
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: setup-rusty-v8
|
| 2 |
+
description: Download and verify Codex-built rusty_v8 artifacts for Cargo builds.
|
| 3 |
+
inputs:
|
| 4 |
+
target:
|
| 5 |
+
description: Rust target triple with Codex-built V8 release artifacts.
|
| 6 |
+
required: true
|
| 7 |
+
|
| 8 |
+
runs:
|
| 9 |
+
using: composite
|
| 10 |
+
steps:
|
| 11 |
+
- name: Configure rusty_v8 artifact overrides and verify checksums
|
| 12 |
+
shell: bash
|
| 13 |
+
env:
|
| 14 |
+
TARGET: ${{ inputs.target }}
|
| 15 |
+
run: |
|
| 16 |
+
set -euo pipefail
|
| 17 |
+
|
| 18 |
+
version="$(python3 "${GITHUB_WORKSPACE}/.github/scripts/rusty_v8_bazel.py" resolved-v8-crate-version)"
|
| 19 |
+
release_tag="rusty-v8-v${version}"
|
| 20 |
+
base_url="https://github.com/openai/codex/releases/download/${release_tag}"
|
| 21 |
+
binding_dir="${RUNNER_TEMP}/rusty_v8"
|
| 22 |
+
|
| 23 |
+
profile="ptrcomp_sandbox_release"
|
| 24 |
+
|
| 25 |
+
if [[ "$TARGET" == *-pc-windows-msvc ]]; then
|
| 26 |
+
archive_name="rusty_v8_${profile}_${TARGET}.lib.gz"
|
| 27 |
+
else
|
| 28 |
+
archive_name="librusty_v8_${profile}_${TARGET}.a.gz"
|
| 29 |
+
fi
|
| 30 |
+
binding_name="src_binding_${profile}_${TARGET}.rs"
|
| 31 |
+
checksums_name="rusty_v8_${profile}_${TARGET}.sha256"
|
| 32 |
+
|
| 33 |
+
archive_path="${binding_dir}/${archive_name}"
|
| 34 |
+
binding_path="${binding_dir}/${binding_name}"
|
| 35 |
+
checksums_path="${binding_dir}/${checksums_name}"
|
| 36 |
+
trusted_checksums="${GITHUB_WORKSPACE}/third_party/v8/rusty_v8_${version//./_}_release_manifests.sha256"
|
| 37 |
+
|
| 38 |
+
if command -v sha256sum >/dev/null 2>&1; then
|
| 39 |
+
checksum_command=(sha256sum --check -)
|
| 40 |
+
else
|
| 41 |
+
checksum_command=(shasum -a 256 --check -)
|
| 42 |
+
fi
|
| 43 |
+
|
| 44 |
+
mkdir -p "${binding_dir}"
|
| 45 |
+
curl -fsSL "${base_url}/${checksums_name}" -o "${checksums_path}"
|
| 46 |
+
# Check the original manifest bytes even when either checksum file uses CRLF.
|
| 47 |
+
expected_manifest_checksum="$(grep -F " ${checksums_name}" "${trusted_checksums}" | cut -d ' ' -f 1)"
|
| 48 |
+
actual_manifest_checksum="$(python3 -c 'import hashlib, pathlib, sys; print(hashlib.sha256(pathlib.Path(sys.argv[1]).read_bytes()).hexdigest())' "${checksums_path}")"
|
| 49 |
+
if [[ "${actual_manifest_checksum}" != "${expected_manifest_checksum}" ]]; then
|
| 50 |
+
echo "Checksum mismatch for ${checksums_name}: expected ${expected_manifest_checksum}, got ${actual_manifest_checksum}" >&2
|
| 51 |
+
exit 1
|
| 52 |
+
fi
|
| 53 |
+
curl -fsSL "${base_url}/${archive_name}" -o "${archive_path}"
|
| 54 |
+
curl -fsSL "${base_url}/${binding_name}" -o "${binding_path}"
|
| 55 |
+
|
| 56 |
+
if [[ "$(wc -l < "${checksums_path}")" -ne 2 ]]; then
|
| 57 |
+
echo "Expected exactly two checksums for ${TARGET} in ${checksums_path}" >&2
|
| 58 |
+
exit 1
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
# Existing Windows-built release manifests use CRLF line endings.
|
| 62 |
+
(cd "${binding_dir}" && tr -d '\r' < "${checksums_path}" | "${checksum_command[@]}")
|
| 63 |
+
echo "RUSTY_V8_ARCHIVE=${archive_path}" >> "${GITHUB_ENV}"
|
| 64 |
+
echo "RUSTY_V8_SRC_BINDING_PATH=${binding_path}" >> "${GITHUB_ENV}"
|
.github/actions/windows-code-sign/action.yml
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
name: windows-code-sign
|
| 2 |
+
description: Sign Windows binaries with Azure Trusted Signing.
|
| 3 |
+
inputs:
|
| 4 |
+
target:
|
| 5 |
+
description: Target triple for the artifacts to sign.
|
| 6 |
+
required: true
|
| 7 |
+
binaries:
|
| 8 |
+
description: Space-delimited binary basenames to sign.
|
| 9 |
+
default: "codex codex-responses-api-proxy codex-windows-sandbox-setup codex-command-runner"
|
| 10 |
+
client-id:
|
| 11 |
+
description: Azure Trusted Signing client ID.
|
| 12 |
+
required: true
|
| 13 |
+
tenant-id:
|
| 14 |
+
description: Azure tenant ID for Trusted Signing.
|
| 15 |
+
required: true
|
| 16 |
+
subscription-id:
|
| 17 |
+
description: Azure subscription ID for Trusted Signing.
|
| 18 |
+
required: true
|
| 19 |
+
endpoint:
|
| 20 |
+
description: Azure Trusted Signing endpoint.
|
| 21 |
+
required: true
|
| 22 |
+
account-name:
|
| 23 |
+
description: Azure Trusted Signing account name.
|
| 24 |
+
required: true
|
| 25 |
+
certificate-profile-name:
|
| 26 |
+
description: Certificate profile name for signing.
|
| 27 |
+
required: true
|
| 28 |
+
|
| 29 |
+
runs:
|
| 30 |
+
using: composite
|
| 31 |
+
steps:
|
| 32 |
+
- name: Azure login for Trusted Signing (OIDC)
|
| 33 |
+
uses: azure/login@a457da9ea143d694b1b9c7c869ebb04ebe844ef5 # v2.3.0
|
| 34 |
+
with:
|
| 35 |
+
client-id: ${{ inputs.client-id }}
|
| 36 |
+
tenant-id: ${{ inputs.tenant-id }}
|
| 37 |
+
subscription-id: ${{ inputs.subscription-id }}
|
| 38 |
+
|
| 39 |
+
- name: Prepare file list
|
| 40 |
+
id: prepare
|
| 41 |
+
shell: bash
|
| 42 |
+
env:
|
| 43 |
+
TARGET: ${{ inputs.target }}
|
| 44 |
+
BINARIES: ${{ inputs.binaries }}
|
| 45 |
+
run: |
|
| 46 |
+
set -euo pipefail
|
| 47 |
+
|
| 48 |
+
{
|
| 49 |
+
echo "files<<EOF"
|
| 50 |
+
for binary in ${BINARIES}; do
|
| 51 |
+
echo "${CARGO_TARGET_DIR}/${TARGET}/release/${binary}.exe"
|
| 52 |
+
done
|
| 53 |
+
echo "EOF"
|
| 54 |
+
} >> "$GITHUB_OUTPUT"
|
| 55 |
+
|
| 56 |
+
- name: Sign Windows binaries with Azure Trusted Signing
|
| 57 |
+
uses: azure/trusted-signing-action@1d365fec12862c4aa68fcac418143d73f0cea293 # v0.5.11
|
| 58 |
+
with:
|
| 59 |
+
endpoint: ${{ inputs.endpoint }}
|
| 60 |
+
trusted-signing-account-name: ${{ inputs.account-name }}
|
| 61 |
+
certificate-profile-name: ${{ inputs.certificate-profile-name }}
|
| 62 |
+
exclude-environment-credential: true
|
| 63 |
+
exclude-workload-identity-credential: true
|
| 64 |
+
exclude-managed-identity-credential: true
|
| 65 |
+
exclude-shared-token-cache-credential: true
|
| 66 |
+
exclude-visual-studio-credential: true
|
| 67 |
+
exclude-visual-studio-code-credential: true
|
| 68 |
+
exclude-azure-cli-credential: false
|
| 69 |
+
exclude-azure-powershell-credential: true
|
| 70 |
+
exclude-azure-developer-cli-credential: true
|
| 71 |
+
exclude-interactive-browser-credential: true
|
| 72 |
+
cache-dependencies: false
|
| 73 |
+
files: ${{ steps.prepare.outputs.files }}
|
.github/codex/labels/codex-triage.md
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Troubleshoot whether the reported issue is valid.
|
| 2 |
+
|
| 3 |
+
Provide a concise and respectful comment summarizing the findings.
|
| 4 |
+
|
| 5 |
+
### {CODEX_ACTION_ISSUE_TITLE}
|
| 6 |
+
|
| 7 |
+
{CODEX_ACTION_ISSUE_BODY}
|
.github/scripts/macos-signing/codex-app-server.entitlements.plist
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 3 |
+
<plist version="1.0">
|
| 4 |
+
<dict>
|
| 5 |
+
<key>com.apple.security.cs.allow-jit</key>
|
| 6 |
+
<true/>
|
| 7 |
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
| 8 |
+
<true/>
|
| 9 |
+
</dict>
|
| 10 |
+
</plist>
|
.github/scripts/macos-signing/codex-code-mode-host.entitlements.plist
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 3 |
+
<plist version="1.0">
|
| 4 |
+
<dict>
|
| 5 |
+
<key>com.apple.security.cs.allow-jit</key>
|
| 6 |
+
<true/>
|
| 7 |
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
| 8 |
+
<true/>
|
| 9 |
+
</dict>
|
| 10 |
+
</plist>
|
.github/scripts/macos-signing/codex-responses-api-proxy.entitlements.plist
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 3 |
+
<plist version="1.0">
|
| 4 |
+
<dict>
|
| 5 |
+
<key>com.apple.security.cs.allow-jit</key>
|
| 6 |
+
<true/>
|
| 7 |
+
</dict>
|
| 8 |
+
</plist>
|
.github/scripts/macos-signing/codex-voice-host.entitlements.plist
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 3 |
+
<plist version="1.0">
|
| 4 |
+
<dict>
|
| 5 |
+
<key>com.apple.security.device.audio-input</key>
|
| 6 |
+
<true/>
|
| 7 |
+
</dict>
|
| 8 |
+
</plist>
|
.github/scripts/macos-signing/codex.entitlements.plist
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<?xml version="1.0" encoding="UTF-8"?>
|
| 2 |
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
| 3 |
+
<plist version="1.0">
|
| 4 |
+
<dict>
|
| 5 |
+
<key>com.apple.security.cs.allow-jit</key>
|
| 6 |
+
<true/>
|
| 7 |
+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
|
| 8 |
+
<true/>
|
| 9 |
+
</dict>
|
| 10 |
+
</plist>
|
.github/scripts/macos-signing/notarize_macos_binary_with_akv.sh
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# Notarize a standalone binary and retain its diagnostic report.
|
| 4 |
+
|
| 5 |
+
set -euo pipefail
|
| 6 |
+
|
| 7 |
+
usage() {
|
| 8 |
+
cat >&2 <<'EOF'
|
| 9 |
+
Usage: notarize_macos_binary_with_akv.sh --binary PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
|
| 10 |
+
|
| 11 |
+
Options:
|
| 12 |
+
--binary PATH Signed standalone macOS binary to notarize.
|
| 13 |
+
--report-dir PATH Directory for notarization logs.
|
| 14 |
+
--max-wait-seconds SECONDS Maximum Apple notarization wait time.
|
| 15 |
+
EOF
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
binary_path=""
|
| 19 |
+
report_dir="${RUNNER_TEMP:-/tmp}/macos-binary-notarization-verification"
|
| 20 |
+
max_wait_seconds="600"
|
| 21 |
+
|
| 22 |
+
while [[ $# -gt 0 ]]; do
|
| 23 |
+
case "$1" in
|
| 24 |
+
--binary)
|
| 25 |
+
binary_path="${2:-}"
|
| 26 |
+
shift 2
|
| 27 |
+
;;
|
| 28 |
+
--report-dir)
|
| 29 |
+
report_dir="${2:-}"
|
| 30 |
+
shift 2
|
| 31 |
+
;;
|
| 32 |
+
--max-wait-seconds)
|
| 33 |
+
max_wait_seconds="${2:-}"
|
| 34 |
+
shift 2
|
| 35 |
+
;;
|
| 36 |
+
-h|--help)
|
| 37 |
+
usage
|
| 38 |
+
exit 0
|
| 39 |
+
;;
|
| 40 |
+
*)
|
| 41 |
+
echo "Unknown notarization argument: $1" >&2
|
| 42 |
+
usage
|
| 43 |
+
exit 2
|
| 44 |
+
;;
|
| 45 |
+
esac
|
| 46 |
+
done
|
| 47 |
+
|
| 48 |
+
if [[ -z "$binary_path" ]]; then
|
| 49 |
+
echo "--binary is required." >&2
|
| 50 |
+
usage
|
| 51 |
+
exit 2
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
if [[ ! -f "$binary_path" ]]; then
|
| 55 |
+
echo "Binary does not exist: $binary_path" >&2
|
| 56 |
+
exit 1
|
| 57 |
+
fi
|
| 58 |
+
|
| 59 |
+
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
|
| 60 |
+
echo "--max-wait-seconds must be a non-negative integer." >&2
|
| 61 |
+
exit 2
|
| 62 |
+
fi
|
| 63 |
+
|
| 64 |
+
for command_name in python3 zip; do
|
| 65 |
+
if ! command -v "$command_name" >/dev/null 2>&1; then
|
| 66 |
+
echo "$command_name was not found on PATH." >&2
|
| 67 |
+
exit 1
|
| 68 |
+
fi
|
| 69 |
+
done
|
| 70 |
+
|
| 71 |
+
missing_environment=0
|
| 72 |
+
for variable_name in \
|
| 73 |
+
APPLE_NOTARIZATION_AKV_KEY_NAME \
|
| 74 |
+
AZURE_KEYVAULT_NAME
|
| 75 |
+
do
|
| 76 |
+
if [[ -z "${!variable_name:-}" ]]; then
|
| 77 |
+
echo "$variable_name must be configured before notarizing a binary." >&2
|
| 78 |
+
missing_environment=1
|
| 79 |
+
fi
|
| 80 |
+
done
|
| 81 |
+
|
| 82 |
+
if [[ "$missing_environment" -ne 0 ]]; then
|
| 83 |
+
exit 2
|
| 84 |
+
fi
|
| 85 |
+
|
| 86 |
+
mkdir -p "$report_dir"
|
| 87 |
+
|
| 88 |
+
notarization_temp_dir="$(mktemp -d)"
|
| 89 |
+
trap 'rm -rf "$notarization_temp_dir" >/dev/null' EXIT
|
| 90 |
+
|
| 91 |
+
binary_name="$(basename "$binary_path")"
|
| 92 |
+
archive_path="$notarization_temp_dir/${binary_name}.zip"
|
| 93 |
+
(
|
| 94 |
+
cd "$(dirname "$binary_path")"
|
| 95 |
+
zip -q "$archive_path" "$binary_name"
|
| 96 |
+
)
|
| 97 |
+
|
| 98 |
+
notarization_log="$report_dir/${binary_name}-notarization.log"
|
| 99 |
+
python3 "$(dirname "$0")/notarize_with_akv.py" \
|
| 100 |
+
--file "$archive_path" \
|
| 101 |
+
--report-log "$report_dir/${binary_name}-notarization-developer-log.json" \
|
| 102 |
+
--max-wait-seconds "$max_wait_seconds" \
|
| 103 |
+
2>&1 | tee "$notarization_log"
|
| 104 |
+
|
| 105 |
+
{
|
| 106 |
+
echo "binary_name=$binary_name"
|
| 107 |
+
echo "max_wait_seconds=$max_wait_seconds"
|
| 108 |
+
echo "binary_sha256=$(shasum -a 256 "$binary_path" | awk '{ print $1 }')"
|
| 109 |
+
echo "notarization=completed"
|
| 110 |
+
} >"$report_dir/${binary_name}-notarization-summary.txt"
|
.github/scripts/macos-signing/notarize_macos_dmg_with_akv.sh
ADDED
|
@@ -0,0 +1,100 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# Notarize a signed disk image and staple its ticket.
|
| 4 |
+
|
| 5 |
+
set -euo pipefail
|
| 6 |
+
|
| 7 |
+
usage() {
|
| 8 |
+
cat >&2 <<'EOF'
|
| 9 |
+
Usage: notarize_macos_dmg_with_akv.sh --dmg PATH [--report-dir PATH] [--max-wait-seconds SECONDS]
|
| 10 |
+
|
| 11 |
+
Options:
|
| 12 |
+
--dmg PATH Signed DMG to submit to Apple notarization.
|
| 13 |
+
--report-dir PATH Directory for notarization logs.
|
| 14 |
+
--max-wait-seconds SECONDS Maximum Apple notarization wait time.
|
| 15 |
+
EOF
|
| 16 |
+
}
|
| 17 |
+
|
| 18 |
+
dmg_path=""
|
| 19 |
+
report_dir="${RUNNER_TEMP:-/tmp}/macos-notarization-verification"
|
| 20 |
+
max_wait_seconds="600"
|
| 21 |
+
|
| 22 |
+
while [[ $# -gt 0 ]]; do
|
| 23 |
+
case "$1" in
|
| 24 |
+
--dmg)
|
| 25 |
+
dmg_path="${2:-}"
|
| 26 |
+
shift 2
|
| 27 |
+
;;
|
| 28 |
+
--report-dir)
|
| 29 |
+
report_dir="${2:-}"
|
| 30 |
+
shift 2
|
| 31 |
+
;;
|
| 32 |
+
--max-wait-seconds)
|
| 33 |
+
max_wait_seconds="${2:-}"
|
| 34 |
+
shift 2
|
| 35 |
+
;;
|
| 36 |
+
-h|--help)
|
| 37 |
+
usage
|
| 38 |
+
exit 0
|
| 39 |
+
;;
|
| 40 |
+
*)
|
| 41 |
+
echo "Unknown notarization argument: $1" >&2
|
| 42 |
+
usage
|
| 43 |
+
exit 2
|
| 44 |
+
;;
|
| 45 |
+
esac
|
| 46 |
+
done
|
| 47 |
+
|
| 48 |
+
if [[ -z "$dmg_path" ]]; then
|
| 49 |
+
echo "--dmg is required." >&2
|
| 50 |
+
usage
|
| 51 |
+
exit 2
|
| 52 |
+
fi
|
| 53 |
+
|
| 54 |
+
if [[ ! -f "$dmg_path" ]]; then
|
| 55 |
+
echo "DMG does not exist: $dmg_path" >&2
|
| 56 |
+
exit 1
|
| 57 |
+
fi
|
| 58 |
+
|
| 59 |
+
if [[ ! "$max_wait_seconds" =~ ^[0-9]+$ ]]; then
|
| 60 |
+
echo "--max-wait-seconds must be a non-negative integer." >&2
|
| 61 |
+
exit 2
|
| 62 |
+
fi
|
| 63 |
+
|
| 64 |
+
if ! command -v rcodesign > /dev/null 2>&1; then
|
| 65 |
+
echo "rcodesign was not found on PATH." >&2
|
| 66 |
+
exit 1
|
| 67 |
+
fi
|
| 68 |
+
|
| 69 |
+
missing_environment=0
|
| 70 |
+
for variable_name in \
|
| 71 |
+
APPLE_NOTARIZATION_AKV_KEY_NAME \
|
| 72 |
+
AZURE_KEYVAULT_NAME
|
| 73 |
+
do
|
| 74 |
+
if [[ -z "${!variable_name:-}" ]]; then
|
| 75 |
+
echo "$variable_name must be configured before notarizing a DMG." >&2
|
| 76 |
+
missing_environment=1
|
| 77 |
+
fi
|
| 78 |
+
done
|
| 79 |
+
|
| 80 |
+
if [[ "$missing_environment" -ne 0 ]]; then
|
| 81 |
+
exit 2
|
| 82 |
+
fi
|
| 83 |
+
|
| 84 |
+
mkdir -p "$report_dir"
|
| 85 |
+
|
| 86 |
+
notarization_log="$report_dir/dmg-notarization.log"
|
| 87 |
+
python3 "$(dirname "$0")/notarize_with_akv.py" \
|
| 88 |
+
--file "$dmg_path" \
|
| 89 |
+
--report-log "$report_dir/dmg-notarization-developer-log.json" \
|
| 90 |
+
--max-wait-seconds "$max_wait_seconds" \
|
| 91 |
+
2>&1 | tee "$notarization_log"
|
| 92 |
+
|
| 93 |
+
rcodesign staple "$dmg_path" 2>&1 | tee -a "$notarization_log"
|
| 94 |
+
|
| 95 |
+
{
|
| 96 |
+
echo "dmg_path=$dmg_path"
|
| 97 |
+
echo "max_wait_seconds=$max_wait_seconds"
|
| 98 |
+
echo "dmg_sha256=$(shasum -a 256 "$dmg_path" | awk '{ print $1 }')"
|
| 99 |
+
echo "notarization_staple=completed"
|
| 100 |
+
} > "$report_dir/dmg-notarization-summary.txt"
|
.github/scripts/macos-signing/notarize_with_akv.py
ADDED
|
@@ -0,0 +1,519 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Submit release artifacts for notarization and wait for the result."""
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import base64
|
| 6 |
+
import datetime
|
| 7 |
+
import hashlib
|
| 8 |
+
import hmac
|
| 9 |
+
import json
|
| 10 |
+
import os
|
| 11 |
+
import re
|
| 12 |
+
import subprocess
|
| 13 |
+
import sys
|
| 14 |
+
import time
|
| 15 |
+
import urllib.error
|
| 16 |
+
import urllib.parse
|
| 17 |
+
import urllib.request
|
| 18 |
+
from dataclasses import dataclass
|
| 19 |
+
from pathlib import Path
|
| 20 |
+
from typing import Any
|
| 21 |
+
|
| 22 |
+
APPLE_NOTARY_URL = "https://appstoreconnect.apple.com/notary/v2/submissions"
|
| 23 |
+
AZURE_KEYVAULT_API_VERSION = "7.4"
|
| 24 |
+
AWS_REGION = "us-west-2"
|
| 25 |
+
JWT_LIFETIME_SECONDS = 15 * 60
|
| 26 |
+
POLL_INTERVAL_SECONDS = 10
|
| 27 |
+
MAX_SINGLE_UPLOAD_BYTES = 5 * 1024 * 1024 * 1024
|
| 28 |
+
|
| 29 |
+
|
| 30 |
+
class NotarizationError(RuntimeError):
|
| 31 |
+
"""Describe a notarization failure."""
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
@dataclass(frozen=True)
|
| 35 |
+
class NotarizationConfiguration:
|
| 36 |
+
"""Hold the account and signing-key configuration."""
|
| 37 |
+
|
| 38 |
+
issuer_id: str
|
| 39 |
+
apple_key_id: str
|
| 40 |
+
vault_name: str
|
| 41 |
+
vault_key_name: str
|
| 42 |
+
vault_key_version: str
|
| 43 |
+
|
| 44 |
+
@classmethod
|
| 45 |
+
def from_environment(cls) -> "NotarizationConfiguration":
|
| 46 |
+
"""Load and validate the notarization configuration."""
|
| 47 |
+
|
| 48 |
+
required = {
|
| 49 |
+
"vault_name": "AZURE_KEYVAULT_NAME",
|
| 50 |
+
"vault_key_name": "APPLE_NOTARIZATION_AKV_KEY_NAME",
|
| 51 |
+
}
|
| 52 |
+
values = {
|
| 53 |
+
field: os.environ.get(name, "").strip() for field, name in required.items()
|
| 54 |
+
}
|
| 55 |
+
missing = [name for field, name in required.items() if not values[field]]
|
| 56 |
+
if missing:
|
| 57 |
+
raise NotarizationError(
|
| 58 |
+
"Missing notarization configuration: " + ", ".join(missing)
|
| 59 |
+
)
|
| 60 |
+
if not re.fullmatch(r"[A-Za-z][A-Za-z0-9-]{2,23}", values["vault_name"]):
|
| 61 |
+
raise NotarizationError("Invalid signing vault name")
|
| 62 |
+
if not re.fullmatch(r"[A-Za-z0-9-]+", values["vault_key_name"]):
|
| 63 |
+
raise NotarizationError("Invalid notarization key name")
|
| 64 |
+
key_version = os.environ.get("APPLE_NOTARIZATION_AKV_KEY_VERSION", "").strip()
|
| 65 |
+
if key_version and not re.fullmatch(r"[0-9a-fA-F]{32}", key_version):
|
| 66 |
+
raise NotarizationError(
|
| 67 |
+
"Notarization key must resolve to a 32-character pinned version"
|
| 68 |
+
)
|
| 69 |
+
|
| 70 |
+
command = [
|
| 71 |
+
"az",
|
| 72 |
+
"keyvault",
|
| 73 |
+
"key",
|
| 74 |
+
"show",
|
| 75 |
+
"--vault-name",
|
| 76 |
+
values["vault_name"],
|
| 77 |
+
"--name",
|
| 78 |
+
values["vault_key_name"],
|
| 79 |
+
]
|
| 80 |
+
if key_version:
|
| 81 |
+
command.extend(["--version", key_version])
|
| 82 |
+
command.extend(
|
| 83 |
+
[
|
| 84 |
+
"--query",
|
| 85 |
+
'{id:key.kid,apple_key_id:tags."apple-key-id",'
|
| 86 |
+
'apple_issuer_id:tags."apple-issuer-id"}',
|
| 87 |
+
"--output",
|
| 88 |
+
"json",
|
| 89 |
+
"--only-show-errors",
|
| 90 |
+
]
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
# Resolve the key identifier and version from the same lookup.
|
| 94 |
+
try:
|
| 95 |
+
result = subprocess.run(command, check=True, capture_output=True, text=True)
|
| 96 |
+
except (OSError, subprocess.CalledProcessError) as error:
|
| 97 |
+
raise NotarizationError(
|
| 98 |
+
"Notarization signing key could not be read"
|
| 99 |
+
) from error
|
| 100 |
+
try:
|
| 101 |
+
metadata = json.loads(result.stdout)
|
| 102 |
+
returned_key_id = metadata["id"]
|
| 103 |
+
apple_key_id = metadata["apple_key_id"]
|
| 104 |
+
apple_issuer_id = metadata["apple_issuer_id"]
|
| 105 |
+
except (json.JSONDecodeError, KeyError, TypeError) as error:
|
| 106 |
+
raise NotarizationError("Notarization key metadata is invalid") from error
|
| 107 |
+
|
| 108 |
+
key_id_prefix = f"https://{values['vault_name']}.vault.azure.net/keys/{values['vault_key_name']}/"
|
| 109 |
+
if not isinstance(returned_key_id, str) or not returned_key_id.startswith(
|
| 110 |
+
key_id_prefix
|
| 111 |
+
):
|
| 112 |
+
raise NotarizationError("Unexpected notarization signing key")
|
| 113 |
+
returned_key_version = returned_key_id[len(key_id_prefix) :]
|
| 114 |
+
if not re.fullmatch(r"[0-9a-fA-F]{32}", returned_key_version):
|
| 115 |
+
raise NotarizationError(
|
| 116 |
+
"Notarization key must resolve to a 32-character pinned version"
|
| 117 |
+
)
|
| 118 |
+
if key_version and returned_key_version.lower() != key_version.lower():
|
| 119 |
+
raise NotarizationError("Unexpected notarization key version")
|
| 120 |
+
if not isinstance(apple_key_id, str) or not apple_key_id.strip():
|
| 121 |
+
raise NotarizationError("Notarization key must have an apple-key-id tag")
|
| 122 |
+
if not isinstance(apple_issuer_id, str) or not apple_issuer_id.strip():
|
| 123 |
+
raise NotarizationError("Notarization key must have an apple-issuer-id tag")
|
| 124 |
+
if not re.fullmatch(
|
| 125 |
+
r"[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-"
|
| 126 |
+
r"[0-9a-fA-F]{4}-[0-9a-fA-F]{12}",
|
| 127 |
+
apple_issuer_id.strip(),
|
| 128 |
+
):
|
| 129 |
+
raise NotarizationError(
|
| 130 |
+
"Notarization apple-issuer-id tag must contain a valid UUID"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
values["issuer_id"] = apple_issuer_id.strip()
|
| 134 |
+
values["apple_key_id"] = apple_key_id.strip()
|
| 135 |
+
values["vault_key_version"] = returned_key_version
|
| 136 |
+
return cls(**values)
|
| 137 |
+
|
| 138 |
+
@property
|
| 139 |
+
def versioned_key_id(self) -> str:
|
| 140 |
+
"""Return the versioned signing-key identifier."""
|
| 141 |
+
|
| 142 |
+
return (
|
| 143 |
+
f"https://{self.vault_name}.vault.azure.net/keys/"
|
| 144 |
+
f"{self.vault_key_name}/{self.vault_key_version}"
|
| 145 |
+
)
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
def base64url_encode(value: bytes) -> str:
|
| 149 |
+
"""Encode bytes using the unpadded base64url format required by JWTs."""
|
| 150 |
+
|
| 151 |
+
return base64.urlsafe_b64encode(value).decode("ascii").rstrip("=")
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def base64url_decode(value: str) -> bytes:
|
| 155 |
+
"""Decode a base64url value and reject malformed input."""
|
| 156 |
+
|
| 157 |
+
try:
|
| 158 |
+
return base64.b64decode(
|
| 159 |
+
value + "=" * (-len(value) % 4), altchars=b"-_", validate=True
|
| 160 |
+
)
|
| 161 |
+
except (ValueError, UnicodeEncodeError) as error:
|
| 162 |
+
raise NotarizationError(
|
| 163 |
+
"Signing service returned an invalid signature"
|
| 164 |
+
) from error
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
def create_apple_jwt(
|
| 168 |
+
configuration: NotarizationConfiguration, *, issued_at: int
|
| 169 |
+
) -> str:
|
| 170 |
+
"""Create an authentication token for notarization requests."""
|
| 171 |
+
|
| 172 |
+
header = {"alg": "ES256", "kid": configuration.apple_key_id, "typ": "JWT"}
|
| 173 |
+
# Restrict the token to notarization requests.
|
| 174 |
+
claims = {
|
| 175 |
+
"iss": configuration.issuer_id,
|
| 176 |
+
"iat": issued_at,
|
| 177 |
+
"exp": issued_at + JWT_LIFETIME_SECONDS,
|
| 178 |
+
"aud": "appstoreconnect-v1",
|
| 179 |
+
"scope": ["/notary/v2"],
|
| 180 |
+
}
|
| 181 |
+
encoded_header = base64url_encode(
|
| 182 |
+
json.dumps(header, separators=(",", ":")).encode("utf-8")
|
| 183 |
+
)
|
| 184 |
+
encoded_claims = base64url_encode(
|
| 185 |
+
json.dumps(claims, separators=(",", ":")).encode("utf-8")
|
| 186 |
+
)
|
| 187 |
+
signing_input = f"{encoded_header}.{encoded_claims}"
|
| 188 |
+
digest = hashlib.sha256(signing_input.encode("ascii")).digest()
|
| 189 |
+
key_url = configuration.versioned_key_id
|
| 190 |
+
request_body = json.dumps({"alg": "ES256", "value": base64url_encode(digest)})
|
| 191 |
+
|
| 192 |
+
try:
|
| 193 |
+
result = subprocess.run(
|
| 194 |
+
[
|
| 195 |
+
"az",
|
| 196 |
+
"rest",
|
| 197 |
+
"--method",
|
| 198 |
+
"post",
|
| 199 |
+
"--url",
|
| 200 |
+
f"{key_url}/sign?api-version={AZURE_KEYVAULT_API_VERSION}",
|
| 201 |
+
"--resource",
|
| 202 |
+
"https://vault.azure.net",
|
| 203 |
+
"--headers",
|
| 204 |
+
"Content-Type=application/json",
|
| 205 |
+
"--body",
|
| 206 |
+
request_body,
|
| 207 |
+
"--output",
|
| 208 |
+
"json",
|
| 209 |
+
"--only-show-errors",
|
| 210 |
+
],
|
| 211 |
+
check=True,
|
| 212 |
+
capture_output=True,
|
| 213 |
+
text=True,
|
| 214 |
+
)
|
| 215 |
+
except (OSError, subprocess.CalledProcessError) as error:
|
| 216 |
+
detail = getattr(error, "stderr", "").strip()
|
| 217 |
+
message = "Signing service could not sign the authentication token"
|
| 218 |
+
if detail:
|
| 219 |
+
message = f"{message}: {detail}"
|
| 220 |
+
raise NotarizationError(message) from error
|
| 221 |
+
|
| 222 |
+
try:
|
| 223 |
+
response = json.loads(result.stdout)
|
| 224 |
+
returned_key_id = response["kid"]
|
| 225 |
+
encoded_signature = response["value"]
|
| 226 |
+
except (json.JSONDecodeError, KeyError, TypeError) as error:
|
| 227 |
+
raise NotarizationError(
|
| 228 |
+
"Signing service returned an invalid response"
|
| 229 |
+
) from error
|
| 230 |
+
|
| 231 |
+
# Reject signatures generated with a different key version.
|
| 232 |
+
if returned_key_id != key_url:
|
| 233 |
+
raise NotarizationError("Signing service returned an unexpected key version")
|
| 234 |
+
if not isinstance(encoded_signature, str):
|
| 235 |
+
raise NotarizationError("Signing service returned an invalid signature")
|
| 236 |
+
signature = base64url_decode(encoded_signature)
|
| 237 |
+
# ES256 JWT signatures are 64 raw R || S bytes, not ASN.1/DER-encoded ECDSA signatures.
|
| 238 |
+
if len(signature) != 64:
|
| 239 |
+
raise NotarizationError("ES256 signature must contain 64 JOSE R || S bytes")
|
| 240 |
+
return f"{signing_input}.{base64url_encode(signature)}"
|
| 241 |
+
|
| 242 |
+
|
| 243 |
+
def json_request(
|
| 244 |
+
url: str, token: str, *, body: dict[str, Any] | None = None
|
| 245 |
+
) -> dict[str, Any]:
|
| 246 |
+
"""Send an authenticated JSON request to Apple's notarization service."""
|
| 247 |
+
|
| 248 |
+
data = None if body is None else json.dumps(body).encode("utf-8")
|
| 249 |
+
request = urllib.request.Request(
|
| 250 |
+
url,
|
| 251 |
+
data=data,
|
| 252 |
+
method="GET" if body is None else "POST",
|
| 253 |
+
headers={
|
| 254 |
+
"Accept": "application/json",
|
| 255 |
+
"Authorization": f"Bearer {token}",
|
| 256 |
+
"Content-Type": "application/json",
|
| 257 |
+
},
|
| 258 |
+
)
|
| 259 |
+
try:
|
| 260 |
+
with urllib.request.urlopen(request, timeout=60) as response:
|
| 261 |
+
result = json.load(response)
|
| 262 |
+
except urllib.error.HTTPError as error:
|
| 263 |
+
detail = error.read(1024).decode("utf-8", errors="replace")
|
| 264 |
+
raise NotarizationError(
|
| 265 |
+
f"Apple Notary API returned HTTP {error.code}: {detail}"
|
| 266 |
+
) from error
|
| 267 |
+
except (OSError, json.JSONDecodeError) as error:
|
| 268 |
+
raise NotarizationError("Apple Notary API request failed") from error
|
| 269 |
+
|
| 270 |
+
if not isinstance(result, dict):
|
| 271 |
+
raise NotarizationError("Apple Notary API returned an invalid response")
|
| 272 |
+
return result
|
| 273 |
+
|
| 274 |
+
|
| 275 |
+
def sha256_file(path: Path) -> str:
|
| 276 |
+
"""Hash an artifact incrementally so large release files stay off the heap."""
|
| 277 |
+
|
| 278 |
+
digest = hashlib.sha256()
|
| 279 |
+
with path.open("rb") as source:
|
| 280 |
+
for chunk in iter(lambda: source.read(1024 * 1024), b""):
|
| 281 |
+
digest.update(chunk)
|
| 282 |
+
return digest.hexdigest()
|
| 283 |
+
|
| 284 |
+
|
| 285 |
+
def hmac_sha256(key: bytes, value: str) -> bytes:
|
| 286 |
+
"""Derive one AWS Signature Version 4 signing-key component."""
|
| 287 |
+
|
| 288 |
+
return hmac.new(key, value.encode("utf-8"), hashlib.sha256).digest()
|
| 289 |
+
|
| 290 |
+
|
| 291 |
+
def upload_to_apple_s3(
|
| 292 |
+
path: Path,
|
| 293 |
+
file_digest: str,
|
| 294 |
+
credentials: dict[str, Any],
|
| 295 |
+
*,
|
| 296 |
+
timestamp: datetime.datetime | None = None,
|
| 297 |
+
) -> None:
|
| 298 |
+
"""Upload an artifact using the provided temporary credentials."""
|
| 299 |
+
|
| 300 |
+
required = (
|
| 301 |
+
"awsAccessKeyId",
|
| 302 |
+
"awsSecretAccessKey",
|
| 303 |
+
"awsSessionToken",
|
| 304 |
+
"bucket",
|
| 305 |
+
"object",
|
| 306 |
+
)
|
| 307 |
+
if any(
|
| 308 |
+
not isinstance(credentials.get(name), str) or not credentials[name]
|
| 309 |
+
for name in required
|
| 310 |
+
):
|
| 311 |
+
raise NotarizationError(
|
| 312 |
+
"Apple returned incomplete temporary upload credentials"
|
| 313 |
+
)
|
| 314 |
+
|
| 315 |
+
size = path.stat().st_size
|
| 316 |
+
if not 0 < size <= MAX_SINGLE_UPLOAD_BYTES:
|
| 317 |
+
raise NotarizationError("Notarization payload must be between 1 byte and 5 GiB")
|
| 318 |
+
|
| 319 |
+
now = timestamp or datetime.datetime.now(datetime.timezone.utc)
|
| 320 |
+
amz_date = now.strftime("%Y%m%dT%H%M%SZ")
|
| 321 |
+
date_stamp = now.strftime("%Y%m%d")
|
| 322 |
+
host = f"{credentials['bucket']}.s3.{AWS_REGION}.amazonaws.com"
|
| 323 |
+
object_path = "/" + urllib.parse.quote(
|
| 324 |
+
credentials["object"].lstrip("/"), safe="/-_.~"
|
| 325 |
+
)
|
| 326 |
+
headers = {
|
| 327 |
+
"content-type": "application/octet-stream",
|
| 328 |
+
"host": host,
|
| 329 |
+
"x-amz-content-sha256": file_digest,
|
| 330 |
+
"x-amz-date": amz_date,
|
| 331 |
+
"x-amz-security-token": credentials["awsSessionToken"],
|
| 332 |
+
}
|
| 333 |
+
# Build the signature required by the upload endpoint.
|
| 334 |
+
signed_headers = ";".join(sorted(headers))
|
| 335 |
+
canonical_headers = "".join(f"{name}:{headers[name]}\n" for name in sorted(headers))
|
| 336 |
+
canonical_request = "\n".join(
|
| 337 |
+
["PUT", object_path, "", canonical_headers, signed_headers, file_digest]
|
| 338 |
+
)
|
| 339 |
+
credential_scope = f"{date_stamp}/{AWS_REGION}/s3/aws4_request"
|
| 340 |
+
string_to_sign = "\n".join(
|
| 341 |
+
[
|
| 342 |
+
"AWS4-HMAC-SHA256",
|
| 343 |
+
amz_date,
|
| 344 |
+
credential_scope,
|
| 345 |
+
hashlib.sha256(canonical_request.encode("utf-8")).hexdigest(),
|
| 346 |
+
]
|
| 347 |
+
)
|
| 348 |
+
signing_key = ("AWS4" + credentials["awsSecretAccessKey"]).encode("utf-8")
|
| 349 |
+
for component in (date_stamp, AWS_REGION, "s3", "aws4_request"):
|
| 350 |
+
signing_key = hmac_sha256(signing_key, component)
|
| 351 |
+
signature = hmac.new(
|
| 352 |
+
signing_key, string_to_sign.encode("utf-8"), hashlib.sha256
|
| 353 |
+
).hexdigest()
|
| 354 |
+
authorization = (
|
| 355 |
+
"AWS4-HMAC-SHA256 "
|
| 356 |
+
f"Credential={credentials['awsAccessKeyId']}/{credential_scope}, "
|
| 357 |
+
f"SignedHeaders={signed_headers}, Signature={signature}"
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
# Stream the artifact to avoid buffering its full contents.
|
| 361 |
+
with path.open("rb") as source:
|
| 362 |
+
request = urllib.request.Request(
|
| 363 |
+
f"https://{host}{object_path}",
|
| 364 |
+
data=source,
|
| 365 |
+
method="PUT",
|
| 366 |
+
headers={
|
| 367 |
+
**headers,
|
| 368 |
+
"Authorization": authorization,
|
| 369 |
+
"Content-Length": str(size),
|
| 370 |
+
},
|
| 371 |
+
)
|
| 372 |
+
try:
|
| 373 |
+
with urllib.request.urlopen(request, timeout=300):
|
| 374 |
+
pass
|
| 375 |
+
except urllib.error.HTTPError as error:
|
| 376 |
+
raise NotarizationError(
|
| 377 |
+
f"Apple notarization payload upload failed with HTTP {error.code}"
|
| 378 |
+
) from error
|
| 379 |
+
except OSError as error:
|
| 380 |
+
raise NotarizationError(
|
| 381 |
+
"Apple notarization payload upload failed"
|
| 382 |
+
) from error
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
def write_developer_log(
|
| 386 |
+
submission_id: str, token: str, destination: Path | None
|
| 387 |
+
) -> None:
|
| 388 |
+
"""Write the submission's optional diagnostic report."""
|
| 389 |
+
|
| 390 |
+
if destination is None:
|
| 391 |
+
return
|
| 392 |
+
response = json_request(f"{APPLE_NOTARY_URL}/{submission_id}/logs", token)
|
| 393 |
+
try:
|
| 394 |
+
log_url = response["data"]["attributes"]["developerLogUrl"]
|
| 395 |
+
except (KeyError, TypeError) as error:
|
| 396 |
+
raise NotarizationError(
|
| 397 |
+
"Apple did not provide a notarization developer log"
|
| 398 |
+
) from error
|
| 399 |
+
if not isinstance(log_url, str) or not log_url.startswith("https://"):
|
| 400 |
+
raise NotarizationError(
|
| 401 |
+
"Apple returned an invalid notarization developer log URL"
|
| 402 |
+
)
|
| 403 |
+
try:
|
| 404 |
+
# Do not forward unrelated authorization headers to the download URL.
|
| 405 |
+
with urllib.request.urlopen(log_url, timeout=60) as response:
|
| 406 |
+
destination.write_bytes(response.read())
|
| 407 |
+
except urllib.error.HTTPError as error:
|
| 408 |
+
raise NotarizationError(
|
| 409 |
+
f"Apple notarization developer log download failed with HTTP {error.code}"
|
| 410 |
+
) from error
|
| 411 |
+
except OSError as error:
|
| 412 |
+
raise NotarizationError(
|
| 413 |
+
"Apple notarization developer log download failed"
|
| 414 |
+
) from error
|
| 415 |
+
|
| 416 |
+
|
| 417 |
+
def notarize(
|
| 418 |
+
path: Path,
|
| 419 |
+
configuration: NotarizationConfiguration,
|
| 420 |
+
*,
|
| 421 |
+
max_wait_seconds: int,
|
| 422 |
+
report_log: Path | None,
|
| 423 |
+
) -> str:
|
| 424 |
+
"""Submit an artifact, upload it to Apple, and wait for a terminal result."""
|
| 425 |
+
|
| 426 |
+
issued_at = int(time.time())
|
| 427 |
+
token = create_apple_jwt(configuration, issued_at=issued_at)
|
| 428 |
+
file_digest = sha256_file(path)
|
| 429 |
+
# Create the submission before uploading the artifact.
|
| 430 |
+
response = json_request(
|
| 431 |
+
APPLE_NOTARY_URL,
|
| 432 |
+
token,
|
| 433 |
+
body={"sha256": file_digest, "submissionName": path.name},
|
| 434 |
+
)
|
| 435 |
+
try:
|
| 436 |
+
submission_id = response["data"]["id"]
|
| 437 |
+
upload_credentials = response["data"]["attributes"]
|
| 438 |
+
except (KeyError, TypeError) as error:
|
| 439 |
+
raise NotarizationError(
|
| 440 |
+
"Apple returned an invalid notarization submission"
|
| 441 |
+
) from error
|
| 442 |
+
if not isinstance(submission_id, str) or not submission_id:
|
| 443 |
+
raise NotarizationError("Apple did not return a notarization submission ID")
|
| 444 |
+
if not isinstance(upload_credentials, dict):
|
| 445 |
+
raise NotarizationError(
|
| 446 |
+
"Apple returned invalid notarization upload credentials"
|
| 447 |
+
)
|
| 448 |
+
|
| 449 |
+
print(f"Uploading notarization submission {submission_id} for {path.name}")
|
| 450 |
+
upload_to_apple_s3(path, file_digest, upload_credentials)
|
| 451 |
+
# A monotonic deadline keeps the wait limit stable if the system clock changes.
|
| 452 |
+
deadline = time.monotonic() + max_wait_seconds
|
| 453 |
+
|
| 454 |
+
# Wait for completion, equivalent to `notarytool submit --wait`.
|
| 455 |
+
while True:
|
| 456 |
+
# Refresh the token before it expires.
|
| 457 |
+
if int(time.time()) >= issued_at + JWT_LIFETIME_SECONDS - 60:
|
| 458 |
+
issued_at = int(time.time())
|
| 459 |
+
token = create_apple_jwt(configuration, issued_at=issued_at)
|
| 460 |
+
result = json_request(f"{APPLE_NOTARY_URL}/{submission_id}", token)
|
| 461 |
+
try:
|
| 462 |
+
status = result["data"]["attributes"]["status"]
|
| 463 |
+
except (KeyError, TypeError) as error:
|
| 464 |
+
raise NotarizationError(
|
| 465 |
+
"Apple returned an invalid notarization status"
|
| 466 |
+
) from error
|
| 467 |
+
|
| 468 |
+
if status in {"Accepted", "Invalid", "Rejected"}:
|
| 469 |
+
# Save diagnostics for both accepted and rejected submissions.
|
| 470 |
+
write_developer_log(submission_id, token, report_log)
|
| 471 |
+
message = f"Notarization submission {submission_id} completed with status {status}"
|
| 472 |
+
if status != "Accepted":
|
| 473 |
+
raise NotarizationError(message)
|
| 474 |
+
print(message)
|
| 475 |
+
return submission_id
|
| 476 |
+
if status != "In Progress":
|
| 477 |
+
raise NotarizationError(f"Unexpected notarization status: {status!r}")
|
| 478 |
+
|
| 479 |
+
remaining = deadline - time.monotonic()
|
| 480 |
+
if remaining <= 0:
|
| 481 |
+
raise NotarizationError(
|
| 482 |
+
f"Notarization submission {submission_id} exceeded {max_wait_seconds} seconds"
|
| 483 |
+
)
|
| 484 |
+
print(f"Notarization submission {submission_id} is still in progress")
|
| 485 |
+
time.sleep(min(POLL_INTERVAL_SECONDS, remaining))
|
| 486 |
+
|
| 487 |
+
|
| 488 |
+
def main() -> int:
|
| 489 |
+
"""Run notarization using command-line arguments."""
|
| 490 |
+
|
| 491 |
+
parser = argparse.ArgumentParser()
|
| 492 |
+
parser.add_argument("--file", type=Path, required=True)
|
| 493 |
+
parser.add_argument("--report-log", type=Path)
|
| 494 |
+
parser.add_argument("--max-wait-seconds", type=int, default=600)
|
| 495 |
+
arguments = parser.parse_args()
|
| 496 |
+
|
| 497 |
+
try:
|
| 498 |
+
if not arguments.file.is_file():
|
| 499 |
+
raise NotarizationError(
|
| 500 |
+
f"Notarization payload does not exist: {arguments.file}"
|
| 501 |
+
)
|
| 502 |
+
if arguments.max_wait_seconds < 0:
|
| 503 |
+
raise NotarizationError("--max-wait-seconds must be non-negative")
|
| 504 |
+
if arguments.report_log is not None:
|
| 505 |
+
arguments.report_log.parent.mkdir(parents=True, exist_ok=True)
|
| 506 |
+
notarize(
|
| 507 |
+
arguments.file,
|
| 508 |
+
NotarizationConfiguration.from_environment(),
|
| 509 |
+
max_wait_seconds=arguments.max_wait_seconds,
|
| 510 |
+
report_log=arguments.report_log,
|
| 511 |
+
)
|
| 512 |
+
except NotarizationError as error:
|
| 513 |
+
print(f"Notarization failed: {error}", file=sys.stderr)
|
| 514 |
+
return 1
|
| 515 |
+
return 0
|
| 516 |
+
|
| 517 |
+
|
| 518 |
+
if __name__ == "__main__":
|
| 519 |
+
raise SystemExit(main())
|
.github/scripts/macos-signing/provisioned_macos_cli_package.py
ADDED
|
@@ -0,0 +1,229 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Prepare and verify provisioned CLI bundles using an explicitly approved profile."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import plistlib
|
| 8 |
+
import re
|
| 9 |
+
import shutil
|
| 10 |
+
import subprocess
|
| 11 |
+
from dataclasses import dataclass
|
| 12 |
+
from datetime import datetime, timezone
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
BUNDLE_ID = "com.openai.codex.cli"
|
| 16 |
+
APP = Path("CodexCLI.app")
|
| 17 |
+
EXECUTABLE = APP / "Contents/MacOS/codex"
|
| 18 |
+
SIGNING = Path(__file__).resolve().parent
|
| 19 |
+
HELPERS = ("bin/codex-code-mode-host", "codex-path/rg", "codex-resources/zsh/bin/zsh")
|
| 20 |
+
LAUNCHER = """#!/bin/sh
|
| 21 |
+
set -eu
|
| 22 |
+
entry="$0"
|
| 23 |
+
while [ -L "$entry" ]; do
|
| 24 |
+
parent=$(CDPATH= cd -P -- "$(dirname -- "$entry")" && pwd)
|
| 25 |
+
entry=$(readlink "$entry")
|
| 26 |
+
case "$entry" in /*) ;; *) entry="$parent/$entry" ;; esac
|
| 27 |
+
done
|
| 28 |
+
bin_dir=$(CDPATH= cd -P -- "$(dirname -- "$entry")" && pwd)
|
| 29 |
+
exec "$bin_dir/../CodexCLI.app/Contents/MacOS/codex" "$@"
|
| 30 |
+
"""
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
@dataclass(frozen=True)
|
| 34 |
+
class ProfileConfiguration:
|
| 35 |
+
"""Independent release expectations; never infer these from the signed package."""
|
| 36 |
+
|
| 37 |
+
profile: Path
|
| 38 |
+
profile_sha256: str
|
| 39 |
+
certificate_sha256: str
|
| 40 |
+
team_id: str
|
| 41 |
+
|
| 42 |
+
def __post_init__(self):
|
| 43 |
+
for name in ("profile_sha256", "certificate_sha256"):
|
| 44 |
+
if not re.fullmatch(r"[0-9a-f]{64}", getattr(self, name)):
|
| 45 |
+
raise ValueError(f"{name} must be a lowercase SHA-256 digest")
|
| 46 |
+
if not re.fullmatch(r"[A-Z0-9]{10}", self.team_id):
|
| 47 |
+
raise ValueError("team_id must be a ten-character Apple Team ID")
|
| 48 |
+
|
| 49 |
+
def validate_signing_certificate(self, certificate: Path):
|
| 50 |
+
certificate_der = subprocess.check_output(
|
| 51 |
+
["openssl", "x509", "-in", str(certificate), "-outform", "DER"]
|
| 52 |
+
)
|
| 53 |
+
if hashlib.sha256(certificate_der).hexdigest() != self.certificate_sha256:
|
| 54 |
+
raise ValueError("AKV signing certificate is not authorized by the profile")
|
| 55 |
+
|
| 56 |
+
|
| 57 |
+
def load_profile(configuration: ProfileConfiguration):
|
| 58 |
+
# Pin the portal-approved CMS as well as checking its signature. -noverify
|
| 59 |
+
# skips OpenSSL's non-Apple trust store, not CMS signature verification.
|
| 60 |
+
if (
|
| 61 |
+
hashlib.sha256(configuration.profile.read_bytes()).hexdigest()
|
| 62 |
+
!= configuration.profile_sha256
|
| 63 |
+
):
|
| 64 |
+
raise ValueError("Provisioning profile changed; review and update its pin")
|
| 65 |
+
profile = plistlib.loads(
|
| 66 |
+
subprocess.check_output(
|
| 67 |
+
[
|
| 68 |
+
"openssl",
|
| 69 |
+
"cms",
|
| 70 |
+
"-verify",
|
| 71 |
+
"-inform",
|
| 72 |
+
"DER",
|
| 73 |
+
"-noverify",
|
| 74 |
+
"-in",
|
| 75 |
+
str(configuration.profile),
|
| 76 |
+
]
|
| 77 |
+
)
|
| 78 |
+
)
|
| 79 |
+
validate_profile(profile, configuration)
|
| 80 |
+
return profile
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
def validate_profile(profile, configuration: ProfileConfiguration):
|
| 84 |
+
allowed = profile["Entitlements"]
|
| 85 |
+
if (
|
| 86 |
+
profile["TeamIdentifier"] != [configuration.team_id]
|
| 87 |
+
or profile["ApplicationIdentifierPrefix"] != [configuration.team_id]
|
| 88 |
+
or allowed["com.apple.application-identifier"]
|
| 89 |
+
!= f"{configuration.team_id}.{BUNDLE_ID}"
|
| 90 |
+
or allowed["com.apple.developer.team-identifier"] != configuration.team_id
|
| 91 |
+
or allowed["keychain-access-groups"] != [f"{configuration.team_id}.*"]
|
| 92 |
+
or allowed.get("get-task-allow", False)
|
| 93 |
+
or not profile.get("ProvisionsAllDevices")
|
| 94 |
+
or "ProvisionedDevices" in profile
|
| 95 |
+
):
|
| 96 |
+
raise ValueError("Profile does not authorize the CLI Developer ID identity")
|
| 97 |
+
now = datetime.now(timezone.utc).replace(tzinfo=None)
|
| 98 |
+
if not profile["CreationDate"] <= now < profile["ExpirationDate"]:
|
| 99 |
+
raise ValueError("Provisioning profile is not currently valid")
|
| 100 |
+
if [
|
| 101 |
+
hashlib.sha256(cert).hexdigest() for cert in profile["DeveloperCertificates"]
|
| 102 |
+
] != [configuration.certificate_sha256]:
|
| 103 |
+
raise ValueError("Profile does not authorize the approved signing certificate")
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def entitlements(configuration: ProfileConfiguration):
|
| 107 |
+
base = plistlib.loads((SIGNING / "codex.entitlements.plist").read_bytes())
|
| 108 |
+
return {
|
| 109 |
+
**base,
|
| 110 |
+
"com.apple.application-identifier": f"{configuration.team_id}.{BUNDLE_ID}",
|
| 111 |
+
"com.apple.developer.team-identifier": configuration.team_id,
|
| 112 |
+
"keychain-access-groups": [f"{configuration.team_id}.{BUNDLE_ID}"],
|
| 113 |
+
}
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def prepare(package, reports, configuration: ProfileConfiguration):
|
| 117 |
+
load_profile(configuration)
|
| 118 |
+
metadata = json.loads((package / "codex-package.json").read_text())
|
| 119 |
+
if (
|
| 120 |
+
metadata["variant"] != "codex"
|
| 121 |
+
or metadata["layoutVersion"] != 1
|
| 122 |
+
or metadata["target"] not in ("aarch64-apple-darwin", "x86_64-apple-darwin")
|
| 123 |
+
or metadata["entrypoint"] != "bin/codex"
|
| 124 |
+
):
|
| 125 |
+
raise ValueError("Expected a canonical macOS CLI package")
|
| 126 |
+
for relative in ("bin/codex", *HELPERS):
|
| 127 |
+
path = package / relative
|
| 128 |
+
if path.is_symlink() or not path.is_file() or not os.access(path, os.X_OK):
|
| 129 |
+
raise ValueError(f"Expected a regular executable: {relative}")
|
| 130 |
+
# Refuse to overwrite a previously prepared or signed bundle.
|
| 131 |
+
(package / EXECUTABLE).parent.mkdir(parents=True, exist_ok=False)
|
| 132 |
+
(package / "bin/codex").rename(package / EXECUTABLE)
|
| 133 |
+
(package / "bin/codex").write_text(LAUNCHER)
|
| 134 |
+
(package / "bin/codex").chmod(0o755)
|
| 135 |
+
contents = package / APP / "Contents"
|
| 136 |
+
shutil.copyfile(configuration.profile, contents / "embedded.provisionprofile")
|
| 137 |
+
(contents / "Info.plist").write_bytes(
|
| 138 |
+
plistlib.dumps(
|
| 139 |
+
{
|
| 140 |
+
"CFBundleIdentifier": BUNDLE_ID,
|
| 141 |
+
"CFBundleExecutable": "codex",
|
| 142 |
+
"CFBundleName": "Codex CLI",
|
| 143 |
+
"CFBundlePackageType": "APPL",
|
| 144 |
+
"CFBundleVersion": "1",
|
| 145 |
+
}
|
| 146 |
+
)
|
| 147 |
+
)
|
| 148 |
+
reports.mkdir(parents=True, exist_ok=True)
|
| 149 |
+
# Keep expected entitlements separate from the shared verifier's extracted
|
| 150 |
+
# codex-entitlements.plist, otherwise it would overwrite its own expectation.
|
| 151 |
+
(reports / "codex-provisioned-entitlements.plist").write_bytes(
|
| 152 |
+
plistlib.dumps(entitlements(configuration))
|
| 153 |
+
)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def verify(package, reports, expected_target, configuration: ProfileConfiguration):
|
| 157 |
+
"""Check profile and certificate pins; the signing driver verifies code."""
|
| 158 |
+
load_profile(configuration)
|
| 159 |
+
if (
|
| 160 |
+
package / APP / "Contents/embedded.provisionprofile"
|
| 161 |
+
).read_bytes() != configuration.profile.read_bytes():
|
| 162 |
+
raise ValueError("Embedded profile differs from the reviewed profile")
|
| 163 |
+
if (package / "bin/codex").read_text() != LAUNCHER:
|
| 164 |
+
raise ValueError("Unexpected CLI launcher")
|
| 165 |
+
metadata = json.loads((package / "codex-package.json").read_text())
|
| 166 |
+
if metadata["target"] != expected_target:
|
| 167 |
+
raise ValueError(
|
| 168 |
+
"Package target differs from the requested verification target"
|
| 169 |
+
)
|
| 170 |
+
reports.mkdir(parents=True, exist_ok=True)
|
| 171 |
+
(reports / "codex-provisioned-entitlements.plist").write_bytes(
|
| 172 |
+
plistlib.dumps(entitlements(configuration))
|
| 173 |
+
)
|
| 174 |
+
# sign_macos_cli_package.py owns shared architecture, signature and entitlement
|
| 175 |
+
# checks. Here, require the exact certificate authorized by the profile.
|
| 176 |
+
for relative in (EXECUTABLE, *(Path(helper) for helper in HELPERS)):
|
| 177 |
+
binary = package / relative
|
| 178 |
+
target = package / APP if relative == EXECUTABLE else binary
|
| 179 |
+
prefix = reports / f"{binary.name}-cert-"
|
| 180 |
+
subprocess.run(
|
| 181 |
+
["codesign", "-d", f"--extract-certificates={prefix}", str(target)],
|
| 182 |
+
check=True,
|
| 183 |
+
)
|
| 184 |
+
if (
|
| 185 |
+
hashlib.sha256(Path(f"{prefix}0").read_bytes()).hexdigest()
|
| 186 |
+
!= configuration.certificate_sha256
|
| 187 |
+
):
|
| 188 |
+
raise ValueError(f"Unexpected signing certificate: {relative}")
|
| 189 |
+
|
| 190 |
+
|
| 191 |
+
def parse_profile_args(parser, configuration: ProfileConfiguration | None):
|
| 192 |
+
"""Require independent profile expectations unless the caller supplies them."""
|
| 193 |
+
if configuration is None:
|
| 194 |
+
parser.add_argument("--profile", type=Path, required=True)
|
| 195 |
+
parser.add_argument("--profile-sha256", required=True)
|
| 196 |
+
parser.add_argument("--certificate-sha256", required=True)
|
| 197 |
+
parser.add_argument("--team-id", required=True)
|
| 198 |
+
args = parser.parse_args()
|
| 199 |
+
if configuration is None:
|
| 200 |
+
configuration = ProfileConfiguration(
|
| 201 |
+
args.profile, args.profile_sha256, args.certificate_sha256, args.team_id
|
| 202 |
+
)
|
| 203 |
+
return args, configuration
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
def main(configuration: ProfileConfiguration | None = None):
|
| 207 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 208 |
+
parser.add_argument("operation", choices=("prepare", "verify", "validate-profile"))
|
| 209 |
+
parser.add_argument("--package", type=Path, default=Path("package"))
|
| 210 |
+
parser.add_argument("--reports", type=Path, default=Path("provisioned-reports"))
|
| 211 |
+
parser.add_argument("--certificate", type=Path)
|
| 212 |
+
parser.add_argument(
|
| 213 |
+
"--target", choices=("aarch64-apple-darwin", "x86_64-apple-darwin")
|
| 214 |
+
)
|
| 215 |
+
args, configuration = parse_profile_args(parser, configuration)
|
| 216 |
+
if args.certificate:
|
| 217 |
+
configuration.validate_signing_certificate(args.certificate)
|
| 218 |
+
if args.operation == "validate-profile":
|
| 219 |
+
load_profile(configuration)
|
| 220 |
+
elif args.operation == "prepare":
|
| 221 |
+
prepare(args.package, args.reports, configuration)
|
| 222 |
+
else:
|
| 223 |
+
if not args.target:
|
| 224 |
+
parser.error("--target is required for verification")
|
| 225 |
+
verify(args.package, args.reports, args.target, configuration)
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
if __name__ == "__main__":
|
| 229 |
+
main()
|
.github/scripts/macos-signing/sign_macos_cli_package.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Sign and verify macOS CLI packages using the shared AKV and native tools."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import os
|
| 5 |
+
import subprocess
|
| 6 |
+
import sys
|
| 7 |
+
from pathlib import Path
|
| 8 |
+
|
| 9 |
+
import provisioned_macos_cli_package as bundle
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def main(configuration: bundle.ProfileConfiguration | None = None):
|
| 13 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 14 |
+
parser.add_argument("operation", choices=("sign", "verify"))
|
| 15 |
+
parser.add_argument("package", type=Path)
|
| 16 |
+
args, configuration = bundle.parse_profile_args(parser, configuration)
|
| 17 |
+
package = args.package.resolve(strict=True)
|
| 18 |
+
if not package.is_dir():
|
| 19 |
+
parser.error("package must be an extracted CLI package directory")
|
| 20 |
+
repo_root = os.environ.get("CODEX_REPO_ROOT")
|
| 21 |
+
if not repo_root:
|
| 22 |
+
parser.error("CODEX_REPO_ROOT must be set")
|
| 23 |
+
reports = Path(repo_root) / "signing-verification"
|
| 24 |
+
reports.mkdir(parents=True, exist_ok=True)
|
| 25 |
+
provisioned = os.environ.get("PROVISIONED_MACOS", "false") == "true"
|
| 26 |
+
if provisioned:
|
| 27 |
+
if args.operation == "sign":
|
| 28 |
+
configuration.validate_signing_certificate(
|
| 29 |
+
Path(os.environ["OAI_AKV_SIGNING_CERTIFICATE_PEM"])
|
| 30 |
+
)
|
| 31 |
+
bundle.prepare(package, reports, configuration)
|
| 32 |
+
else:
|
| 33 |
+
bundle.verify(package, reports, os.environ["TARGET"], configuration)
|
| 34 |
+
|
| 35 |
+
for relative in ("bin/codex", *bundle.HELPERS):
|
| 36 |
+
binary = package / relative
|
| 37 |
+
name = binary.name
|
| 38 |
+
target = binary
|
| 39 |
+
identifier = name
|
| 40 |
+
entitlements = bundle.SIGNING / f"{name}.entitlements.plist"
|
| 41 |
+
if provisioned and relative == "bin/codex":
|
| 42 |
+
binary = package / bundle.EXECUTABLE
|
| 43 |
+
target = package / bundle.APP
|
| 44 |
+
identifier = bundle.BUNDLE_ID
|
| 45 |
+
entitlements = reports / "codex-provisioned-entitlements.plist"
|
| 46 |
+
if args.operation == "sign":
|
| 47 |
+
command = [
|
| 48 |
+
"bash",
|
| 49 |
+
str(bundle.SIGNING / "sign_macos_code.sh"),
|
| 50 |
+
"--target",
|
| 51 |
+
str(target),
|
| 52 |
+
"--identity",
|
| 53 |
+
"unused",
|
| 54 |
+
"--deep",
|
| 55 |
+
"false",
|
| 56 |
+
"--options",
|
| 57 |
+
"runtime",
|
| 58 |
+
"--timestamp",
|
| 59 |
+
"true",
|
| 60 |
+
]
|
| 61 |
+
if relative.startswith("bin/"):
|
| 62 |
+
command.extend(
|
| 63 |
+
["--identifier", identifier, "--entitlements", str(entitlements)]
|
| 64 |
+
)
|
| 65 |
+
else:
|
| 66 |
+
command.extend(["--identifier", f"com.openai.codex.{name}"])
|
| 67 |
+
subprocess.run(command, check=True)
|
| 68 |
+
with (reports / f"{name}-signature.yaml").open("wb") as output:
|
| 69 |
+
subprocess.run(
|
| 70 |
+
["rcodesign", "print-signature-info", str(binary)],
|
| 71 |
+
stdout=output,
|
| 72 |
+
check=True,
|
| 73 |
+
)
|
| 74 |
+
if not provisioned:
|
| 75 |
+
subprocess.run(
|
| 76 |
+
[
|
| 77 |
+
"bash",
|
| 78 |
+
str(bundle.SIGNING / "notarize_macos_binary_with_akv.sh"),
|
| 79 |
+
"--binary",
|
| 80 |
+
str(binary),
|
| 81 |
+
"--report-dir",
|
| 82 |
+
str(reports / name),
|
| 83 |
+
],
|
| 84 |
+
check=True,
|
| 85 |
+
)
|
| 86 |
+
else:
|
| 87 |
+
target_triple = os.environ["TARGET"]
|
| 88 |
+
architectures = {
|
| 89 |
+
"aarch64-apple-darwin": "arm64",
|
| 90 |
+
"x86_64-apple-darwin": "x86_64",
|
| 91 |
+
}
|
| 92 |
+
if target_triple not in architectures:
|
| 93 |
+
raise ValueError(f"Unexpected macOS target: {target_triple}")
|
| 94 |
+
subprocess.run(
|
| 95 |
+
["lipo", str(binary), "-verify_arch", architectures[target_triple]],
|
| 96 |
+
check=True,
|
| 97 |
+
)
|
| 98 |
+
subprocess.run(
|
| 99 |
+
[
|
| 100 |
+
"codesign",
|
| 101 |
+
"--verify",
|
| 102 |
+
"--strict",
|
| 103 |
+
"--verbose=2",
|
| 104 |
+
"--test-requirement",
|
| 105 |
+
# Require Apple's Developer ID Application certificate chain.
|
| 106 |
+
(
|
| 107 |
+
"=anchor apple generic"
|
| 108 |
+
" and certificate 1[field.1.2.840.113635.100.6.2.6] exists"
|
| 109 |
+
" and certificate leaf[field.1.2.840.113635.100.6.1.13] exists"
|
| 110 |
+
),
|
| 111 |
+
str(target),
|
| 112 |
+
],
|
| 113 |
+
check=True,
|
| 114 |
+
)
|
| 115 |
+
signature = reports / f"{name}-signature.txt"
|
| 116 |
+
with signature.open("wb") as output:
|
| 117 |
+
subprocess.run(
|
| 118 |
+
["codesign", "-d", "--verbose=4", str(target)],
|
| 119 |
+
stderr=output,
|
| 120 |
+
check=True,
|
| 121 |
+
)
|
| 122 |
+
actual = reports / f"{name}-entitlements.plist"
|
| 123 |
+
with actual.open("wb") as output:
|
| 124 |
+
subprocess.run(
|
| 125 |
+
["codesign", "-d", "--entitlements", ":-", str(target)],
|
| 126 |
+
stdout=output,
|
| 127 |
+
check=True,
|
| 128 |
+
)
|
| 129 |
+
if relative.startswith("bin/"):
|
| 130 |
+
expected = reports / f"{name}-expected.plist"
|
| 131 |
+
subprocess.run(
|
| 132 |
+
[
|
| 133 |
+
"plutil",
|
| 134 |
+
"-convert",
|
| 135 |
+
"xml1",
|
| 136 |
+
"-o",
|
| 137 |
+
str(expected),
|
| 138 |
+
str(entitlements),
|
| 139 |
+
],
|
| 140 |
+
check=True,
|
| 141 |
+
)
|
| 142 |
+
subprocess.run(["plutil", "-convert", "xml1", str(actual)], check=True)
|
| 143 |
+
subprocess.run(["diff", "-u", str(expected), str(actual)], check=True)
|
| 144 |
+
elif actual.stat().st_size:
|
| 145 |
+
raise ValueError(
|
| 146 |
+
f"Bundled helper {name} must not have signing entitlements"
|
| 147 |
+
)
|
| 148 |
+
|
| 149 |
+
if provisioned and args.operation == "sign":
|
| 150 |
+
archive = Path(os.environ["RUNNER_TEMP"]) / "provisioned-cli.zip"
|
| 151 |
+
subprocess.run(["zip", "-q", "-r", str(archive), "."], cwd=package, check=True)
|
| 152 |
+
command = [
|
| 153 |
+
sys.executable,
|
| 154 |
+
str(bundle.SIGNING / "notarize_with_akv.py"),
|
| 155 |
+
"--file",
|
| 156 |
+
str(archive),
|
| 157 |
+
"--report-log",
|
| 158 |
+
str(reports / "notarization-developer-log.json"),
|
| 159 |
+
"--max-wait-seconds",
|
| 160 |
+
"1200",
|
| 161 |
+
]
|
| 162 |
+
with (
|
| 163 |
+
(reports / "notarization.log").open("w") as output,
|
| 164 |
+
subprocess.Popen(
|
| 165 |
+
command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True
|
| 166 |
+
) as process,
|
| 167 |
+
):
|
| 168 |
+
assert process.stdout is not None
|
| 169 |
+
for line in process.stdout:
|
| 170 |
+
print(line, end="", flush=True)
|
| 171 |
+
output.write(line)
|
| 172 |
+
if returncode := process.wait():
|
| 173 |
+
raise subprocess.CalledProcessError(returncode, command)
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
if __name__ == "__main__":
|
| 177 |
+
main()
|
.github/scripts/macos-signing/sign_macos_code.sh
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
|
| 3 |
+
# Small compatibility wrapper around native codesign and rcodesign.
|
| 4 |
+
#
|
| 5 |
+
# Existing packaging scripts call this instead of choosing a signing backend
|
| 6 |
+
# directly. OAI_CODESIGN_BACKEND=akv-pkcs11 routes signing through rcodesign
|
| 7 |
+
# while preserving the option, entitlement, identifier, timestamp, and deep
|
| 8 |
+
# signing surface used by the native codesign path.
|
| 9 |
+
|
| 10 |
+
set -euo pipefail
|
| 11 |
+
|
| 12 |
+
usage() {
|
| 13 |
+
cat >&2 <<'EOF'
|
| 14 |
+
Usage: sign_macos_code.sh --target PATH --identity IDENTITY [options]
|
| 15 |
+
|
| 16 |
+
Options:
|
| 17 |
+
--deep true|false
|
| 18 |
+
--entitlements PATH
|
| 19 |
+
--identifier IDENTIFIER
|
| 20 |
+
--identity IDENTITY
|
| 21 |
+
--options FLAGS
|
| 22 |
+
--target PATH
|
| 23 |
+
--timestamp true|false|none
|
| 24 |
+
EOF
|
| 25 |
+
}
|
| 26 |
+
|
| 27 |
+
target=""
|
| 28 |
+
identity=""
|
| 29 |
+
options=""
|
| 30 |
+
entitlements_file=""
|
| 31 |
+
identifier=""
|
| 32 |
+
deep="false"
|
| 33 |
+
timestamp="true"
|
| 34 |
+
|
| 35 |
+
while [[ $# -gt 0 ]]; do
|
| 36 |
+
case "$1" in
|
| 37 |
+
--deep)
|
| 38 |
+
deep="${2:-}"
|
| 39 |
+
shift 2
|
| 40 |
+
;;
|
| 41 |
+
--entitlements)
|
| 42 |
+
entitlements_file="${2:-}"
|
| 43 |
+
shift 2
|
| 44 |
+
;;
|
| 45 |
+
--identifier)
|
| 46 |
+
identifier="${2:-}"
|
| 47 |
+
shift 2
|
| 48 |
+
;;
|
| 49 |
+
--identity)
|
| 50 |
+
identity="${2:-}"
|
| 51 |
+
shift 2
|
| 52 |
+
;;
|
| 53 |
+
--options)
|
| 54 |
+
options="${2:-}"
|
| 55 |
+
shift 2
|
| 56 |
+
;;
|
| 57 |
+
--target)
|
| 58 |
+
target="${2:-}"
|
| 59 |
+
shift 2
|
| 60 |
+
;;
|
| 61 |
+
--timestamp)
|
| 62 |
+
timestamp="${2:-}"
|
| 63 |
+
shift 2
|
| 64 |
+
;;
|
| 65 |
+
-h|--help)
|
| 66 |
+
usage
|
| 67 |
+
exit 0
|
| 68 |
+
;;
|
| 69 |
+
*)
|
| 70 |
+
echo "Unknown signing argument: $1" >&2
|
| 71 |
+
usage
|
| 72 |
+
exit 2
|
| 73 |
+
;;
|
| 74 |
+
esac
|
| 75 |
+
done
|
| 76 |
+
|
| 77 |
+
if [[ -z "$target" ]]; then
|
| 78 |
+
echo "--target is required." >&2
|
| 79 |
+
usage
|
| 80 |
+
exit 2
|
| 81 |
+
fi
|
| 82 |
+
|
| 83 |
+
if [[ ! -e "$target" ]]; then
|
| 84 |
+
echo "Signing target does not exist: $target" >&2
|
| 85 |
+
exit 1
|
| 86 |
+
fi
|
| 87 |
+
|
| 88 |
+
case "$deep" in
|
| 89 |
+
true|false) ;;
|
| 90 |
+
*)
|
| 91 |
+
echo "--deep must be true or false, got '$deep'." >&2
|
| 92 |
+
exit 2
|
| 93 |
+
;;
|
| 94 |
+
esac
|
| 95 |
+
|
| 96 |
+
case "$timestamp" in
|
| 97 |
+
true|false|none) ;;
|
| 98 |
+
*)
|
| 99 |
+
echo "--timestamp must be true, false, or none, got '$timestamp'." >&2
|
| 100 |
+
exit 2
|
| 101 |
+
;;
|
| 102 |
+
esac
|
| 103 |
+
|
| 104 |
+
sign_with_codesign() {
|
| 105 |
+
if [[ -z "$identity" ]]; then
|
| 106 |
+
echo "Native codesign requires --identity." >&2
|
| 107 |
+
exit 2
|
| 108 |
+
fi
|
| 109 |
+
|
| 110 |
+
local -a args
|
| 111 |
+
args=(--force)
|
| 112 |
+
|
| 113 |
+
if [[ "$deep" == "true" ]]; then
|
| 114 |
+
args+=(--deep)
|
| 115 |
+
fi
|
| 116 |
+
|
| 117 |
+
if [[ -n "$options" ]]; then
|
| 118 |
+
args+=(--options "$options")
|
| 119 |
+
fi
|
| 120 |
+
|
| 121 |
+
case "$timestamp" in
|
| 122 |
+
true)
|
| 123 |
+
args+=(--timestamp)
|
| 124 |
+
;;
|
| 125 |
+
false|none)
|
| 126 |
+
args+=(--timestamp=none)
|
| 127 |
+
;;
|
| 128 |
+
esac
|
| 129 |
+
|
| 130 |
+
if [[ -n "$entitlements_file" ]]; then
|
| 131 |
+
args+=(--entitlements "$entitlements_file")
|
| 132 |
+
fi
|
| 133 |
+
|
| 134 |
+
if [[ -n "$identifier" ]]; then
|
| 135 |
+
args+=(--identifier "$identifier")
|
| 136 |
+
fi
|
| 137 |
+
|
| 138 |
+
args+=(--sign "$identity" "$target")
|
| 139 |
+
codesign "${args[@]}"
|
| 140 |
+
}
|
| 141 |
+
|
| 142 |
+
append_rcodesign_flags() {
|
| 143 |
+
local raw_options="$1"
|
| 144 |
+
local option=""
|
| 145 |
+
|
| 146 |
+
if [[ -z "$raw_options" ]]; then
|
| 147 |
+
return 0
|
| 148 |
+
fi
|
| 149 |
+
|
| 150 |
+
IFS=',' read -ra split_options <<< "$raw_options"
|
| 151 |
+
for option in "${split_options[@]}"; do
|
| 152 |
+
option="${option//[[:space:]]/}"
|
| 153 |
+
[[ -z "$option" ]] && continue
|
| 154 |
+
|
| 155 |
+
case "$option" in
|
| 156 |
+
host|hard|kill|expires|restrict|library|runtime|linker-signed)
|
| 157 |
+
rcodesign_args+=(--code-signature-flags "$option")
|
| 158 |
+
;;
|
| 159 |
+
*)
|
| 160 |
+
echo "Unsupported rcodesign code signature option: $option" >&2
|
| 161 |
+
exit 2
|
| 162 |
+
;;
|
| 163 |
+
esac
|
| 164 |
+
done
|
| 165 |
+
}
|
| 166 |
+
|
| 167 |
+
rcodesign_options_require_notarization() {
|
| 168 |
+
local raw_options="$1"
|
| 169 |
+
local option=""
|
| 170 |
+
|
| 171 |
+
if [[ -z "$raw_options" || "$timestamp" != "true" ]]; then
|
| 172 |
+
return 1
|
| 173 |
+
fi
|
| 174 |
+
|
| 175 |
+
IFS=',' read -ra split_options <<< "$raw_options"
|
| 176 |
+
for option in "${split_options[@]}"; do
|
| 177 |
+
option="${option//[[:space:]]/}"
|
| 178 |
+
if [[ "$option" == "runtime" ]]; then
|
| 179 |
+
return 0
|
| 180 |
+
fi
|
| 181 |
+
done
|
| 182 |
+
|
| 183 |
+
return 1
|
| 184 |
+
}
|
| 185 |
+
|
| 186 |
+
sign_with_rcodesign() {
|
| 187 |
+
: "${OAI_AKV_PKCS11_LIBRARY:?OAI_AKV_PKCS11_LIBRARY is required for AKV PKCS11 signing.}"
|
| 188 |
+
: "${OAI_AKV_SIGNING_CERTIFICATE_PEM:?OAI_AKV_SIGNING_CERTIFICATE_PEM is required for AKV PKCS11 signing.}"
|
| 189 |
+
: "${OAI_AKV_KEY_LABEL:?OAI_AKV_KEY_LABEL is required for AKV PKCS11 signing.}"
|
| 190 |
+
|
| 191 |
+
if ! command -v rcodesign >/dev/null 2>&1; then
|
| 192 |
+
echo "rcodesign was not found on PATH." >&2
|
| 193 |
+
exit 1
|
| 194 |
+
fi
|
| 195 |
+
|
| 196 |
+
local -a rcodesign_args
|
| 197 |
+
rcodesign_args=(
|
| 198 |
+
sign
|
| 199 |
+
--config-file /dev/null
|
| 200 |
+
--pkcs11-library "$OAI_AKV_PKCS11_LIBRARY"
|
| 201 |
+
--pkcs11-certificate-file "$OAI_AKV_SIGNING_CERTIFICATE_PEM"
|
| 202 |
+
--pkcs11-key-label "$OAI_AKV_KEY_LABEL"
|
| 203 |
+
)
|
| 204 |
+
|
| 205 |
+
if [[ "$deep" == "false" ]]; then
|
| 206 |
+
rcodesign_args+=(--shallow)
|
| 207 |
+
fi
|
| 208 |
+
|
| 209 |
+
case "$timestamp" in
|
| 210 |
+
true)
|
| 211 |
+
;;
|
| 212 |
+
false|none)
|
| 213 |
+
rcodesign_args+=(--timestamp-url none)
|
| 214 |
+
;;
|
| 215 |
+
esac
|
| 216 |
+
|
| 217 |
+
append_rcodesign_flags "$options"
|
| 218 |
+
if rcodesign_options_require_notarization "$options"; then
|
| 219 |
+
rcodesign_args+=(--for-notarization)
|
| 220 |
+
fi
|
| 221 |
+
|
| 222 |
+
if [[ -n "$entitlements_file" ]]; then
|
| 223 |
+
rcodesign_args+=(--entitlements-xml-file "$entitlements_file")
|
| 224 |
+
fi
|
| 225 |
+
|
| 226 |
+
if [[ -n "$identifier" ]]; then
|
| 227 |
+
rcodesign_args+=(--binary-identifier "$identifier")
|
| 228 |
+
fi
|
| 229 |
+
|
| 230 |
+
rcodesign_args+=("$target")
|
| 231 |
+
rcodesign "${rcodesign_args[@]}"
|
| 232 |
+
}
|
| 233 |
+
|
| 234 |
+
case "${OAI_CODESIGN_BACKEND:-codesign}" in
|
| 235 |
+
codesign|"")
|
| 236 |
+
sign_with_codesign
|
| 237 |
+
;;
|
| 238 |
+
akv-pkcs11)
|
| 239 |
+
sign_with_rcodesign
|
| 240 |
+
;;
|
| 241 |
+
*)
|
| 242 |
+
echo "Unsupported OAI_CODESIGN_BACKEND: ${OAI_CODESIGN_BACKEND}" >&2
|
| 243 |
+
exit 2
|
| 244 |
+
;;
|
| 245 |
+
esac
|
.github/scripts/macos-signing/test_notarize_with_akv.py
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import contextlib
|
| 2 |
+
import hashlib
|
| 3 |
+
import io
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import subprocess
|
| 7 |
+
import sys
|
| 8 |
+
import tempfile
|
| 9 |
+
import unittest
|
| 10 |
+
from pathlib import Path
|
| 11 |
+
from unittest.mock import patch
|
| 12 |
+
|
| 13 |
+
SIGNING_DIRECTORY = Path(__file__).resolve().parent
|
| 14 |
+
sys.path.insert(0, str(SIGNING_DIRECTORY))
|
| 15 |
+
import notarize_with_akv as notary # noqa: E402
|
| 16 |
+
|
| 17 |
+
CONFIGURATION = notary.NotarizationConfiguration(
|
| 18 |
+
"01234567-89ab-cdef-0123-456789abcdef",
|
| 19 |
+
"APPLEKEY01",
|
| 20 |
+
"notary-vault",
|
| 21 |
+
"example-signing-key",
|
| 22 |
+
"0123456789abcdef" * 2,
|
| 23 |
+
)
|
| 24 |
+
ENVIRONMENT = {
|
| 25 |
+
"AZURE_KEYVAULT_NAME": CONFIGURATION.vault_name,
|
| 26 |
+
"APPLE_NOTARIZATION_AKV_KEY_NAME": CONFIGURATION.vault_key_name,
|
| 27 |
+
}
|
| 28 |
+
CREDENTIALS = {
|
| 29 |
+
"awsAccessKeyId": "temporary-access-key",
|
| 30 |
+
"awsSecretAccessKey": "temporary-secret-key",
|
| 31 |
+
"awsSessionToken": "temporary-session-token",
|
| 32 |
+
"bucket": "apple-notary-bucket",
|
| 33 |
+
"object": "uploads/release artifact.dmg",
|
| 34 |
+
}
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
def response(payload):
|
| 38 |
+
return io.BytesIO(
|
| 39 |
+
json.dumps(payload).encode() if isinstance(payload, dict) else payload
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def azure_response(payload):
|
| 44 |
+
return subprocess.CompletedProcess([], 0, stdout=json.dumps(payload), stderr="")
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
class NotarizationTest(unittest.TestCase):
|
| 48 |
+
def test_validates_the_apple_key_tags_and_pins_its_version(self) -> None:
|
| 49 |
+
metadata = {
|
| 50 |
+
"id": CONFIGURATION.versioned_key_id,
|
| 51 |
+
"apple_key_id": "APPLEKEY01",
|
| 52 |
+
"apple_issuer_id": CONFIGURATION.issuer_id,
|
| 53 |
+
}
|
| 54 |
+
with (
|
| 55 |
+
patch.dict(os.environ, ENVIRONMENT, clear=True),
|
| 56 |
+
patch.object(
|
| 57 |
+
notary.subprocess,
|
| 58 |
+
"run",
|
| 59 |
+
return_value=azure_response(metadata),
|
| 60 |
+
) as show,
|
| 61 |
+
):
|
| 62 |
+
self.assertEqual(
|
| 63 |
+
notary.NotarizationConfiguration.from_environment(),
|
| 64 |
+
CONFIGURATION,
|
| 65 |
+
)
|
| 66 |
+
show.return_value = azure_response({**metadata, "apple_key_id": None})
|
| 67 |
+
with self.assertRaisesRegex(notary.NotarizationError, "apple-key-id tag"):
|
| 68 |
+
notary.NotarizationConfiguration.from_environment()
|
| 69 |
+
for issuer_id in (None, "", " "):
|
| 70 |
+
with self.subTest(issuer_id=issuer_id):
|
| 71 |
+
show.return_value = azure_response(
|
| 72 |
+
{**metadata, "apple_issuer_id": issuer_id}
|
| 73 |
+
)
|
| 74 |
+
with self.assertRaisesRegex(
|
| 75 |
+
notary.NotarizationError, "apple-issuer-id tag"
|
| 76 |
+
):
|
| 77 |
+
notary.NotarizationConfiguration.from_environment()
|
| 78 |
+
show.return_value = azure_response(
|
| 79 |
+
{**metadata, "apple_issuer_id": "invalid-issuer"}
|
| 80 |
+
)
|
| 81 |
+
with self.assertRaisesRegex(notary.NotarizationError, "valid UUID"):
|
| 82 |
+
notary.NotarizationConfiguration.from_environment()
|
| 83 |
+
self.assertIn(
|
| 84 |
+
'{id:key.kid,apple_key_id:tags."apple-key-id",'
|
| 85 |
+
'apple_issuer_id:tags."apple-issuer-id"}',
|
| 86 |
+
show.call_args.args[0],
|
| 87 |
+
)
|
| 88 |
+
|
| 89 |
+
invalid_environment = {
|
| 90 |
+
**ENVIRONMENT,
|
| 91 |
+
"APPLE_NOTARIZATION_AKV_KEY_NAME": "../bad",
|
| 92 |
+
}
|
| 93 |
+
with (
|
| 94 |
+
patch.dict(os.environ, invalid_environment, clear=True),
|
| 95 |
+
self.assertRaisesRegex(notary.NotarizationError, "key name"),
|
| 96 |
+
):
|
| 97 |
+
notary.NotarizationConfiguration.from_environment()
|
| 98 |
+
|
| 99 |
+
def test_signs_the_notary_scoped_digest_with_the_pinned_key(self) -> None:
|
| 100 |
+
signature = bytes(range(64))
|
| 101 |
+
payload = {
|
| 102 |
+
"kid": CONFIGURATION.versioned_key_id,
|
| 103 |
+
"value": notary.base64url_encode(signature),
|
| 104 |
+
}
|
| 105 |
+
with patch.object(
|
| 106 |
+
notary.subprocess, "run", return_value=azure_response(payload)
|
| 107 |
+
) as sign:
|
| 108 |
+
token = notary.create_apple_jwt(CONFIGURATION, issued_at=1_780_000_000)
|
| 109 |
+
header, claims, encoded_signature = token.split(".")
|
| 110 |
+
self.assertEqual(
|
| 111 |
+
json.loads(notary.base64url_decode(header))["kid"], "APPLEKEY01"
|
| 112 |
+
)
|
| 113 |
+
self.assertEqual(
|
| 114 |
+
json.loads(notary.base64url_decode(claims))["iss"], CONFIGURATION.issuer_id
|
| 115 |
+
)
|
| 116 |
+
self.assertEqual(
|
| 117 |
+
json.loads(notary.base64url_decode(claims))["scope"],
|
| 118 |
+
["/notary/v2"],
|
| 119 |
+
)
|
| 120 |
+
self.assertEqual(notary.base64url_decode(encoded_signature), signature)
|
| 121 |
+
command = sign.call_args.args[0]
|
| 122 |
+
body = json.loads(command[command.index("--body") + 1])
|
| 123 |
+
self.assertEqual(body["alg"], "ES256")
|
| 124 |
+
self.assertEqual(
|
| 125 |
+
notary.base64url_decode(body["value"]),
|
| 126 |
+
hashlib.sha256(f"{header}.{claims}".encode()).digest(),
|
| 127 |
+
)
|
| 128 |
+
|
| 129 |
+
def test_rejects_wrong_key_versions_and_invalid_signatures(self) -> None:
|
| 130 |
+
cases = (
|
| 131 |
+
(
|
| 132 |
+
CONFIGURATION.versioned_key_id + "wrong",
|
| 133 |
+
bytes(64),
|
| 134 |
+
"unexpected key version",
|
| 135 |
+
),
|
| 136 |
+
(CONFIGURATION.versioned_key_id, b"short", "64 JOSE"),
|
| 137 |
+
)
|
| 138 |
+
for key_id, signature, message in cases:
|
| 139 |
+
payload = {"kid": key_id, "value": notary.base64url_encode(signature)}
|
| 140 |
+
with (
|
| 141 |
+
self.subTest(message=message),
|
| 142 |
+
patch.object(
|
| 143 |
+
notary.subprocess,
|
| 144 |
+
"run",
|
| 145 |
+
return_value=azure_response(payload),
|
| 146 |
+
),
|
| 147 |
+
self.assertRaisesRegex(notary.NotarizationError, message),
|
| 148 |
+
):
|
| 149 |
+
notary.create_apple_jwt(CONFIGURATION, issued_at=1)
|
| 150 |
+
|
| 151 |
+
def test_uploads_polls_and_retains_sanitized_apple_diagnostics(self) -> None:
|
| 152 |
+
signature = azure_response(
|
| 153 |
+
{
|
| 154 |
+
"kid": CONFIGURATION.versioned_key_id,
|
| 155 |
+
"value": notary.base64url_encode(bytes(64)),
|
| 156 |
+
}
|
| 157 |
+
)
|
| 158 |
+
responses = [
|
| 159 |
+
response({"data": {"id": "submission-1", "attributes": CREDENTIALS}}),
|
| 160 |
+
response(b""),
|
| 161 |
+
response({"data": {"attributes": {"status": "In Progress"}}}),
|
| 162 |
+
response({"data": {"attributes": {"status": "Accepted"}}}),
|
| 163 |
+
response(
|
| 164 |
+
{
|
| 165 |
+
"data": {
|
| 166 |
+
"attributes": {
|
| 167 |
+
"developerLogUrl": "https://logs.example.com/log"
|
| 168 |
+
}
|
| 169 |
+
}
|
| 170 |
+
}
|
| 171 |
+
),
|
| 172 |
+
response(b'{"status":"Accepted"}'),
|
| 173 |
+
]
|
| 174 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 175 |
+
artifact, log = (
|
| 176 |
+
Path(directory) / "codex.zip",
|
| 177 |
+
Path(directory) / "notary-log.json",
|
| 178 |
+
)
|
| 179 |
+
artifact.write_bytes(b"signed release binary")
|
| 180 |
+
output = io.StringIO()
|
| 181 |
+
with (
|
| 182 |
+
patch.object(notary.subprocess, "run", return_value=signature),
|
| 183 |
+
patch.object(
|
| 184 |
+
notary.urllib.request, "urlopen", side_effect=responses
|
| 185 |
+
) as requests,
|
| 186 |
+
patch.object(notary.time, "sleep") as sleep,
|
| 187 |
+
contextlib.redirect_stdout(output),
|
| 188 |
+
):
|
| 189 |
+
submission = notary.notarize(
|
| 190 |
+
artifact, CONFIGURATION, max_wait_seconds=600, report_log=log
|
| 191 |
+
)
|
| 192 |
+
self.assertEqual(submission, "submission-1")
|
| 193 |
+
self.assertEqual(json.loads(log.read_text()), {"status": "Accepted"})
|
| 194 |
+
sleep.assert_called_once_with(10)
|
| 195 |
+
upload = requests.call_args_list[1].args[0]
|
| 196 |
+
self.assertEqual(upload.get_method(), "PUT")
|
| 197 |
+
self.assertNotIn("temporary-secret-key", upload.get_header("Authorization"))
|
| 198 |
+
self.assertNotIn("temporary-secret-key", output.getvalue())
|
| 199 |
+
|
| 200 |
+
|
| 201 |
+
class NotarizationWrapperTest(unittest.TestCase):
|
| 202 |
+
def test_binary_and_dmg_wrappers_preserve_notarization_contracts(
|
| 203 |
+
self,
|
| 204 |
+
) -> None:
|
| 205 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 206 |
+
root, tools = Path(directory), Path(directory) / "tools"
|
| 207 |
+
tools.mkdir()
|
| 208 |
+
call_log = root / "calls.txt"
|
| 209 |
+
environment = {
|
| 210 |
+
**os.environ,
|
| 211 |
+
**ENVIRONMENT,
|
| 212 |
+
"PATH": f"{tools}:{os.environ['PATH']}",
|
| 213 |
+
"RUNNER_TEMP": str(root),
|
| 214 |
+
"CALL_LOG": str(call_log),
|
| 215 |
+
}
|
| 216 |
+
for name in ("az", "python3", "rcodesign"):
|
| 217 |
+
body = (
|
| 218 |
+
"exit 0"
|
| 219 |
+
if name == "az"
|
| 220 |
+
else f'printf "{name} %s\\n" "$*" >> "$CALL_LOG"'
|
| 221 |
+
)
|
| 222 |
+
executable = tools / name
|
| 223 |
+
executable.write_text(
|
| 224 |
+
f"#!/usr/bin/env bash\nset -euo pipefail\n{body}\n"
|
| 225 |
+
)
|
| 226 |
+
executable.chmod(0o755)
|
| 227 |
+
for kind in ("binary", "dmg"):
|
| 228 |
+
with self.subTest(kind=kind):
|
| 229 |
+
artifact = root / ("codex.dmg" if kind == "dmg" else "codex")
|
| 230 |
+
artifact.write_bytes(b"signed release artifact")
|
| 231 |
+
subprocess.run(
|
| 232 |
+
[
|
| 233 |
+
str(
|
| 234 |
+
SIGNING_DIRECTORY / f"notarize_macos_{kind}_with_akv.sh"
|
| 235 |
+
),
|
| 236 |
+
f"--{kind}",
|
| 237 |
+
str(artifact),
|
| 238 |
+
"--report-dir",
|
| 239 |
+
str(root / "report"),
|
| 240 |
+
],
|
| 241 |
+
env=environment,
|
| 242 |
+
check=True,
|
| 243 |
+
capture_output=True,
|
| 244 |
+
text=True,
|
| 245 |
+
)
|
| 246 |
+
calls = call_log.read_text().splitlines()
|
| 247 |
+
self.assertIn("notarize_with_akv.py --file", calls[0])
|
| 248 |
+
if kind == "dmg":
|
| 249 |
+
self.assertEqual(calls[1], f"rcodesign staple {artifact}")
|
| 250 |
+
else:
|
| 251 |
+
self.assertEqual(len(calls), 1)
|
| 252 |
+
self.assertEqual(list(root.rglob("*.p8")), [])
|
| 253 |
+
call_log.unlink()
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
if __name__ == "__main__":
|
| 257 |
+
unittest.main()
|
.github/scripts/macos-signing/test_provisioned_macos_cli_package.py
ADDED
|
@@ -0,0 +1,217 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exercise profile validation, relocation and signing without release credentials."""
|
| 2 |
+
|
| 3 |
+
import copy
|
| 4 |
+
import hashlib
|
| 5 |
+
import json
|
| 6 |
+
import plistlib
|
| 7 |
+
import shutil
|
| 8 |
+
import ssl
|
| 9 |
+
import subprocess
|
| 10 |
+
import tarfile
|
| 11 |
+
import tempfile
|
| 12 |
+
import unittest
|
| 13 |
+
from dataclasses import replace
|
| 14 |
+
from datetime import datetime, timedelta, timezone
|
| 15 |
+
from pathlib import Path
|
| 16 |
+
|
| 17 |
+
import provisioned_macos_cli_package as bundle
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
class ProvisioningTestCase(unittest.TestCase):
|
| 21 |
+
@classmethod
|
| 22 |
+
def setUpClass(cls):
|
| 23 |
+
directory = tempfile.TemporaryDirectory()
|
| 24 |
+
cls.addClassCleanup(directory.cleanup)
|
| 25 |
+
root = Path(directory.name)
|
| 26 |
+
key = root / "test-key.pem"
|
| 27 |
+
certificate = root / "test-certificate.pem"
|
| 28 |
+
subprocess.run(
|
| 29 |
+
[
|
| 30 |
+
"openssl",
|
| 31 |
+
"req",
|
| 32 |
+
"-x509",
|
| 33 |
+
"-newkey",
|
| 34 |
+
"rsa:2048",
|
| 35 |
+
"-nodes",
|
| 36 |
+
"-keyout",
|
| 37 |
+
str(key),
|
| 38 |
+
"-out",
|
| 39 |
+
str(certificate),
|
| 40 |
+
"-days",
|
| 41 |
+
"1",
|
| 42 |
+
"-subj",
|
| 43 |
+
"/CN=CLI provisioning test/OU=TESTTEAM01",
|
| 44 |
+
],
|
| 45 |
+
check=True,
|
| 46 |
+
capture_output=True,
|
| 47 |
+
)
|
| 48 |
+
cert_der = ssl.PEM_cert_to_DER_cert(certificate.read_text())
|
| 49 |
+
now = datetime.now(timezone.utc).replace(tzinfo=None, microsecond=0)
|
| 50 |
+
cls.profile = {
|
| 51 |
+
"TeamIdentifier": ["TESTTEAM01"],
|
| 52 |
+
"ApplicationIdentifierPrefix": ["TESTTEAM01"],
|
| 53 |
+
"Entitlements": {
|
| 54 |
+
"com.apple.application-identifier": "TESTTEAM01.com.openai.codex.cli",
|
| 55 |
+
"com.apple.developer.team-identifier": "TESTTEAM01",
|
| 56 |
+
"keychain-access-groups": ["TESTTEAM01.*"],
|
| 57 |
+
},
|
| 58 |
+
"CreationDate": now - timedelta(days=1),
|
| 59 |
+
"ExpirationDate": now + timedelta(days=1),
|
| 60 |
+
"ProvisionsAllDevices": True,
|
| 61 |
+
"DeveloperCertificates": [cert_der],
|
| 62 |
+
}
|
| 63 |
+
payload = root / "profile.plist"
|
| 64 |
+
payload.write_bytes(plistlib.dumps(cls.profile))
|
| 65 |
+
profile_path = root / "test.provisionprofile"
|
| 66 |
+
subprocess.run(
|
| 67 |
+
[
|
| 68 |
+
"openssl",
|
| 69 |
+
"cms",
|
| 70 |
+
"-sign",
|
| 71 |
+
"-binary",
|
| 72 |
+
"-nodetach",
|
| 73 |
+
"-outform",
|
| 74 |
+
"DER",
|
| 75 |
+
"-in",
|
| 76 |
+
str(payload),
|
| 77 |
+
"-signer",
|
| 78 |
+
str(certificate),
|
| 79 |
+
"-inkey",
|
| 80 |
+
str(key),
|
| 81 |
+
"-out",
|
| 82 |
+
str(profile_path),
|
| 83 |
+
],
|
| 84 |
+
check=True,
|
| 85 |
+
capture_output=True,
|
| 86 |
+
)
|
| 87 |
+
cls.configuration = bundle.ProfileConfiguration(
|
| 88 |
+
profile=profile_path,
|
| 89 |
+
profile_sha256=hashlib.sha256(profile_path.read_bytes()).hexdigest(),
|
| 90 |
+
certificate_sha256=hashlib.sha256(cert_der).hexdigest(),
|
| 91 |
+
team_id="TESTTEAM01",
|
| 92 |
+
)
|
| 93 |
+
|
| 94 |
+
|
| 95 |
+
class ProvisionedCliTests(ProvisioningTestCase):
|
| 96 |
+
def test_profile_configuration_fails_closed(self):
|
| 97 |
+
self.assertEqual(bundle.load_profile(self.configuration), self.profile)
|
| 98 |
+
for changes in (
|
| 99 |
+
{"profile_sha256": "0" * 64},
|
| 100 |
+
{"certificate_sha256": "0" * 64},
|
| 101 |
+
{"team_id": "OTHERTEAM1"},
|
| 102 |
+
{"profile_sha256": ""},
|
| 103 |
+
{"certificate_sha256": ""},
|
| 104 |
+
{"team_id": ""},
|
| 105 |
+
):
|
| 106 |
+
with self.subTest(changes=changes), self.assertRaises(ValueError):
|
| 107 |
+
bundle.load_profile(replace(self.configuration, **changes))
|
| 108 |
+
|
| 109 |
+
def test_rejects_profile_with_wrong_identity_certificate_or_validity(self):
|
| 110 |
+
for field, value in (
|
| 111 |
+
("TeamIdentifier", ["OTHERTEAM"]),
|
| 112 |
+
("DeveloperCertificates", [b"another certificate"]),
|
| 113 |
+
(
|
| 114 |
+
"ExpirationDate",
|
| 115 |
+
datetime(2000, 1, 1, tzinfo=timezone.utc).replace(tzinfo=None),
|
| 116 |
+
),
|
| 117 |
+
(
|
| 118 |
+
"CreationDate",
|
| 119 |
+
datetime(2099, 1, 1, tzinfo=timezone.utc).replace(tzinfo=None),
|
| 120 |
+
),
|
| 121 |
+
("ProvisionsAllDevices", False),
|
| 122 |
+
("ProvisionedDevices", ["device"]),
|
| 123 |
+
):
|
| 124 |
+
with self.subTest(field=field):
|
| 125 |
+
profile = copy.deepcopy(self.profile)
|
| 126 |
+
profile[field] = value
|
| 127 |
+
with self.assertRaises(ValueError):
|
| 128 |
+
bundle.validate_profile(profile, self.configuration)
|
| 129 |
+
for field, value in (
|
| 130 |
+
(
|
| 131 |
+
"com.apple.application-identifier",
|
| 132 |
+
f"{self.configuration.team_id}.another.app",
|
| 133 |
+
),
|
| 134 |
+
("keychain-access-groups", [f"{self.configuration.team_id}.another.app"]),
|
| 135 |
+
("get-task-allow", True),
|
| 136 |
+
):
|
| 137 |
+
with self.subTest(field=field):
|
| 138 |
+
profile = copy.deepcopy(self.profile)
|
| 139 |
+
profile["Entitlements"][field] = value
|
| 140 |
+
with self.assertRaises(ValueError):
|
| 141 |
+
bundle.validate_profile(profile, self.configuration)
|
| 142 |
+
|
| 143 |
+
def test_archive_relocation_copy_and_symlink_preserve_launcher(self):
|
| 144 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 145 |
+
root = Path(directory)
|
| 146 |
+
package = root / "original package"
|
| 147 |
+
metadata = {
|
| 148 |
+
"variant": "codex",
|
| 149 |
+
"layoutVersion": 1,
|
| 150 |
+
"target": "aarch64-apple-darwin",
|
| 151 |
+
"entrypoint": "bin/codex",
|
| 152 |
+
}
|
| 153 |
+
for relative in ("bin/codex", *bundle.HELPERS):
|
| 154 |
+
path = package / relative
|
| 155 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 156 |
+
path.write_text('#!/bin/sh\nprintf "%s\\n" "$@"\nexit 23\n')
|
| 157 |
+
path.chmod(0o755)
|
| 158 |
+
(package / "codex-package.json").write_text(json.dumps(metadata))
|
| 159 |
+
original_binary = (package / "bin/codex").read_bytes()
|
| 160 |
+
bundle.prepare(package, root / "reports", self.configuration)
|
| 161 |
+
with self.assertRaises(FileExistsError):
|
| 162 |
+
bundle.prepare(package, root / "reports", self.configuration)
|
| 163 |
+
with self.assertRaisesRegex(ValueError, "Package target differs"):
|
| 164 |
+
bundle.verify(
|
| 165 |
+
package, root / "reports", "x86_64-apple-darwin", self.configuration
|
| 166 |
+
)
|
| 167 |
+
self.assertEqual(
|
| 168 |
+
(package / bundle.EXECUTABLE).read_bytes(), original_binary
|
| 169 |
+
)
|
| 170 |
+
self.assertEqual(
|
| 171 |
+
json.loads((package / "codex-package.json").read_text()), metadata
|
| 172 |
+
)
|
| 173 |
+
|
| 174 |
+
archive_path = root / "package.tar.gz"
|
| 175 |
+
with tarfile.open(archive_path, "w:gz") as archive:
|
| 176 |
+
archive.add(package, arcname=".")
|
| 177 |
+
relocated = root / "relocated package"
|
| 178 |
+
with tarfile.open(archive_path) as archive:
|
| 179 |
+
archive.extractall(relocated, filter="data")
|
| 180 |
+
shutil.rmtree(package)
|
| 181 |
+
# npm currently dereferences symlinks when copying packages. The
|
| 182 |
+
# launcher remains a script and the provisioned executable stays put.
|
| 183 |
+
copied = root / "copied package"
|
| 184 |
+
shutil.copytree(relocated, copied)
|
| 185 |
+
link = root / "installed codex"
|
| 186 |
+
link.symlink_to(Path("copied package/bin/codex"))
|
| 187 |
+
absolute_link = root / "absolute codex"
|
| 188 |
+
absolute_link.symlink_to(link)
|
| 189 |
+
for entry in (
|
| 190 |
+
relocated / "bin/codex",
|
| 191 |
+
copied / "bin/codex",
|
| 192 |
+
link,
|
| 193 |
+
absolute_link,
|
| 194 |
+
):
|
| 195 |
+
with self.subTest(entry=entry):
|
| 196 |
+
result = subprocess.run(
|
| 197 |
+
[str(entry), "space argument", "*", "--flag"],
|
| 198 |
+
cwd="/",
|
| 199 |
+
capture_output=True,
|
| 200 |
+
text=True,
|
| 201 |
+
check=False,
|
| 202 |
+
)
|
| 203 |
+
self.assertEqual(
|
| 204 |
+
(result.returncode, result.stdout, result.stderr),
|
| 205 |
+
(23, "space argument\n*\n--flag\n", ""),
|
| 206 |
+
)
|
| 207 |
+
(copied / bundle.APP / "Contents/embedded.provisionprofile").write_bytes(
|
| 208 |
+
b"tampered"
|
| 209 |
+
)
|
| 210 |
+
with self.assertRaisesRegex(ValueError, "Embedded profile differs"):
|
| 211 |
+
bundle.verify(
|
| 212 |
+
copied, root / "reports", "aarch64-apple-darwin", self.configuration
|
| 213 |
+
)
|
| 214 |
+
|
| 215 |
+
|
| 216 |
+
if __name__ == "__main__":
|
| 217 |
+
unittest.main()
|
.github/scripts/macos-signing/test_sign_macos_cli_package.py
ADDED
|
@@ -0,0 +1,293 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Exercise package signing and native verification with generated credentials."""
|
| 2 |
+
|
| 3 |
+
import itertools
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import shutil
|
| 7 |
+
import ssl
|
| 8 |
+
import subprocess
|
| 9 |
+
import sys
|
| 10 |
+
import tempfile
|
| 11 |
+
import unittest
|
| 12 |
+
import zipfile
|
| 13 |
+
from pathlib import Path
|
| 14 |
+
|
| 15 |
+
import provisioned_macos_cli_package as bundle
|
| 16 |
+
from test_provisioned_macos_cli_package import ProvisioningTestCase
|
| 17 |
+
|
| 18 |
+
NATIVE_TOOL = """#!/usr/bin/env python3
|
| 19 |
+
import json, os, plistlib, sys
|
| 20 |
+
from pathlib import Path
|
| 21 |
+
name = Path(sys.argv[0]).name
|
| 22 |
+
args = sys.argv[1:]
|
| 23 |
+
with open(os.environ["CALL_LOG"], "a") as log:
|
| 24 |
+
log.write(json.dumps([name, *args]) + "\\n")
|
| 25 |
+
if name == "codesign":
|
| 26 |
+
for arg in args:
|
| 27 |
+
if arg.startswith("--extract-certificates="):
|
| 28 |
+
Path(arg.split("=", 1)[1] + "0").write_bytes(Path(os.environ["MOCK_CERTIFICATE"]).read_bytes())
|
| 29 |
+
if "--test-requirement" in args and os.environ.get("MOCK_WRONG_SIGNING_IDENTITY"):
|
| 30 |
+
print("code failed to satisfy specified code requirement(s)", file=sys.stderr)
|
| 31 |
+
sys.exit(1)
|
| 32 |
+
if "--entitlements" in args:
|
| 33 |
+
target = Path(args[-1]).name
|
| 34 |
+
if target == "CodexCLI.app":
|
| 35 |
+
source = Path(os.environ["CODEX_REPO_ROOT"]) / "signing-verification/codex-provisioned-entitlements.plist"
|
| 36 |
+
elif target in ("codex", "codex-code-mode-host"):
|
| 37 |
+
source = Path(os.environ["MOCK_SIGNING_SCRIPTS"]) / (target + ".entitlements.plist")
|
| 38 |
+
else:
|
| 39 |
+
sys.exit(0)
|
| 40 |
+
if os.environ.get("MOCK_WRONG_ENTITLEMENTS"):
|
| 41 |
+
sys.stdout.buffer.write(plistlib.dumps({"unexpected": True}))
|
| 42 |
+
else:
|
| 43 |
+
sys.stdout.buffer.write(source.read_bytes())
|
| 44 |
+
elif name == "plutil":
|
| 45 |
+
source = Path(args[-1])
|
| 46 |
+
target = Path(args[args.index("-o") + 1]) if "-o" in args else source
|
| 47 |
+
target.write_bytes(plistlib.dumps(plistlib.loads(source.read_bytes())))
|
| 48 |
+
"""
|
| 49 |
+
|
| 50 |
+
|
| 51 |
+
class MacosSigningTests(ProvisioningTestCase):
|
| 52 |
+
def fixture(self, root, provisioned, target="aarch64-apple-darwin"):
|
| 53 |
+
source = Path(bundle.__file__).parent
|
| 54 |
+
common = Path(os.path.commonpath((source, bundle.SIGNING)))
|
| 55 |
+
helpers = root / source.relative_to(common)
|
| 56 |
+
scripts = root / bundle.SIGNING.relative_to(common)
|
| 57 |
+
helpers.mkdir(parents=True, exist_ok=True)
|
| 58 |
+
scripts.mkdir(parents=True, exist_ok=True)
|
| 59 |
+
for name in ("provisioned_macos_cli_package.py", "sign_macos_cli_package.py"):
|
| 60 |
+
shutil.copyfile(source / name, helpers / name)
|
| 61 |
+
for path in bundle.SIGNING.glob("*.entitlements.plist"):
|
| 62 |
+
shutil.copyfile(path, scripts / path.name)
|
| 63 |
+
(root / "record.py").write_text(
|
| 64 |
+
"import json, os, sys\n"
|
| 65 |
+
"with open(os.environ['CALL_LOG'], 'a') as log:\n"
|
| 66 |
+
" log.write(json.dumps(sys.argv[1:]) + '\\n')\n"
|
| 67 |
+
"if os.environ.get('FAIL_TOOL') == sys.argv[1]:\n"
|
| 68 |
+
" print('fixture stdout', flush=True)\n"
|
| 69 |
+
" print('fixture stderr', file=sys.stderr, flush=True)\n"
|
| 70 |
+
" sys.exit(17)\n"
|
| 71 |
+
)
|
| 72 |
+
for name in ("sign_macos_code.sh", "notarize_macos_binary_with_akv.sh"):
|
| 73 |
+
(scripts / name).write_text(
|
| 74 |
+
'python3 "$CODEX_REPO_ROOT/record.py" "$(basename "$0")" "$@"\n'
|
| 75 |
+
)
|
| 76 |
+
(scripts / "notarize_with_akv.py").write_text(
|
| 77 |
+
"import os, runpy, sys\n"
|
| 78 |
+
"sys.argv.insert(1, 'notarize_with_akv.py')\n"
|
| 79 |
+
"runpy.run_path(os.environ['CODEX_REPO_ROOT'] + '/record.py')\n"
|
| 80 |
+
)
|
| 81 |
+
for name in ("rcodesign", "codesign", "lipo", "plutil"):
|
| 82 |
+
path = root / name
|
| 83 |
+
path.write_text(NATIVE_TOOL)
|
| 84 |
+
path.chmod(0o755)
|
| 85 |
+
certificate_der = self.profile["DeveloperCertificates"][0]
|
| 86 |
+
certificate = root / "certificate.pem"
|
| 87 |
+
certificate.write_text(ssl.DER_cert_to_PEM_cert(certificate_der))
|
| 88 |
+
(root / "certificate.der").write_bytes(certificate_der)
|
| 89 |
+
package = root / "package with spaces"
|
| 90 |
+
for relative in ("bin/codex", *bundle.HELPERS):
|
| 91 |
+
path = package / relative
|
| 92 |
+
path.parent.mkdir(parents=True, exist_ok=True)
|
| 93 |
+
path.write_text("#!/bin/sh\nexit 0\n")
|
| 94 |
+
path.chmod(0o755)
|
| 95 |
+
(package / "codex-package.json").write_text(
|
| 96 |
+
json.dumps(
|
| 97 |
+
{
|
| 98 |
+
"variant": "codex",
|
| 99 |
+
"layoutVersion": 1,
|
| 100 |
+
"target": target,
|
| 101 |
+
"entrypoint": "bin/codex",
|
| 102 |
+
}
|
| 103 |
+
)
|
| 104 |
+
)
|
| 105 |
+
env = {
|
| 106 |
+
key: value
|
| 107 |
+
for key, value in os.environ.items()
|
| 108 |
+
if key not in ("GITHUB_WORKSPACE", "CODEX_PROVISIONING_HELPER")
|
| 109 |
+
}
|
| 110 |
+
env.update(
|
| 111 |
+
CODEX_REPO_ROOT=str(root),
|
| 112 |
+
RUNNER_TEMP=str(root),
|
| 113 |
+
TARGET=target,
|
| 114 |
+
PATH=f"{root}{os.pathsep}{os.environ['PATH']}",
|
| 115 |
+
CALL_LOG=str(root / "calls.jsonl"),
|
| 116 |
+
PROVISIONED_MACOS=str(provisioned).lower(),
|
| 117 |
+
OAI_AKV_SIGNING_CERTIFICATE_PEM=str(certificate),
|
| 118 |
+
MOCK_CERTIFICATE=str(root / "certificate.der"),
|
| 119 |
+
MOCK_SIGNING_SCRIPTS=str(scripts),
|
| 120 |
+
)
|
| 121 |
+
return package, helpers / "sign_macos_cli_package.py", env
|
| 122 |
+
|
| 123 |
+
def run_driver(self, driver, operation, package, env):
|
| 124 |
+
return subprocess.run(
|
| 125 |
+
[
|
| 126 |
+
sys.executable,
|
| 127 |
+
str(driver),
|
| 128 |
+
operation,
|
| 129 |
+
str(package),
|
| 130 |
+
"--profile",
|
| 131 |
+
str(self.configuration.profile),
|
| 132 |
+
"--profile-sha256",
|
| 133 |
+
self.configuration.profile_sha256,
|
| 134 |
+
"--certificate-sha256",
|
| 135 |
+
self.configuration.certificate_sha256,
|
| 136 |
+
"--team-id",
|
| 137 |
+
self.configuration.team_id,
|
| 138 |
+
],
|
| 139 |
+
env=env,
|
| 140 |
+
capture_output=True,
|
| 141 |
+
text=True,
|
| 142 |
+
timeout=30,
|
| 143 |
+
check=False,
|
| 144 |
+
)
|
| 145 |
+
|
| 146 |
+
def test_standard_and_provisioned_signing_and_verification(self):
|
| 147 |
+
for provisioned, target in itertools.product(
|
| 148 |
+
(False, True), ("aarch64-apple-darwin", "x86_64-apple-darwin")
|
| 149 |
+
):
|
| 150 |
+
with (
|
| 151 |
+
self.subTest(provisioned=provisioned, target=target),
|
| 152 |
+
tempfile.TemporaryDirectory() as directory,
|
| 153 |
+
):
|
| 154 |
+
root = Path(directory).resolve()
|
| 155 |
+
package, driver, env = self.fixture(root, provisioned, target)
|
| 156 |
+
result = self.run_driver(driver, "sign", package, env)
|
| 157 |
+
self.assertEqual(result.returncode, 0, result.stderr)
|
| 158 |
+
result = self.run_driver(driver, "verify", package, env)
|
| 159 |
+
self.assertEqual(result.returncode, 0, result.stderr)
|
| 160 |
+
calls = [
|
| 161 |
+
json.loads(line)
|
| 162 |
+
for line in (root / "calls.jsonl").read_text().splitlines()
|
| 163 |
+
]
|
| 164 |
+
signing = [call for call in calls if call[0] == "sign_macos_code.sh"]
|
| 165 |
+
self.assertEqual(
|
| 166 |
+
[call[call.index("--target") + 1] for call in signing],
|
| 167 |
+
[
|
| 168 |
+
str(package / relative)
|
| 169 |
+
for relative in (
|
| 170 |
+
bundle.APP if provisioned else "bin/codex",
|
| 171 |
+
*bundle.HELPERS,
|
| 172 |
+
)
|
| 173 |
+
],
|
| 174 |
+
)
|
| 175 |
+
self.assertEqual(
|
| 176 |
+
signing[0][signing[0].index("--entitlements") + 1],
|
| 177 |
+
str(
|
| 178 |
+
root
|
| 179 |
+
/ "signing-verification/codex-provisioned-entitlements.plist"
|
| 180 |
+
)
|
| 181 |
+
if provisioned
|
| 182 |
+
else str(
|
| 183 |
+
Path(env["MOCK_SIGNING_SCRIPTS"]) / "codex.entitlements.plist"
|
| 184 |
+
),
|
| 185 |
+
)
|
| 186 |
+
self.assertEqual(
|
| 187 |
+
["--entitlements" in call for call in signing],
|
| 188 |
+
[True, True, False, False],
|
| 189 |
+
)
|
| 190 |
+
self.assertEqual(
|
| 191 |
+
[call[0] for call in calls if call[0].startswith("notarize")],
|
| 192 |
+
["notarize_with_akv.py"]
|
| 193 |
+
if provisioned
|
| 194 |
+
else ["notarize_macos_binary_with_akv.sh"] * 4,
|
| 195 |
+
)
|
| 196 |
+
self.assertEqual(
|
| 197 |
+
[call[1:] for call in calls if call[0] == "lipo"],
|
| 198 |
+
[
|
| 199 |
+
[
|
| 200 |
+
str(package / relative),
|
| 201 |
+
"-verify_arch",
|
| 202 |
+
"arm64" if target.startswith("aarch64") else "x86_64",
|
| 203 |
+
]
|
| 204 |
+
for relative in (
|
| 205 |
+
bundle.EXECUTABLE if provisioned else "bin/codex",
|
| 206 |
+
*bundle.HELPERS,
|
| 207 |
+
)
|
| 208 |
+
],
|
| 209 |
+
)
|
| 210 |
+
if provisioned:
|
| 211 |
+
with zipfile.ZipFile(root / "provisioned-cli.zip") as archive:
|
| 212 |
+
self.assertEqual(
|
| 213 |
+
archive.read(
|
| 214 |
+
"CodexCLI.app/Contents/embedded.provisionprofile"
|
| 215 |
+
),
|
| 216 |
+
self.configuration.profile.read_bytes(),
|
| 217 |
+
)
|
| 218 |
+
self.assertEqual(
|
| 219 |
+
archive.read("bin/codex").decode(), bundle.LAUNCHER
|
| 220 |
+
)
|
| 221 |
+
self.assertTrue(
|
| 222 |
+
all(
|
| 223 |
+
helper in archive.namelist()
|
| 224 |
+
for helper in bundle.HELPERS
|
| 225 |
+
)
|
| 226 |
+
)
|
| 227 |
+
|
| 228 |
+
def test_signing_and_notarization_failures_stop_the_driver(self):
|
| 229 |
+
for tool in ("sign_macos_code.sh", "notarize_with_akv.py"):
|
| 230 |
+
with self.subTest(tool=tool), tempfile.TemporaryDirectory() as directory:
|
| 231 |
+
root = Path(directory).resolve()
|
| 232 |
+
package, driver, env = self.fixture(root, True)
|
| 233 |
+
result = self.run_driver(
|
| 234 |
+
driver, "sign", package, {**env, "FAIL_TOOL": tool}
|
| 235 |
+
)
|
| 236 |
+
self.assertNotEqual(result.returncode, 0)
|
| 237 |
+
calls = [
|
| 238 |
+
json.loads(line)
|
| 239 |
+
for line in (root / "calls.jsonl").read_text().splitlines()
|
| 240 |
+
]
|
| 241 |
+
self.assertEqual(calls[-1][0], tool)
|
| 242 |
+
if tool == "notarize_with_akv.py":
|
| 243 |
+
self.assertEqual(
|
| 244 |
+
(root / "signing-verification/notarization.log").read_text(),
|
| 245 |
+
"fixture stdout\nfixture stderr\n",
|
| 246 |
+
)
|
| 247 |
+
self.assertIn("fixture stdout\nfixture stderr\n", result.stdout)
|
| 248 |
+
else:
|
| 249 |
+
self.assertFalse((root / "provisioned-cli.zip").exists())
|
| 250 |
+
|
| 251 |
+
def test_verification_rejects_wrong_signing_identity_and_entitlements(self):
|
| 252 |
+
for provisioned, failure in itertools.product(
|
| 253 |
+
(False, True), ("MOCK_WRONG_SIGNING_IDENTITY", "MOCK_WRONG_ENTITLEMENTS")
|
| 254 |
+
):
|
| 255 |
+
with (
|
| 256 |
+
self.subTest(provisioned=provisioned, failure=failure),
|
| 257 |
+
tempfile.TemporaryDirectory() as directory,
|
| 258 |
+
):
|
| 259 |
+
root = Path(directory).resolve()
|
| 260 |
+
package, driver, env = self.fixture(root, provisioned)
|
| 261 |
+
signed = self.run_driver(driver, "sign", package, env)
|
| 262 |
+
self.assertEqual(signed.returncode, 0, signed.stderr)
|
| 263 |
+
result = self.run_driver(
|
| 264 |
+
driver, "verify", package, {**env, failure: "true"}
|
| 265 |
+
)
|
| 266 |
+
self.assertNotEqual(result.returncode, 0)
|
| 267 |
+
if failure == "MOCK_WRONG_SIGNING_IDENTITY":
|
| 268 |
+
self.assertIn(
|
| 269 |
+
"code failed to satisfy specified code requirement(s)",
|
| 270 |
+
result.stderr,
|
| 271 |
+
)
|
| 272 |
+
else:
|
| 273 |
+
self.assertIn("unexpected", result.stdout)
|
| 274 |
+
|
| 275 |
+
def test_cli_requires_independent_profile_configuration(self):
|
| 276 |
+
with tempfile.TemporaryDirectory() as directory:
|
| 277 |
+
result = subprocess.run(
|
| 278 |
+
[
|
| 279 |
+
sys.executable,
|
| 280 |
+
str(Path(bundle.__file__).with_name("sign_macos_cli_package.py")),
|
| 281 |
+
"verify",
|
| 282 |
+
directory,
|
| 283 |
+
],
|
| 284 |
+
capture_output=True,
|
| 285 |
+
text=True,
|
| 286 |
+
check=False,
|
| 287 |
+
)
|
| 288 |
+
self.assertEqual(result.returncode, 2)
|
| 289 |
+
self.assertIn("the following arguments are required", result.stderr)
|
| 290 |
+
|
| 291 |
+
|
| 292 |
+
if __name__ == "__main__":
|
| 293 |
+
unittest.main()
|
docs/CLA.md
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Individual Contributor License Agreement (v1.0, OpenAI)
|
| 2 |
+
|
| 3 |
+
_Based on the Apache Software Foundation Individual CLA v 2.2._
|
| 4 |
+
|
| 5 |
+
By commenting **“I have read the CLA Document and I hereby sign the CLA”**
|
| 6 |
+
on a Pull Request, **you (“Contributor”) agree to the following terms** for any
|
| 7 |
+
past and future “Contributions” submitted to the **OpenAI Codex CLI project
|
| 8 |
+
(the “Project”)**.
|
| 9 |
+
|
| 10 |
+
---
|
| 11 |
+
|
| 12 |
+
## 1. Definitions
|
| 13 |
+
|
| 14 |
+
- **“Contribution”** – any original work of authorship submitted to the Project
|
| 15 |
+
(code, documentation, designs, etc.).
|
| 16 |
+
- **“You” / “Your”** – the individual (or legal entity) posting the acceptance
|
| 17 |
+
comment.
|
| 18 |
+
|
| 19 |
+
## 2. Copyright License
|
| 20 |
+
|
| 21 |
+
You grant **OpenAI, Inc.** and all recipients of software distributed by the
|
| 22 |
+
Project a perpetual, worldwide, non‑exclusive, royalty‑free, irrevocable
|
| 23 |
+
license to reproduce, prepare derivative works of, publicly display, publicly
|
| 24 |
+
perform, sublicense, and distribute Your Contributions and derivative works.
|
| 25 |
+
|
| 26 |
+
## 3. Patent License
|
| 27 |
+
|
| 28 |
+
You grant **OpenAI, Inc.** and all recipients of the Project a perpetual,
|
| 29 |
+
worldwide, non‑exclusive, royalty‑free, irrevocable (except as below) patent
|
| 30 |
+
license to make, have made, use, sell, offer to sell, import, and otherwise
|
| 31 |
+
transfer Your Contributions alone or in combination with the Project.
|
| 32 |
+
|
| 33 |
+
If any entity brings patent litigation alleging that the Project or a
|
| 34 |
+
Contribution infringes a patent, the patent licenses granted by You to that
|
| 35 |
+
entity under this CLA terminate.
|
| 36 |
+
|
| 37 |
+
## 4. Representations
|
| 38 |
+
|
| 39 |
+
1. You are legally entitled to grant the licenses above.
|
| 40 |
+
2. Each Contribution is either Your original creation or You have authority to
|
| 41 |
+
submit it under this CLA.
|
| 42 |
+
3. Your Contributions are provided **“AS IS”** without warranties of any kind.
|
| 43 |
+
4. You will notify the Project if any statement above becomes inaccurate.
|
| 44 |
+
|
| 45 |
+
## 5. Miscellany
|
| 46 |
+
|
| 47 |
+
This Agreement is governed by the laws of the **State of California**, USA,
|
| 48 |
+
excluding its conflict‑of‑laws rules. If any provision is held unenforceable,
|
| 49 |
+
the remaining provisions remain in force.
|
docs/agents_md.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AGENTS.md
|
| 2 |
+
|
| 3 |
+
For information about AGENTS.md, see [this documentation](https://developers.openai.com/codex/guides/agents-md).
|
docs/authentication.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Authentication
|
| 2 |
+
|
| 3 |
+
For information about Codex CLI authentication, see [this documentation](https://developers.openai.com/codex/auth).
|
docs/config.md
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Configuration
|
| 2 |
+
|
| 3 |
+
For basic configuration instructions, see [this documentation](https://developers.openai.com/codex/config-basic).
|
| 4 |
+
|
| 5 |
+
For advanced configuration instructions, see [this documentation](https://developers.openai.com/codex/config-advanced).
|
| 6 |
+
|
| 7 |
+
For a full configuration reference, see [this documentation](https://developers.openai.com/codex/config-reference).
|
| 8 |
+
|
| 9 |
+
## Lifecycle hooks
|
| 10 |
+
|
| 11 |
+
Admins can set top-level `allow_managed_hooks_only = true` in
|
| 12 |
+
`requirements.toml` to ignore user, project, and session hook configs while
|
| 13 |
+
still allowing managed hooks from requirements and managed config layers. This
|
| 14 |
+
setting is only supported in `requirements.toml`; putting it in `config.toml`
|
| 15 |
+
does not enable managed-hooks-only mode.
|
docs/contributing.md
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Contributing
|
| 2 |
+
|
| 3 |
+
We welcome community contributions through the [openai/codex issue tracker](https://github.com/openai/codex/issues). Bug reports, root-cause analyses, and feature requests help us understand what matters most and improve Codex.
|
| 4 |
+
|
| 5 |
+
**We do not accept external code contributions or pull requests.**
|
| 6 |
+
|
| 7 |
+
### Why we do not accept external code contributions
|
| 8 |
+
|
| 9 |
+
Effective changes to Codex require architectural context, an understanding of system-level constraints, and visibility into the project's roadmap. External pull requests often focus on issues that are lower priority, affect a small number of users, or need substantial changes to fit the broader system. Reviewing and iterating on those changes can take more time than implementing a fix directly, diverting attention from higher-priority work.
|
| 10 |
+
|
| 11 |
+
Community expertise is most valuable when shared through detailed bug reports, reproduction steps, logs, root-cause analysis, and design discussions in issues. Understanding the problem, identifying the right solution, and prioritizing the work are typically the hard parts; implementation is comparatively straightforward with the help of Codex itself.
|
| 12 |
+
|
| 13 |
+
For these reasons, we focus community contributions on issue reports, analysis, and feedback, while the Codex team handles code changes.
|
| 14 |
+
|
| 15 |
+
### Reporting bugs
|
| 16 |
+
|
| 17 |
+
Before opening a new issue, search the issue tracker to see whether the problem has already been reported. If it has, add any new information to the existing issue.
|
| 18 |
+
|
| 19 |
+
When reporting a bug, include as much relevant detail as possible:
|
| 20 |
+
|
| 21 |
+
- Clear, detailed steps to reproduce the problem.
|
| 22 |
+
- Expected and actual behavior.
|
| 23 |
+
- Your Codex version, operating system, and other relevant environment details.
|
| 24 |
+
- Logs, error messages, or other diagnostic information, with sensitive information removed.
|
| 25 |
+
- Root-cause analysis, technical observations, or potential approaches to a fix, if available.
|
| 26 |
+
|
| 27 |
+
### Requesting features
|
| 28 |
+
|
| 29 |
+
Open a feature request in the issue tracker, or upvote an existing request that describes the same need. Explain your use case, the behavior you would like, and why it would improve your workflow.
|
| 30 |
+
|
| 31 |
+
### Community values
|
| 32 |
+
|
| 33 |
+
- **Be kind and inclusive.** Treat others with respect; we follow the [Contributor Covenant](https://www.contributor-covenant.org/).
|
| 34 |
+
- **Assume good intent.** Written communication is hard, so err on the side of generosity.
|
| 35 |
+
- **Share what you learn.** Reproduction details, logs, and analysis help the entire community.
|
| 36 |
+
|
| 37 |
+
### Security
|
| 38 |
+
|
| 39 |
+
If you discover a security vulnerability, follow the [security policy](../SECURITY.md) instead of reporting it in a public issue.
|
docs/example-config.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Sample configuration
|
| 2 |
+
|
| 3 |
+
For a sample configuration file, see [this documentation](https://developers.openai.com/codex/config-sample).
|
docs/exec.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Non-interactive mode
|
| 2 |
+
|
| 3 |
+
For information about non-interactive mode, see [this documentation](https://developers.openai.com/codex/noninteractive).
|
docs/execpolicy.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Execution policy
|
| 2 |
+
|
| 3 |
+
For an overview of execution policy rules, see [this documentation](https://developers.openai.com/codex/exec-policy).
|
docs/getting-started.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Getting started with Codex CLI
|
| 2 |
+
|
| 3 |
+
For an overview of Codex CLI features, see [this documentation](https://developers.openai.com/codex/cli/features#running-in-interactive-mode).
|
docs/install.md
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Installing & building
|
| 2 |
+
|
| 3 |
+
### System requirements
|
| 4 |
+
|
| 5 |
+
| Requirement | Details |
|
| 6 |
+
| --------------------------- | --------------------------------------------------------------- |
|
| 7 |
+
| Operating systems | macOS 12+, Ubuntu 20.04+/Debian 10+, or Windows 11 **via WSL2** |
|
| 8 |
+
| Git (optional, recommended) | 2.23+ for built-in PR helpers |
|
| 9 |
+
| RAM | 4-GB minimum (8-GB recommended) |
|
| 10 |
+
|
| 11 |
+
### DotSlash
|
| 12 |
+
|
| 13 |
+
The GitHub Release also contains a [DotSlash](https://dotslash-cli.com/) file for the Codex CLI named `codex`. Using a DotSlash file makes it possible to make a lightweight commit to source control to ensure all contributors use the same version of an executable, regardless of what platform they use for development.
|
| 14 |
+
|
| 15 |
+
### Build from source
|
| 16 |
+
|
| 17 |
+
```bash
|
| 18 |
+
# Clone the repository and navigate to the root of the Cargo workspace.
|
| 19 |
+
git clone https://github.com/openai/codex.git
|
| 20 |
+
cd codex/codex-rs
|
| 21 |
+
|
| 22 |
+
# Install the Rust toolchain, if necessary.
|
| 23 |
+
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y
|
| 24 |
+
source "$HOME/.cargo/env"
|
| 25 |
+
rustup component add rustfmt
|
| 26 |
+
rustup component add clippy
|
| 27 |
+
# Install helper tools used by the workspace justfile:
|
| 28 |
+
cargo install --locked just
|
| 29 |
+
# DotSlash fetches pinned development tools such as buildifier on first use.
|
| 30 |
+
cargo install --locked dotslash
|
| 31 |
+
# Install nextest for the `just test` helper.
|
| 32 |
+
cargo install --locked cargo-nextest
|
| 33 |
+
|
| 34 |
+
# Build Codex.
|
| 35 |
+
cargo build
|
| 36 |
+
|
| 37 |
+
# Launch the TUI with a sample prompt.
|
| 38 |
+
cargo run --bin codex -- "explain this codebase to me"
|
| 39 |
+
|
| 40 |
+
# After making changes, use the root justfile helpers (they default to codex-rs):
|
| 41 |
+
just fmt
|
| 42 |
+
just fix -p <crate-you-touched>
|
| 43 |
+
|
| 44 |
+
# Run the relevant tests (project-specific is fastest), for example:
|
| 45 |
+
just test -p codex-tui
|
| 46 |
+
# `just test` runs the test suite via nextest:
|
| 47 |
+
just test
|
| 48 |
+
# Avoid `--all-features` for routine local runs because it increases build
|
| 49 |
+
# time and `target/` disk usage by compiling additional feature combinations.
|
| 50 |
+
```
|
| 51 |
+
|
| 52 |
+
## Tracing / verbose logging
|
| 53 |
+
|
| 54 |
+
Codex is written in Rust, so it honors the `RUST_LOG` environment variable to configure its logging behavior.
|
| 55 |
+
|
| 56 |
+
The TUI records diagnostics in bounded local stores by default. Set `log_dir` explicitly to enable a plaintext TUI log for a run:
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
codex -c log_dir=./.codex-log
|
| 60 |
+
tail -F ./.codex-log/codex-tui.log
|
| 61 |
+
```
|
| 62 |
+
|
| 63 |
+
The non-interactive mode (`codex exec`) defaults to `RUST_LOG=error`, but messages are printed inline, so there is no need to monitor a separate file.
|
| 64 |
+
|
| 65 |
+
See the Rust documentation on [`RUST_LOG`](https://docs.rs/env_logger/latest/env_logger/#enabling-logging) for more information on the configuration options.
|
docs/license.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## License
|
| 2 |
+
|
| 3 |
+
This repository is licensed under the [Apache-2.0 License](../LICENSE).
|
docs/open-source-fund.md
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Codex open source fund
|
| 2 |
+
|
| 3 |
+
We're excited to launch a **$1 million initiative** supporting open source projects that use Codex CLI and other OpenAI models.
|
| 4 |
+
|
| 5 |
+
- Grants are awarded up to **$25,000** API credits.
|
| 6 |
+
- Applications are reviewed **on a rolling basis**.
|
| 7 |
+
|
| 8 |
+
**Interested? [Apply here](https://openai.com/form/codex-open-source-fund/).**
|
docs/sandbox.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
## Sandbox & approvals
|
| 2 |
+
|
| 3 |
+
For information about Codex sandboxing and approvals, see [this documentation](https://developers.openai.com/codex/security).
|
docs/skills.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Skills
|
| 2 |
+
|
| 3 |
+
For information about skills, refer to [this documentation](https://developers.openai.com/codex/skills).
|
docs/slash_commands.md
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Slash commands
|
| 2 |
+
|
| 3 |
+
For an overview of Codex CLI slash commands, see [this documentation](https://developers.openai.com/codex/cli/slash-commands).
|
patches/BUILD.bazel
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
exports_files([
|
| 2 |
+
"abseil_windows_gnullvm_thread_identity.patch",
|
| 3 |
+
"bzip2_windows_stack_args.patch",
|
| 4 |
+
"llvm_rusty_v8_custom_libcxx.patch",
|
| 5 |
+
"llvm_windows_arm64_powl.patch",
|
| 6 |
+
"llvm_windows_mingw_compat.patch",
|
| 7 |
+
"rules_rust_build_script_tools_transition.patch",
|
| 8 |
+
"rules_rust_group_build_script_arg_files.patch",
|
| 9 |
+
"rules_rust_windows_execroot_separators.patch",
|
| 10 |
+
"rules_rust_windows_msvc_direct_link_args.patch",
|
| 11 |
+
"rules_rust_windows_process_wrapper_skip_temp_outputs.patch",
|
| 12 |
+
"rules_cc_rusty_v8_custom_libcxx.patch",
|
| 13 |
+
"rules_foreign_cc_make_cppflags.patch",
|
| 14 |
+
"rules_foreign_cc_make_xcompile.patch",
|
| 15 |
+
"rules_foreign_cc_make_msvc_stdint.patch",
|
| 16 |
+
"rules_rs_build_script_deps_annotation.patch",
|
| 17 |
+
"rules_rs_windows_msvc_linker.patch",
|
| 18 |
+
"rules_rs_zlib_snapshot_urls.patch",
|
| 19 |
+
"rusty_v8_prebuilt_out_dir.patch",
|
| 20 |
+
"ring_windows_msvc_include_dirs.patch",
|
| 21 |
+
"tikv-jemalloc-sys_make_env.patch",
|
| 22 |
+
"tikv-jemalloc-sys_dependency_flags.patch",
|
| 23 |
+
"v8_bazel_rules.patch",
|
| 24 |
+
"v8_module_deps.patch",
|
| 25 |
+
"v8_source_portability.patch",
|
| 26 |
+
"webrtc-sys_hermetic_darwin_sysroot.patch",
|
| 27 |
+
"windows-link.patch",
|
| 28 |
+
"windows-support-remote-case-aliases.patch",
|
| 29 |
+
"windows-support-native-tools.patch",
|
| 30 |
+
"xz_windows_stack_args.patch",
|
| 31 |
+
"zstd-sys_windows_msvc_include_dirs.patch",
|
| 32 |
+
])
|
patches/abseil_windows_gnullvm_thread_identity.patch
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# What: avoid MinGW pthread TLS linkage in Abseil thread identity selection.
|
| 2 |
+
# Scope: one MinGW-specific preprocessor branch in Abseil's internal
|
| 3 |
+
# thread-identity mode selection; no other Abseil behavior changes.
|
| 4 |
+
|
| 5 |
+
diff --git a/absl/base/internal/thread_identity.h b/absl/base/internal/thread_identity.h
|
| 6 |
+
--- a/absl/base/internal/thread_identity.h
|
| 7 |
+
+++ b/absl/base/internal/thread_identity.h
|
| 8 |
+
@@ -217,6 +217,8 @@
|
| 9 |
+
#error ABSL_THREAD_IDENTITY_MODE cannot be directly set
|
| 10 |
+
#elif defined(ABSL_FORCE_THREAD_IDENTITY_MODE)
|
| 11 |
+
#define ABSL_THREAD_IDENTITY_MODE ABSL_FORCE_THREAD_IDENTITY_MODE
|
| 12 |
+
+#elif defined(__MINGW32__)
|
| 13 |
+
+#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
|
| 14 |
+
#elif defined(_WIN32) && !defined(__MINGW32__)
|
| 15 |
+
#define ABSL_THREAD_IDENTITY_MODE ABSL_THREAD_IDENTITY_MODE_USE_CPP11
|
| 16 |
+
#elif defined(__APPLE__) && defined(ABSL_HAVE_THREAD_LOCAL)
|
patches/bzip2_windows_stack_args.patch
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/BUILD.bazel b/BUILD.bazel
|
| 2 |
+
--- a/BUILD.bazel
|
| 3 |
+
+++ b/BUILD.bazel
|
| 4 |
+
@@ -28,4 +28,11 @@ cc_library(
|
| 5 |
+
defines = [
|
| 6 |
+
"_FILE_OFFSET_BITS=64",
|
| 7 |
+
],
|
| 8 |
+
+ copts = select({
|
| 9 |
+
+ "@platforms//os:windows": [
|
| 10 |
+
+ "-fno-stack-protector",
|
| 11 |
+
+ "-mno-stack-arg-probe",
|
| 12 |
+
+ ],
|
| 13 |
+
+ "//conditions:default": [],
|
| 14 |
+
+ }),
|
| 15 |
+
includes = ["."],
|
| 16 |
+
diff --git a/MODULE.bazel b/MODULE.bazel
|
| 17 |
+
--- a/MODULE.bazel
|
| 18 |
+
+++ b/MODULE.bazel
|
| 19 |
+
@@ -4,3 +4,4 @@ module(
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
bazel_dep(name = "rules_cc", version = "0.0.10")
|
| 23 |
+
+bazel_dep(name = "platforms", version = "1.0.0")
|
patches/llvm_rusty_v8_custom_libcxx.patch
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/toolchain/BUILD.bazel b/toolchain/BUILD.bazel
|
| 2 |
+
index fa00156236..c9e4348172 100644
|
| 3 |
+
--- a/toolchain/BUILD.bazel
|
| 4 |
+
+++ b/toolchain/BUILD.bazel
|
| 5 |
+
@@ -1,5 +1,6 @@
|
| 6 |
+
load("@bazel_lib//:bzl_library.bzl", "bzl_library")
|
| 7 |
+
+load("@bazel_skylib//lib:selects.bzl", "selects")
|
| 8 |
+
load("@bazel_skylib//rules:common_settings.bzl", "string_flag", "string_setting")
|
| 9 |
+
load("@rules_cc//cc/toolchains:actions.bzl", "cc_action_type_set")
|
| 10 |
+
load("@rules_cc//cc/toolchains:artifacts.bzl", "cc_artifact_name_pattern")
|
| 11 |
+
load("@rules_cc//cc/toolchains/impl:documented_api.bzl", "cc_args_list")
|
| 12 |
+
@@ -64,6 +65,15 @@ config_setting(
|
| 13 |
+
visibility = ["//visibility:public"],
|
| 14 |
+
)
|
| 15 |
+
-
|
| 16 |
+
+
|
| 17 |
+
+selects.config_setting_group(
|
| 18 |
+
+ name = "runtimes_all_with_rusty_v8_custom_libcxx",
|
| 19 |
+
+ match_all = [
|
| 20 |
+
+ ":runtimes_all",
|
| 21 |
+
+ "@@//third_party/v8:use_rusty_v8_custom_libcxx",
|
| 22 |
+
+ ],
|
| 23 |
+
+ visibility = ["//visibility:public"],
|
| 24 |
+
+)
|
| 25 |
+
+
|
| 26 |
+
config_setting(
|
| 27 |
+
name = "runtimes_stage1",
|
| 28 |
+
flag_values = {
|
| 29 |
+
@@ -270,11 +280,16 @@ cc_args_list(
|
| 30 |
+
|
| 31 |
+
# TODO(cerisier): extract those into proper semantic args list.
|
| 32 |
+
# TODO(zbarsky): This must match llvm/toolchains/llvm.bzl
|
| 33 |
+
+_DEFAULT_CXXSTDLIB_HEADER_ARGS = select({
|
| 34 |
+
+ "@@//third_party/v8:use_rusty_v8_custom_libcxx": [],
|
| 35 |
+
+ "//conditions:default": [
|
| 36 |
+
+ "//toolchain/args:cxxstdlib_headers_include_search_paths",
|
| 37 |
+
+ ],
|
| 38 |
+
+})
|
| 39 |
+
+
|
| 40 |
+
cc_args_list(
|
| 41 |
+
name = "linux_toolchain_args",
|
| 42 |
+
- args = [
|
| 43 |
+
- "//toolchain/args:cxxstdlib_headers_include_search_paths",
|
| 44 |
+
- ] + select({
|
| 45 |
+
+ args = _DEFAULT_CXXSTDLIB_HEADER_ARGS + select({
|
| 46 |
+
"//platforms/config:musl": [
|
| 47 |
+
"//toolchain/args/linux:kernel_headers_include_search_paths",
|
| 48 |
+
"//toolchain/args/linux:musl_libc_headers_include_search_paths",
|
| 49 |
+
@@ -302,8 +317,7 @@ cc_args_list(
|
| 50 |
+
# TODO(zbarsky): This must match llvm/toolchains/llvm.bzl
|
| 51 |
+
cc_args_list(
|
| 52 |
+
name = "windows_toolchain_args",
|
| 53 |
+
- args = [
|
| 54 |
+
- "//toolchain/args:cxxstdlib_headers_include_search_paths",
|
| 55 |
+
+ args = _DEFAULT_CXXSTDLIB_HEADER_ARGS + [
|
| 56 |
+
"//toolchain/args/windows:mingw_headers_include_search_paths",
|
| 57 |
+
],
|
| 58 |
+
)
|
| 59 |
+
diff --git a/toolchain/llvm/llvm.bzl b/toolchain/llvm/llvm.bzl
|
| 60 |
+
index d36d8b94bd..900aa908d5 100644
|
| 61 |
+
--- a/toolchain/llvm/llvm.bzl
|
| 62 |
+
+++ b/toolchain/llvm/llvm.bzl
|
| 63 |
+
@@ -297,18 +297,23 @@ def declare_llvm_targets(*, suffix = ""):
|
| 64 |
+
],
|
| 65 |
+
)
|
| 66 |
+
|
| 67 |
+
+ default_cxxstdlib_target_headers = select({
|
| 68 |
+
+ "@llvm//toolchain:runtimes_all_with_rusty_v8_custom_libcxx": [],
|
| 69 |
+
+ "@llvm//toolchain:runtimes_stage1": [],
|
| 70 |
+
+ "@llvm//toolchain:runtimes_stage1_hosted": [],
|
| 71 |
+
+ "@llvm//toolchain:runtimes_none": [],
|
| 72 |
+
+ "//conditions:default": [
|
| 73 |
+
+ "@llvm//runtimes/cxxstdlib:headers_include_search_directory",
|
| 74 |
+
+ "@llvm//runtimes/cxxstdlib:abi_headers_include_search_directory",
|
| 75 |
+
+ ],
|
| 76 |
+
+ })
|
| 77 |
+
+
|
| 78 |
+
# This must match //toolchain:linux_toolchain_args
|
| 79 |
+
include_path(
|
| 80 |
+
name = "linux_target_headers",
|
| 81 |
+
srcs = [
|
| 82 |
+
":builtin_resource_dir",
|
| 83 |
+
- ] + select({
|
| 84 |
+
- "@llvm//toolchain:runtimes_all": [
|
| 85 |
+
- "@llvm//runtimes/cxxstdlib:headers_include_search_directory",
|
| 86 |
+
- "@llvm//runtimes/cxxstdlib:abi_headers_include_search_directory",
|
| 87 |
+
- ],
|
| 88 |
+
- "//conditions:default": [],
|
| 89 |
+
- }) + [
|
| 90 |
+
+ ] + default_cxxstdlib_target_headers + [
|
| 91 |
+
"@kernel_headers//:kernel_headers_directory",
|
| 92 |
+
"@llvm//sanitizers:sanitizers_headers_include_search_directory",
|
| 93 |
+
] + select({
|
| 94 |
+
@@ -324,13 +329,7 @@ def declare_llvm_targets(*, suffix = ""):
|
| 95 |
+
name = "windows_target_headers",
|
| 96 |
+
srcs = [
|
| 97 |
+
":builtin_resource_dir",
|
| 98 |
+
- ] + select({
|
| 99 |
+
- "@llvm//toolchain:runtimes_all": [
|
| 100 |
+
- "@llvm//runtimes/cxxstdlib:headers_include_search_directory",
|
| 101 |
+
- "@llvm//runtimes/cxxstdlib:abi_headers_include_search_directory",
|
| 102 |
+
- ],
|
| 103 |
+
- "//conditions:default": [],
|
| 104 |
+
- }) + [
|
| 105 |
+
+ ] + default_cxxstdlib_target_headers + [
|
| 106 |
+
"@mingw//:mingw_generated_headers_crt_directory",
|
| 107 |
+
"@mingw//:mingw_w64_headers_include_directory",
|
| 108 |
+
"@mingw//:mingw_w64_headers_crt_directory",
|
patches/llvm_windows_arm64_powl.patch
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# What: provide and link MinGW's ARM64 long-double `powl` compatibility shim.
|
| 2 |
+
# Why: Windows ARM64 uses 64-bit long doubles, but LLVM's Bazel source list
|
| 3 |
+
# omits the upstream `powl -> pow` implementation and its default link flags
|
| 4 |
+
# omit the MinGW extension archive that owns the shim.
|
| 5 |
+
|
| 6 |
+
diff --git a/runtimes/mingw/crt_sources.bzl b/runtimes/mingw/crt_sources.bzl
|
| 7 |
+
--- a/runtimes/mingw/crt_sources.bzl
|
| 8 |
+
+++ b/runtimes/mingw/crt_sources.bzl
|
| 9 |
+
@@ -420,4 +420,5 @@
|
| 10 |
+
MINGWEX_ARM64_SRCS = [
|
| 11 |
+
+ "math/arm-common/powl.c",
|
| 12 |
+
"math/sincos.c",
|
| 13 |
+
"math/sincosf.c",
|
| 14 |
+
"math/arm-common/sincosl.c",
|
| 15 |
+
diff --git a/toolchain/args/windows/BUILD.bazel b/toolchain/args/windows/BUILD.bazel
|
| 16 |
+
--- a/toolchain/args/windows/BUILD.bazel
|
| 17 |
+
+++ b/toolchain/args/windows/BUILD.bazel
|
| 18 |
+
@@ -45,5 +45,6 @@ cc_args(
|
| 19 |
+
args = [
|
| 20 |
+
"-L{mingw_import_library_search_path}",
|
| 21 |
+
"-L{mingw_crt_library_search_path}",
|
| 22 |
+
-
|
| 23 |
+
+ "-lmingwex",
|
| 24 |
+
+
|
| 25 |
+
# Clang will respect the user's chosen CRT variant.
|
patches/llvm_windows_mingw_compat.patch
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# What: provide MinGW compatibility archives required by Windows gnullvm links.
|
| 2 |
+
# Why: clang and prebuilt MSVC archives can request these names even though the
|
| 3 |
+
# hermetic MinGW runtime does not publish them by default.
|
| 4 |
+
|
| 5 |
+
diff --git a/runtimes/mingw/BUILD.bazel b/runtimes/mingw/BUILD.bazel
|
| 6 |
+
--- a/runtimes/mingw/BUILD.bazel
|
| 7 |
+
+++ b/runtimes/mingw/BUILD.bazel
|
| 8 |
+
@@ -386,14 +386,42 @@ stub_library(
|
| 9 |
+
name = "stdc++",
|
| 10 |
+
)
|
| 11 |
+
|
| 12 |
+
+# Clang may inject -lssp and -lssp_nonshared for windows-gnu links.
|
| 13 |
+
+# Provide compatibility archives in the MinGW runtime search directory.
|
| 14 |
+
+stub_library(
|
| 15 |
+
+ name = "ssp",
|
| 16 |
+
+)
|
| 17 |
+
+
|
| 18 |
+
+stub_library(
|
| 19 |
+
+ name = "ssp_nonshared",
|
| 20 |
+
+)
|
| 21 |
+
+
|
| 22 |
+
+# Prebuilt MSVC archives can carry /DEFAULTLIB:libcmt.lib and
|
| 23 |
+
+# /DEFAULTLIB:oldnames.lib directives. Provide MinGW-compatible stand-ins so
|
| 24 |
+
+# lld can satisfy the lookup under windows-gnullvm.
|
| 25 |
+
+stub_library(
|
| 26 |
+
+ name = "libcmt",
|
| 27 |
+
+)
|
| 28 |
+
+
|
| 29 |
+
+copy_file(
|
| 30 |
+
+ name = "oldnames",
|
| 31 |
+
+ src = ":moldname",
|
| 32 |
+
+ out = "liboldnames.a",
|
| 33 |
+
+ visibility = ["//visibility:public"],
|
| 34 |
+
+)
|
| 35 |
+
+
|
| 36 |
+
copy_to_directory(
|
| 37 |
+
name = "mingw_crt_library_search_directory",
|
| 38 |
+
srcs = [
|
| 39 |
+
+ ":libcmt",
|
| 40 |
+
":m",
|
| 41 |
+
":mingw32",
|
| 42 |
+
":mingwex",
|
| 43 |
+
":moldname",
|
| 44 |
+
+ ":oldnames",
|
| 45 |
+
":pthread",
|
| 46 |
+
+ ":ssp",
|
| 47 |
+
+ ":ssp_nonshared",
|
| 48 |
+
":stdc++",
|
| 49 |
+
":uuid",
|
| 50 |
+
":winpthread",
|
patches/ring_windows_msvc_include_dirs.patch
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/build.rs b/build.rs
|
| 2 |
+
--- a/build.rs
|
| 3 |
+
+++ b/build.rs
|
| 4 |
+
@@ -346,7 +346,27 @@
|
| 5 |
+
// we want to optimize for minimizing the build tools required: No Perl,
|
| 6 |
+
// no nasm, etc.
|
| 7 |
+
let generated_dir = if !is_git {
|
| 8 |
+
- c_root_dir.join(PREGENERATED)
|
| 9 |
+
+ let pregenerated_dir = c_root_dir.join(PREGENERATED);
|
| 10 |
+
+ if target.os == WINDOWS && target.env == "msvc"
|
| 11 |
+
+ {
|
| 12 |
+
+ let mut pending = vec![(pregenerated_dir.clone(), out_dir.clone())];
|
| 13 |
+
+ while let Some((src_dir, dst_dir)) = pending.pop() {
|
| 14 |
+
+ fs::create_dir_all(&dst_dir).unwrap();
|
| 15 |
+
+ for entry in fs::read_dir(&src_dir).unwrap() {
|
| 16 |
+
+ let entry = entry.unwrap();
|
| 17 |
+
+ let src_path = entry.path();
|
| 18 |
+
+ let dst_path = dst_dir.join(entry.file_name());
|
| 19 |
+
+ if entry.file_type().unwrap().is_dir() {
|
| 20 |
+
+ pending.push((src_path, dst_path));
|
| 21 |
+
+ } else {
|
| 22 |
+
+ fs::copy(&src_path, &dst_path).unwrap();
|
| 23 |
+
+ }
|
| 24 |
+
+ }
|
| 25 |
+
+ }
|
| 26 |
+
+ out_dir.clone()
|
| 27 |
+
+ } else {
|
| 28 |
+
+ pregenerated_dir
|
| 29 |
+
+ }
|
| 30 |
+
} else {
|
| 31 |
+
generate_sources_and_preassemble(
|
| 32 |
+
&out_dir,
|
| 33 |
+
@@ -561,7 +581,13 @@
|
| 34 |
+
let compiler = c.get_compiler();
|
| 35 |
+
// FIXME: On Windows AArch64 we currently must use Clang to compile C code
|
| 36 |
+
let compiler = if target.os == WINDOWS && target.arch == AARCH64 && !compiler.is_like_clang() {
|
| 37 |
+
- let _ = c.compiler("clang");
|
| 38 |
+
+ // Keep inherited MSVC flags compatible with the selected Clang driver.
|
| 39 |
+
+ let clang = if compiler.is_like_msvc() {
|
| 40 |
+
+ "clang-cl"
|
| 41 |
+
+ } else {
|
| 42 |
+
+ "clang"
|
| 43 |
+
+ };
|
| 44 |
+
+ let _ = c.compiler(clang);
|
| 45 |
+
c.get_compiler()
|
| 46 |
+
} else {
|
| 47 |
+
compiler
|
| 48 |
+
@@ -569,6 +595,15 @@
|
| 49 |
+
|
| 50 |
+
let _ = c.include(c_root_dir.join("include"));
|
| 51 |
+
let _ = c.include(include_dir);
|
| 52 |
+
+ let _ = c.include(c_root_dir.join("third_party").join("fiat"));
|
| 53 |
+
+ if compiler.is_like_msvc() {
|
| 54 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("curve25519"));
|
| 55 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("limbs"));
|
| 56 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("fipsmodule").join("aes"));
|
| 57 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("fipsmodule").join("bn"));
|
| 58 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("fipsmodule").join("ec"));
|
| 59 |
+
+ let _ = c.include(c_root_dir.join("crypto").join("poly1305"));
|
| 60 |
+
+ }
|
| 61 |
+
for f in cpp_flags(&compiler) {
|
| 62 |
+
let _ = c.flag(f);
|
| 63 |
+
}
|
| 64 |
+
diff --git a/crypto/curve25519/curve25519.c b/crypto/curve25519/curve25519.c
|
| 65 |
+
index 99d7d7fbb..2f69ad560 100644
|
| 66 |
+
--- a/crypto/curve25519/curve25519.c
|
| 67 |
+
+++ b/crypto/curve25519/curve25519.c
|
| 68 |
+
@@ -47,11 +47,11 @@
|
| 69 |
+
#if defined(__GNUC__)
|
| 70 |
+
#pragma GCC diagnostic ignored "-Wpedantic"
|
| 71 |
+
#endif
|
| 72 |
+
-#include "../../third_party/fiat/curve25519_64.h"
|
| 73 |
+
+#include "curve25519_64.h"
|
| 74 |
+
#elif defined(OPENSSL_64_BIT)
|
| 75 |
+
-#include "../../third_party/fiat/curve25519_64_msvc.h"
|
| 76 |
+
+#include "curve25519_64_msvc.h"
|
| 77 |
+
#else
|
| 78 |
+
-#include "../../third_party/fiat/curve25519_32.h"
|
| 79 |
+
+#include "curve25519_32.h"
|
| 80 |
+
#endif
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
diff --git a/crypto/curve25519/curve25519_64_adx.c b/crypto/curve25519/curve25519_64_adx.c
|
| 84 |
+
index 88964a9dd..b660f55f4 100644
|
| 85 |
+
--- a/crypto/curve25519/curve25519_64_adx.c
|
| 86 |
+
+++ b/crypto/curve25519/curve25519_64_adx.c
|
| 87 |
+
@@ -19,5 +19,5 @@
|
| 88 |
+
#pragma GCC diagnostic ignored "-Wpedantic"
|
| 89 |
+
#pragma GCC diagnostic ignored "-Wsign-conversion"
|
| 90 |
+
|
| 91 |
+
-#include "../../third_party/fiat/curve25519_64_adx.h"
|
| 92 |
+
+#include "curve25519_64_adx.h"
|
| 93 |
+
#endif
|
| 94 |
+
diff --git a/crypto/fipsmodule/ec/p256.c b/crypto/fipsmodule/ec/p256.c
|
| 95 |
+
index 0117916da..4224d1192 100644
|
| 96 |
+
--- a/crypto/fipsmodule/ec/p256.c
|
| 97 |
+
+++ b/crypto/fipsmodule/ec/p256.c
|
| 98 |
+
@@ -50,11 +50,11 @@
|
| 99 |
+
#if defined(__GNUC__)
|
| 100 |
+
#pragma GCC diagnostic ignored "-Wpedantic"
|
| 101 |
+
#endif
|
| 102 |
+
-#include "../../../third_party/fiat/p256_64.h"
|
| 103 |
+
+#include "p256_64.h"
|
| 104 |
+
#elif defined(OPENSSL_64_BIT)
|
| 105 |
+
-#include "../../../third_party/fiat/p256_64_msvc.h"
|
| 106 |
+
+#include "p256_64_msvc.h"
|
| 107 |
+
#else
|
| 108 |
+
-#include "../../../third_party/fiat/p256_32.h"
|
| 109 |
+
+#include "p256_32.h"
|
| 110 |
+
#endif
|
| 111 |
+
|
| 112 |
+
|
patches/rules_cc_rusty_v8_custom_libcxx.patch
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/cc/cc_library.bzl b/cc/cc_library.bzl
|
| 2 |
+
index c45c5fd954..2e3a01f12a 100644
|
| 3 |
+
--- a/cc/cc_library.bzl
|
| 4 |
+
+++ b/cc/cc_library.bzl
|
| 5 |
+
@@ -15,5 +15,39 @@
|
| 6 |
+
|
| 7 |
+
load("@cc_compatibility_proxy//:proxy.bzl", _cc_library = "cc_library")
|
| 8 |
+
|
| 9 |
+
+_RUSTY_V8_CUSTOM_LIBCXX_COPTS = select({
|
| 10 |
+
+ "@@//third_party/v8:use_rusty_v8_custom_libcxx": [
|
| 11 |
+
+ "-nostdinc++",
|
| 12 |
+
+ "-D_LIBCPP_DISABLE_VISIBILITY_ANNOTATIONS",
|
| 13 |
+
+ "-D_LIBCPP_HARDENING_MODE=_LIBCPP_HARDENING_MODE_EXTENSIVE",
|
| 14 |
+
+ "-D_LIBCPP_INSTRUMENTED_WITH_ASAN=0",
|
| 15 |
+
+ "-D_LIBCXXABI_DISABLE_VISIBILITY_ANNOTATIONS",
|
| 16 |
+
+ ],
|
| 17 |
+
+ "//conditions:default": [],
|
| 18 |
+
+})
|
| 19 |
+
+
|
| 20 |
+
+_RUSTY_V8_CUSTOM_LIBCXX_DEPS = select({
|
| 21 |
+
+ "@@//third_party/v8:use_rusty_v8_custom_libcxx": [
|
| 22 |
+
+ "@@//third_party/v8:rusty_v8_custom_libcxx_headers",
|
| 23 |
+
+ ],
|
| 24 |
+
+ "//conditions:default": [],
|
| 25 |
+
+})
|
| 26 |
+
+
|
| 27 |
+
+_RUSTY_V8_CUSTOM_LIBCXX_REPOS = [
|
| 28 |
+
+ # V8 and ICU are patched in-tree. These external C++ dependencies still
|
| 29 |
+
+ # build inside the artifact closure once the LLVM toolchain's default
|
| 30 |
+
+ # libc++ headers are suppressed.
|
| 31 |
+
+ "@abseil-cpp+",
|
| 32 |
+
+ "@v8++http_archive+highway",
|
| 33 |
+
+ "@v8++http_archive+simdutf",
|
| 34 |
+
+]
|
| 35 |
+
+
|
| 36 |
+
+def _should_use_rusty_v8_custom_libcxx():
|
| 37 |
+
+ return native.repository_name() in _RUSTY_V8_CUSTOM_LIBCXX_REPOS
|
| 38 |
+
+
|
| 39 |
+
def cc_library(**kwargs):
|
| 40 |
+
+ if _should_use_rusty_v8_custom_libcxx():
|
| 41 |
+
+ kwargs["copts"] = (kwargs.get("copts", []) or []) + _RUSTY_V8_CUSTOM_LIBCXX_COPTS
|
| 42 |
+
+ kwargs["deps"] = (kwargs.get("deps", []) or []) + _RUSTY_V8_CUSTOM_LIBCXX_DEPS
|
| 43 |
+
+ kwargs["features"] = (kwargs.get("features", []) or []) + ["-module_maps"]
|
| 44 |
+
_cc_library(**kwargs)
|
patches/rules_foreign_cc_make_cppflags.patch
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/foreign_cc/built_tools/make_build.bzl b/foreign_cc/built_tools/make_build.bzl
|
| 2 |
+
--- a/foreign_cc/built_tools/make_build.bzl
|
| 3 |
+
+++ b/foreign_cc/built_tools/make_build.bzl
|
| 4 |
+
@@ -114,6 +114,11 @@
|
| 5 |
+
"ARFLAGS": _join_flags_list(ctx.workspace_name, arflags),
|
| 6 |
+
"CC": absolute_cc,
|
| 7 |
+
"CFLAGS": _join_flags_list(ctx.workspace_name, non_sysroot_cflags),
|
| 8 |
+
+ # Configure's preprocessor checks need the declared header paths too.
|
| 9 |
+
+ "CPPFLAGS": " ".join([
|
| 10 |
+
+ "$${CPPFLAGS:-}$$",
|
| 11 |
+
+ _join_flags_list(ctx.workspace_name, non_sysroot_cflags),
|
| 12 |
+
+ ]).strip(),
|
| 13 |
+
"LD": absolute_ld,
|
| 14 |
+
"LDFLAGS": _join_flags_list(ctx.workspace_name, non_sysroot_ldflags),
|
| 15 |
+
})
|
patches/rules_foreign_cc_make_msvc_stdint.patch
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/foreign_cc/built_tools/make_build.bzl b/foreign_cc/built_tools/make_build.bzl
|
| 2 |
+
--- a/foreign_cc/built_tools/make_build.bzl
|
| 3 |
+
+++ b/foreign_cc/built_tools/make_build.bzl
|
| 4 |
+
@@ -35,6 +35,9 @@
|
| 5 |
+
build_str += " gcc"
|
| 6 |
+
dist_dir = "GccRel"
|
| 7 |
+
else:
|
| 8 |
+
+ # Modern MSVC provides stdint.h, including the intmax_t types that
|
| 9 |
+
+ # Make's legacy Windows config otherwise replaces with macros.
|
| 10 |
+
+ build_str = 'export CL="$${CL:-}$$ /DHAVE_STDINT_H=1"\n' + build_str
|
| 11 |
+
dist_dir = "WinRel"
|
| 12 |
+
|
| 13 |
+
script = [
|
patches/rules_foreign_cc_make_xcompile.patch
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
diff --git a/toolchains/private/BUILD.bazel b/toolchains/private/BUILD.bazel
|
| 2 |
+
--- a/toolchains/private/BUILD.bazel
|
| 3 |
+
+++ b/toolchains/private/BUILD.bazel
|
| 4 |
+
@@ -23,5 +23,6 @@
|
| 5 |
+
make_tool(
|
| 6 |
+
name = "make_tool",
|
| 7 |
+
srcs = "@gnumake_src//:all_srcs",
|
| 8 |
+
+ configure_xcompile = True,
|
| 9 |
+
tags = ["manual"],
|
| 10 |
+
)
|