Files
amethyst/.github/actions/import-macos-cert/action.yml
T
Vitor PamplonaandClaude Opus 4.8 61defaa2e9 fix(ci): resolve the exact Developer ID common name for Compose signing
v1.12.4 got the keychain right ("...in keychain [/Users/.../amethyst-signing
.keychain-db]") but createReleaseDistributable still failed with "Could not
find certificate". Different layer of the same problem:

Compose's MacSigner maps its identity to a cert by running `security
find-certificate -c <identity>`, prepending "Developer ID Application: " when
the identity doesn't already start with it. `codesign --sign` (used by the
signMacJarNatives task, which succeeds in the same job) instead matches a
SHA-1 hash OR any common-name substring. So a MAC_SIGN_IDENTITY secret that is
a fingerprint or a team-ID/partial name signs fine with codesign but, once
prefixed by Compose, is not a substring of the cert's common name -> zero
matches -> failure.

Reproduced locally against the real Developer ID cert:
  find-certificate -c "Developer ID Application: <TEAMID>"      -> 0 matches
  find-certificate -c "Developer ID Application: <full CN>"     -> 1 match

Fix: import-macos-cert now resolves the certificate's full "Developer ID
Application: NAME (TEAMID)" common name from the keychain (via find-identity)
and exposes it as an `identity` output. The desktop build feeds that to
Compose's signing.identity, falling back to the raw secret if resolution
fails. Independent of whatever form the secret takes. The amy CLI leg keeps
using bare codesign with the secret directly and is unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-19 12:54:56 -04:00

91 lines
4.6 KiB
YAML

name: Import macOS Developer ID certificate
description: >
Import a Developer ID Application certificate (base64-encoded .p12) into a
throwaway keychain so codesign/jpackage can find it during the job. Soft:
when no certificate is supplied it is a no-op and reports signing=false, so
callers build UNSIGNED artifacts exactly as before.
inputs:
certificate-p12-base64:
description: Base64 of the Developer ID Application .p12 (cert + private key)
required: true
certificate-password:
description: Password used when the .p12 was exported
required: true
outputs:
signing:
description: "'true' if a certificate was imported, else 'false'"
value: ${{ steps.import.outputs.signing }}
keychain:
description: >
Absolute path of the throwaway keychain holding the imported cert (empty
when signing=false). Pass to tools that need an explicit keychain — e.g.
Compose's macOS signing, whose certificate lookup doesn't resolve the
search list reliably on CI runners the way bare `codesign` does.
value: ${{ steps.import.outputs.keychain }}
identity:
description: >
The imported certificate's full "Developer ID Application: NAME (TEAMID)"
common name, resolved from the keychain (empty when signing=false or if it
could not be parsed). `codesign` accepts a SHA-1 hash or a CN substring,
but Compose's MacSigner runs `security find-certificate -c <identity>`
after prepending "Developer ID Application: " — so it only works with the
exact common name. Feed this to Compose's `signing.identity`.
value: ${{ steps.import.outputs.identity }}
runs:
using: composite
steps:
- id: import
shell: bash
env:
CERT_P12: ${{ inputs.certificate-p12-base64 }}
CERT_PASSWORD: ${{ inputs.certificate-password }}
run: |
set -euo pipefail
if [[ -z "${CERT_P12:-}" ]]; then
echo "::notice::No macOS signing certificate configured — artifacts will be UNSIGNED."
echo "signing=false" >> "$GITHUB_OUTPUT"
exit 0
fi
KEYCHAIN="$RUNNER_TEMP/amethyst-signing.keychain-db"
KEYCHAIN_PWD="$(openssl rand -base64 24)"
CERT_PATH="$RUNNER_TEMP/developer_id.p12"
security create-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PWD" "$KEYCHAIN"
echo "$CERT_P12" | base64 --decode > "$CERT_PATH"
security import "$CERT_PATH" -P "$CERT_PASSWORD" \
-k "$KEYCHAIN" -T /usr/bin/codesign -T /usr/bin/productsign
# Let codesign use the private key without an interactive UI prompt.
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PWD" "$KEYCHAIN" >/dev/null
# Prepend our keychain to the user search list so codesign sees it.
security list-keychains -d user -s "$KEYCHAIN" \
$(security list-keychains -d user | sed -e 's/[\"[:space:]]//g')
# Also make it the default keychain. Bare `codesign` resolves identities
# via the search list, but some tools (Compose's MacSigner runs
# `security find-certificate` to map the identity to a cert) don't find
# it on the search list alone on these runners. Callers that need an
# explicit keychain can read the `keychain` output below.
security default-keychain -d user -s "$KEYCHAIN"
rm -f "$CERT_PATH"
# Resolve the certificate's exact common name. Compose's MacSigner maps
# its identity to a cert via `security find-certificate -c <name>` (after
# prepending "Developer ID Application: " when absent), so it needs the
# full CN — a SHA-1 hash or partial name in the secret signs fine with
# bare `codesign` but yields "Could not find certificate ...". Pull the
# canonical name straight from the keychain so the caller is independent
# of whatever form the MAC_SIGN_IDENTITY secret takes.
IDENTITY_NAME="$(security find-identity -v -p codesigning "$KEYCHAIN" \
| grep -o '"Developer ID Application:[^"]*"' | head -1 | tr -d '"' || true)"
echo "signing=true" >> "$GITHUB_OUTPUT"
echo "keychain=$KEYCHAIN" >> "$GITHUB_OUTPUT"
echo "identity=$IDENTITY_NAME" >> "$GITHUB_OUTPUT"
if [[ -n "$IDENTITY_NAME" ]]; then
echo "::notice::Resolved signing identity: $IDENTITY_NAME"
else
echo "::warning::Could not resolve a 'Developer ID Application' identity from the keychain; callers will fall back to the MAC_SIGN_IDENTITY secret."
fi