GitHub ActionsのmacOSランナーでアプリに署名するには、Developer ID Applicationの証明書を、ランナーのキーチェーンにインポートする必要がある。ランナーは毎回新しい環境なので、ワークフローの実行ごとに、証明書をインポートする。

証明書と秘密鍵をまとめた.p12を、base64にしてGitHubのSecretsに登録しておく。ワークフローでは、Secretsから.p12を復元し、一時的なキーチェーンにインポートする。

- name: Import signing certificate
  env:
    CERTIFICATES_P12: ${{ secrets.CERTIFICATES_P12 }}
    CERTIFICATES_P12_PASSWORD: ${{ secrets.CERTIFICATES_P12_PASSWORD }}
  run: |
    echo "$CERTIFICATES_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
    security create-keychain -p actions "$RUNNER_TEMP/temp.keychain-db"
    security default-keychain -s "$RUNNER_TEMP/temp.keychain-db"
    security unlock-keychain -p actions "$RUNNER_TEMP/temp.keychain-db"
    security import "$RUNNER_TEMP/certificate.p12" -k "$RUNNER_TEMP/temp.keychain-db" -P "$CERTIFICATES_P12_PASSWORD" -T /usr/bin/codesign
    security set-key-partition-list -S apple-tool:,apple: -s -k actions "$RUNNER_TEMP/temp.keychain-db"

実行例は、Xcode 27.0のランナー(runs-on: xcode-27)のもの。.p12の作り方は、Developer ID証明書の記事で紹介している。

参考: 【Developer ID】アプリ配布用の署名証明書を作成する

.p12をSecretsに登録する

.p12は、バイナリのファイルである。base64の文字列にして、Secretsに登録する。.p12のパスワードも、別のSecretsに登録する。

$ /usr/bin/base64 -i developer_id.p12 | tr -d '\n' | gh secret set CERTIFICATES_P12 --repo [ユーザー名]/[リポジトリ名]
$ tr -d '\n' < p12_password.txt | gh secret set CERTIFICATES_P12_PASSWORD --repo [ユーザー名]/[リポジトリ名]

gh secret setは、標準入力から値を読み取る。値をコマンドの引数に渡さないため、シェルの履歴に残らない。tr -d '\n'で、改行を取り除く。

base64には、macOS標準の/usr/bin/base64を使う。Homebrewでcoreutilsを入れていると、base64がGNU版になる場合がある。GNU版のbase64は、-Dを受け付けず、-iの意味も異なる。

$ which -a base64
/opt/homebrew/opt/coreutils/libexec/gnubin/base64
/usr/bin/base64

登録したSecretsは、名前と更新日時だけを確認できる。値は表示されない。

$ gh secret list --repo [ユーザー名]/[リポジトリ名]
CERTIFICATES_P12	[更新日時]
CERTIFICATES_P12_PASSWORD	[更新日時]

keychainのコマンドの意味

ワークフローの各コマンドの意味は、次のとおりである。

コマンド意味
base64 --decode > certificate.p12Secretsのbase64の文字列から、.p12を復元する
security create-keychain -p actions [パス]一時的なキーチェーンを、パスワードactionsで作成する
security default-keychain -s [パス]作成したキーチェーンを、既定のキーチェーンにする
security unlock-keychain -p actions [パス]キーチェーンのロックを解除する
security import [.p12] -k [パス] -P [パスワード] -T /usr/bin/codesign.p12をキーチェーンにインポートし、codesignのアクセスを許可する
security set-key-partition-list -S apple-tool:,apple: -s -k actions [パス]Appleのツール(codesignなど)が、ダイアログなしで秘密鍵を使えるようにする

base64 --decodeは、macOS標準の/usr/bin/base64が受け付けるオプションである。-Dでも復号できるが、--decodeであれば、GNU版でも同じように動く。

キーチェーンのパスワード(actions)は、一時的なキーチェーンの中だけで使う値である。ランナーが破棄されると、キーチェーンもなくなる。

$RUNNER_TEMPは、ジョブの間だけ使える一時ディレクトリである。キーチェーンと.p12を、リポジトリのディレクトリの外に置ける。

証明書がインポートされたことを確認する

インポートの後に、security find-identityで、署名に使える証明書を確認できる。

- name: Show identities
  run: security find-identity -v -p codesigning

ログには、インポートした証明書が表示される。

1 identity imported.
  1) [SHA-1ハッシュ] "Developer ID Application: [名前] ([Team ID])"
     1 valid identities found

このランナーのキーチェーンには、インポートした証明書だけが表示された。同じ名前の証明書が複数ある環境と違い、署名に使う証明書を取り違えない。

ランナーで署名できることは、小さなバイナリで確認できる。

- name: Sign a test binary
  run: |
    cp /bin/echo ./echo-test
    codesign -f -s "Developer ID Application" --timestamp --options runtime ./echo-test
    codesign -dv --verbose=2 ./echo-test 2>&1 | grep -E 'Authority|flags|Timestamp'
./echo-test: replacing existing signature
CodeDirectory v=20500 size=277 flags=0x10000(runtime) hashes=3+2 location=embedded
Authority=Developer ID Application: [名前] ([Team ID])
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=[署名した日時]

Secretsの値はログでマスクされる

GitHub Actionsのログでは、Secretsに登録した値が***に置き換えられる。envで渡したCERTIFICATES_P12などの値も、***と表示される。

env:
  CERTIFICATES_P12: ***
  CERTIFICATES_P12_PASSWORD: ***

Team IDをSecretsに登録している場合は、codesign -dvの出力に含まれるTeam IDも、***になる。

Authority=Developer ID Application: [名前] (***)

set-key-partition-listは省略しても動いた

security set-key-partition-listは、codesignが秘密鍵に、ダイアログなしでアクセスできるようにするコマンドである。GitHub Actionsのランナーで、このコマンドを省略して、.p12のインポートの後にcodesignを実行すると、署名に成功した。

- name: Import signing certificate without set-key-partition-list
  run: |
    echo "$CERTIFICATES_P12" | base64 --decode > "$RUNNER_TEMP/certificate.p12"
    security create-keychain -p actions "$RUNNER_TEMP/temp.keychain-db"
    security default-keychain -s "$RUNNER_TEMP/temp.keychain-db"
    security unlock-keychain -p actions "$RUNNER_TEMP/temp.keychain-db"
    security import "$RUNNER_TEMP/certificate.p12" -k "$RUNNER_TEMP/temp.keychain-db" -P "$CERTIFICATES_P12_PASSWORD" -T /usr/bin/codesign

ここまでの手順で、インポートとcodesignによる署名は、set-key-partition-listなしで成功した。xcodebuildによる署名での必要性は、確認していない。ローカルのMacや別の環境では必要になる場合があるため、一般的な手順として、set-key-partition-listも実行しておく。

キーチェーンを削除する

GitHubが提供するランナーは、ジョブごとに使い捨てである。キーチェーンを削除する必要はない。

セルフホストランナーなど、環境が使い回される場合は、ジョブの最後にキーチェーンを削除する。

- name: Delete keychain
  if: always()
  run: security delete-keychain "$RUNNER_TEMP/temp.keychain-db"

参考: 【macOS】CLIでカスタムKeyChainを削除する