From a99f773a3441d29c3ba1923a95c8d905a6673585 Mon Sep 17 00:00:00 2001 From: Kyle Date: Wed, 25 Mar 2026 15:46:57 +0800 Subject: [PATCH] Add code signing support for XCFrameworks (#844) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add a reusable codesign-xcframework action that imports a signing certificate and signs xcframeworks. Integrate it into the build and release workflows. Changes: - .github/actions/codesign-xcframework — new reusable composite action that sets up a CI keychain, imports a .p12 cert, trusts it for code signing, and signs xcframeworks - .github/actions/build-xcframework — call codesign action after build - .github/workflows/release.yml — pass signing secrets - .github/workflows/build_xcframework.yml — pass signing secrets The signing certificate (self-signed, "OpenSwiftUI") is stored as org-level GitHub secrets (SIGNING_CERTIFICATE_BASE_64 and SIGNING_CERTIFICATE_PASSWORD). Signing is gracefully skipped when secrets are not available. --- .github/actions/build-xcframework/action.yml | 22 ++++ .../actions/codesign-xcframework/action.yml | 104 ++++++++++++++++++ .github/workflows/build_xcframework.yml | 3 + .github/workflows/release.yml | 2 + 4 files changed, 131 insertions(+) create mode 100644 .github/actions/codesign-xcframework/action.yml diff --git a/.github/actions/build-xcframework/action.yml b/.github/actions/build-xcframework/action.yml index bdfdf5507..b43cb59cc 100644 --- a/.github/actions/build-xcframework/action.yml +++ b/.github/actions/build-xcframework/action.yml @@ -10,6 +10,14 @@ inputs: description: 'Tag name for release URLs (empty for manual builds)' required: false default: '' + signing-certificate-base64: + description: 'Base64-encoded .p12 signing certificate' + required: false + default: '' + signing-certificate-password: + description: 'Password for the .p12 signing certificate' + required: false + default: '' outputs: body: @@ -29,6 +37,20 @@ runs: - name: Build XCFrameworks run: Scripts/build_xcframework.sh OpenSwiftUI shell: bash + - name: Code sign XCFrameworks + if: ${{ inputs.signing-certificate-base64 != '' }} + uses: ./.github/actions/codesign-xcframework + with: + signing-certificate-base64: ${{ inputs.signing-certificate-base64 }} + signing-certificate-password: ${{ inputs.signing-certificate-password }} + xcframework-paths: >- + build/OpenSwiftUI.xcframework + build/OpenSwiftUICore.xcframework + build/OpenAttributeGraphShims.xcframework + build/OpenCoreGraphicsShims.xcframework + build/OpenObservation.xcframework + build/OpenQuartzCoreShims.xcframework + build/OpenRenderBoxShims.xcframework - name: Compute Checksums and Generate Summary id: checksums shell: bash diff --git a/.github/actions/codesign-xcframework/action.yml b/.github/actions/codesign-xcframework/action.yml new file mode 100644 index 000000000..b9c818288 --- /dev/null +++ b/.github/actions/codesign-xcframework/action.yml @@ -0,0 +1,104 @@ +name: 'Code Sign XCFramework' +description: 'Import a signing certificate and code sign xcframeworks' + +inputs: + signing-certificate-base64: + description: 'Base64-encoded .p12 signing certificate' + required: true + signing-certificate-password: + description: 'Password for the .p12 signing certificate' + required: true + signing-identity: + description: 'Common Name of the signing certificate' + required: false + default: 'OpenSwiftUI' + xcframework-paths: + description: 'Space-separated paths to xcframeworks to sign' + required: true + +runs: + using: 'composite' + steps: + - name: Set up signing keychain + shell: bash + env: + CERTIFICATE_BASE64: ${{ inputs.signing-certificate-base64 }} + CERTIFICATE_PASSWORD: ${{ inputs.signing-certificate-password }} + SIGNING_IDENTITY: ${{ inputs.signing-identity }} + run: | + KEYCHAIN_PASSWORD="ci_signing_temp" + KEYCHAIN_NAME="ci_signing.keychain-db" + KEYCHAIN_PATH="$HOME/Library/Keychains/$KEYCHAIN_NAME" + + # Save original keychain search list + ORIGINAL_KEYCHAINS=$(security list-keychains -d user | sed 's/^ *"//;s/" *$//' | tr '\n' ' ') + + # Clean up any existing CI keychain + security delete-keychain "$KEYCHAIN_PATH" 2>/dev/null || true + rm -f "$KEYCHAIN_PATH" 2>/dev/null || true + + # Create and unlock keychain + security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" + + # NOTE: Do NOT call security set-keychain-settings here. + # It corrupts keychain state in non-interactive CI sessions. + + # Add to search list (prepend CI keychain, keep existing) + security list-keychains -d user -s "$KEYCHAIN_PATH" $ORIGINAL_KEYCHAINS + + # Decode and import certificate + # Use -A to allow all applications access without prompting. + # Per-app ACL entries (-T) cause "User interaction is not allowed" prompts. + echo "$CERTIFICATE_BASE64" | base64 -d > /tmp/signing.p12 + security import /tmp/signing.p12 -k "$KEYCHAIN_PATH" -P "$CERTIFICATE_PASSWORD" -A + rm -f /tmp/signing.p12 + + # Set partition list to allow codesign access without prompting + security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH" 2>/dev/null || true + + # Try to trust certificate for code signing (works on self-hosted runners) + security find-certificate -c "$SIGNING_IDENTITY" -p "$KEYCHAIN_PATH" > /tmp/signing-cert.pem + sudo security add-trusted-cert -d -r trustRoot -p codeSign /tmp/signing-cert.pem 2>/dev/null || true + rm -f /tmp/signing-cert.pem + + # Debug: inspect keychain contents + echo "=== Certificates in keychain ===" + security find-certificate -a -c "$SIGNING_IDENTITY" -Z "$KEYCHAIN_PATH" 2>&1 | head -5 || true + echo "=== All identities (no policy filter) ===" + security find-identity "$KEYCHAIN_PATH" || true + echo "=== All identities (codesigning policy) ===" + security find-identity -p codesigning "$KEYCHAIN_PATH" || true + echo "=== Valid codesigning identities ===" + security find-identity -v -p codesigning "$KEYCHAIN_PATH" || true + + - name: Code sign xcframeworks + shell: bash + env: + SIGNING_IDENTITY: ${{ inputs.signing-identity }} + XCFRAMEWORK_PATHS: ${{ inputs.xcframework-paths }} + run: | + KEYCHAIN_PATH="$HOME/Library/Keychains/ci_signing.keychain-db" + # Try: trusted codesigning → all codesigning → any identity (no policy) + # Note: || true is needed because grep returns 1 on no match, which kills -e -o pipefail + SIGNING_HASH=$(security find-identity -v -p codesigning "$KEYCHAIN_PATH" 2>/dev/null | grep "$SIGNING_IDENTITY" | head -1 | awk '{print $2}' || true) + if [ -z "$SIGNING_HASH" ]; then + SIGNING_HASH=$(security find-identity -p codesigning "$KEYCHAIN_PATH" 2>/dev/null | grep "$SIGNING_IDENTITY" | head -1 | awk '{print $2}' || true) + fi + if [ -z "$SIGNING_HASH" ]; then + SIGNING_HASH=$(security find-identity "$KEYCHAIN_PATH" 2>/dev/null | grep "$SIGNING_IDENTITY" | head -1 | awk '{print $2}' || true) + fi + if [ -z "$SIGNING_HASH" ]; then + echo "::error::Signing identity '$SIGNING_IDENTITY' not found" + exit 1 + fi + echo "Signing identity: $SIGNING_HASH ($SIGNING_IDENTITY)" + for fw_path in $XCFRAMEWORK_PATHS; do + if [ -d "$fw_path" ]; then + echo "Signing $fw_path..." + codesign --force --timestamp -v --sign "$SIGNING_HASH" --keychain "$KEYCHAIN_PATH" "$fw_path" + else + echo "::warning::$fw_path not found, skipping" + fi + done + echo "Code signing completed." diff --git a/.github/workflows/build_xcframework.yml b/.github/workflows/build_xcframework.yml index a6e30a55a..8b1ba45f3 100644 --- a/.github/workflows/build_xcframework.yml +++ b/.github/workflows/build_xcframework.yml @@ -15,6 +15,9 @@ jobs: - uses: actions/checkout@v4 - name: Build XCFrameworks uses: ./.github/actions/build-xcframework + with: + signing-certificate-base64: ${{ secrets.SIGNING_CERTIFICATE_BASE_64 }} + signing-certificate-password: ${{ secrets.SIGNING_CERTIFICATE_PASSWORD }} - name: Zip XCFrameworks run: cd build && zip -ry xcframeworks.zip *.xcframework - name: Upload XCFrameworks diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 3175ba7fb..688f2cb04 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -23,6 +23,8 @@ jobs: uses: ./.github/actions/build-xcframework with: tag-name: ${{ github.ref_name }} + signing-certificate-base64: ${{ secrets.SIGNING_CERTIFICATE_BASE_64 }} + signing-certificate-password: ${{ secrets.SIGNING_CERTIFICATE_PASSWORD }} - name: Create Release uses: ncipollo/release-action@v1 with: