
Tauri製macOSアプリのコード署名・公証・自動アップデート署名を自動化し、GitHub Releasesで配信する
はじめに
TauriはRust製のデスクトップアプリケーションフレームワークです。フロントエンドにOS標準WebViewを使うためWeb技術(HTML/CSS/JS)が利用でき、バックエンドはRustで書けます。Electronと比較してアプリの軽量化・高速化が図れることから、近年注目を集めているフレームワークです。
macOSでアプリを配布するにはAppleのコード署名と公証(notarization)が必要です。署名・公証されていないアプリはGatekeeperによって「xxxxは壊れているため開けません。」のようにブロックされ、ユーザーが起動できません。

これはmacOSはインターネットからダウンロードしたアプリに com.apple.quarantine 属性を付与し、Gatekeeperは起動時にこの属性を検出して、Appleの署名 + 公証がないアプリをブロックします。ワークアラウンドとして以下のコマンドで com.apple.quarantine 属性を除去すれば起動できますが、ユーザーにこの手順を求めるのは現実的ではありません。
xattr -cr /Applications/Agentoast.app
話は変わり、TauriにはUpdaterプラグインによる自動アップデート機能があり、将来的にこれを利用した自動アップデートの導入も見据えています。Updaterはアップデートバイナリの改ざん検知にminisign署名を使うため、本記事でその鍵ペア生成とCIへの組み込みまで済ませておきます。自動アップデートの実装自体は別記事でフォロー予定です。
本記事では、コード署名・公証済みのDMGとTauriUpdater署名済みアーティファクトをGitHub Actionsで生成、brew tapで配信するところまでを扱います。
前提
- Apple Developer Programに加入済みであること
私が加入した際の手順を紹介しておきます。
- このアプリはストアに配信しません
GitHub Release経由で配信します。種類として Developer ID Application に該当します。またbrew tap --cask で配信するため、CIにはbrew tapで配信する設定がされています。brew tapに関しては、記事の長さの都合上割愛するため、気になる場合は後述のリポジトリを参考にしてください。
-
パッケージマネージャはbunを利用しています。適宜読み替えて実行してください
-
本記事の内容はすべて以下のリポジトリで実際に運用しているもので、困った際はリポジトリを参考にしてください
Tauri Updater 署名用キーペアの生成
Tauri の Updater プラグインは、アップデートバイナリの改ざん検知に minisign 署名を使います。CI でビルドすると、署名ファイル(.app.tar.gz.sig)と、Updater がチェックするマニフェスト(latest.json)が GitHub Release に生成されます。ここで生成する鍵ペアは自動アップデート導入時に必要になるため、CI パイプラインと合わせて先に準備しておきます。
キーペアを生成します。パスワード設定推奨(※)です。初回であれば --force オプションは不要なので削って実行してください。
$ bun run tauri signer generate -w ~/.tauri/agentoast.key --force
$ tauri signer generate -w /Users/shuntaka/.tauri/agentoast.key --force
Please enter a password to protect the secret key.
Password:
Password (one more time):
Deriving a key from the password in order to encrypt the secret key...
Your keypair was generated successfully:
Private: /Users/shuntaka/.tauri/agentoast.key (Keep it secret!)
Public: /Users/shuntaka/.tauri/agentoast.key.pub
---------------------------
Environment variables used to sign:
- `TAURI_SIGNING_PRIVATE_KEY`: String of your private key
- `TAURI_SIGNING_PRIVATE_KEY_PATH`: Path to your private key file
- `TAURI_SIGNING_PRIVATE_KEY_PASSWORD`: Your private key password (optional if key has no password)
ATTENTION: If you lose your private key OR password, you'll not be able to sign your update package and updates will not work
公開鍵を src-tauri/tauri.conf.json に設定します。
$ PUBKEY=$(cat ~/.tauri/agentoast.key.pub) && \
jq --arg pk "$PUBKEY" '.bundle.createUpdaterArtifacts = true | .plugins = { updater: { pubkey: $pk, endpoints: ["https://github.com/shuntaka9576/agentoast/releases/latest/download/latest.json"] } }' src-tauri/tauri.conf.json > /tmp/tauri.conf.tmp && \
mv /tmp/tauri.conf.tmp src-tauri/tauri.conf.json
--- a/src-tauri/tauri.conf.json
+++ b/src-tauri/tauri.conf.json
@@ -39,6 +39,15 @@
"resources": [
"icons/tray-icon.png",
"icons/tray-icon-notification.png"
- ]
+ ],
+ "createUpdaterArtifacts": true
+ },
+ "plugins": {
+ "updater": {
+ "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IDA2MjFGMkExNTUwM0ZDOEYKUldTUC9BTlZvZkloQm5qU0JUTWRqbFZhZWZ0b2tLN0czTHo3RTJaZWpzM2NTSWVmRmdIZDgyVTIK",
+ "endpoints": [
+ "https://github.com/shuntaka9576/agentoast/releases/latest/download/latest.json"
+ ]
+ }
}
}
前述の通り、CIを動かして失敗するより、ローカルで署名が発行できるか確認するのが良いと思います。
$ export TAURI_SIGNING_PRIVATE_KEY="$(cat ~/.tauri/agentoast.key)"
$ export TAURI_SIGNING_PRIVATE_KEY_PASSWORD="<your-password>"
$ cargo make build-app
$ tauri build
Info Looking up installed tauri packages to check mismatched versions...
Running beforeBuildCommand `bun run build`
$ tsc && vite build
vite v7.3.1 building client environment for production...
✓ 1721 modules transformed.
dist/index.html 0.40 kB │ gzip: 0.27 kB
dist/assets/index-DvBhZcgk.css 18.02 kB │ gzip: 4.38 kB
dist/assets/index-CKAVV5DF.js 255.31 kB │ gzip: 82.75 kB
✓ built in 3.41s
Compiling agentoast-app v0.20.0 (/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/src-tauri)
Finished `release` profile [optimized] target(s) in 48.26s
Built application at: /Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/agentoast-app
Bundling Agentoast.app (/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app)
Bundling Agentoast_0.20.0_aarch64.dmg (/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/dmg/Agentoast_0.20.0_aarch64.dmg)
Running bundle_dmg.sh
Bundling /Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app.tar.gz (/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app.tar.gz)
Finished 2 bundles at:
/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app
/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/dmg/Agentoast_0.20.0_aarch64.dmg
/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app.tar.gz (updater)
Finished 1 updater signature at:
/Users/shuntaka/repos/github.com/shuntaka9576/agentoast/target/release/bundle/macos/Agentoast.app.tar.gz.sig
[cargo-make] INFO - Build Done in 74.47 seconds.
minisign 秘密鍵を紛失すると、既存ユーザーが自動アップデートできなくなります(公開鍵が旧バージョンのアプリにハードコードされているため)。以下の内容を1Passwordなどに保管推奨です。1Passwordにドラッグすればアイテムが作成されるので、そこについでにパスワードも設定すれば楽です。
- ~/.tauri/agentoast.key の内容(minisign 秘密鍵)
- 設定したパスワード
Apple Developer ID 証明書の準備
CSR の作成
キーチェーンアクセスを開き、メニューバーから証明書アシスタント>「認証局に証明書を要求」を選択します。

それぞれ以下を選択します。
- ユーザのメールアドレス: Apple ID のメールアドレス
- 通称: そのままでOK
- 要求の処理: 「ディスクに保存」を選択

デフォルトの CertificateSigningRequest.certSigningRequest という名前で保存します。

作成されていることを確認します。

Developer ID 証明書の作成
証明書リストを管理するページに遷移します。 https://developer.apple.com/account/resources/certificates/list です。

+ボタンを押下します。

今回はストア配信せず、GitHub Releaseから配信するため、Developer ID Application を選択 → Continue

Previous Sub-CA がデフォルトになっている場合もありますが、今回は古い macOS をサポートする必要がないので G2 Sub-CA を選択します。
| Sub-CA | 説明 |
|---|---|
| G2 Sub-CA | 新しい中間認証局。SHA-256 ベース。今後の標準 |
| Previous Sub-CA | 古い中間認証局。macOS 10.12 以前との互換性用 |

続いて先ほど作成した CertificateSigningRequest.certSigningRequest ファイルをアップロードします。

アップロードが完了したら以下のような画面になり、Continueが活性化するので押下して、次に進みます。

証明書が作成され、Download で .cer ファイルを保存します。

G2 中間証明書がキーチェーンに登録されているか確認します。キーチェーンの右上の検索ボックスに Developer ID Certification Authority と入力します。
以下の画像で表示されたのは1件でした。有効期限が 2027/02/02 の場合 Previous Sub-CA のもののため、G2 の中間証明書のインストールが必要です。

以下のコマンドでG2の中間証明書をインストールします。
curl -O https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer && open DeveloperIDG2CA.cer
キーチェーンへの追加ダイアログが表示されるので、追加します。

インストールできました。

先ほど Apple Developer Portalで作成し、ダウンロードした .cer ファイルをダブルクリックします。

キーチェーンに証明書を登録します。

登録した証明書を確認します。コード署名用の証明書一覧を取得するコマンドです。基本的にはデフォルトで入っていないので、先ほど登録した1つが見えるようになります。
$ security find-identity -v -p codesigning
1) 8B922501A1D84AE5F47653D958E51D49684F16CA "Developer ID Application: SHUNICHI TAKAHASHI (U6VL4BRVUM)"
1 valid identities found
キーチェーンアクセスで左サイドバーの ログイン → 上部タブの 自分の証明書を選択 → Developer ID Application: SHUNICHI TAKAHASHI (U6VL4BRVUM) を右クリック → 書き出す

フォーマット .p12 で保存を選択します。

パスワードを設定します。このパスワードはメモしてください。後ほど1Passwordに証明書と一緒に保存します。

無事証明書が作成されました。

こちらは任意の手順ですが、証明書が作成されましたら、先ほどのパスワードと同様に安全な場所で保管してください。私は証明書を1Passwordにドラッグし、先ほどのパスワードも同じアイテムとして保存しました。

App-Specific Password の生成
macOS アプリの配布では、コード署名 → 公証 → stapling の3ステップを踏みます。
-
- コード署名
Developer ID 証明書でアプリのバイナリに署名し、「誰が作ったか」と「改ざんされていないか」を保証する
- コード署名
-
- 公証(notarization)
署名済みアプリを Apple のサーバーにアップロードし、Apple がマルウェアスキャンを実施。問題なければ公証チケットを発行する
- 公証(notarization)
-
- stapling
発行された公証チケットをアプリに埋め込む。これにより、ユーザーがオフラインでも Gatekeeper が公証済みであることを検証できる
- stapling
公証と stapling では Apple のサーバーに Apple ID でアクセスする必要がありますが、CI 環境では 2FA の認証コード入力ができません。App-Specific Password を使うと、2FA をバイパスして CI からプログラム的に Apple のサーバーにアクセスできます。
https://account.apple.com/account/manage にいきます。サインインとセキュリティ → App 用パスワードを選択します。

画面が開きます。

ラベルには、agentoast-ci としました。CIで利用するため、アプリ名-ciとしています。

Appleのアカウントのパスワードの入力が求められ、パスワードが発行されます。生成されたパスワードを控えてください(GitHub Secrets の APPLE_PASSWORD になります)

GitHub Secrets 登録
| Secret 名 | 値 | 公開情報 |
|---|---|---|
TAURI_SIGNING_PRIVATE_KEY |
~/.tauri/agentoast.key の内容 |
No |
TAURI_SIGNING_PRIVATE_KEY_PASSWORD |
minisignキーのパスワード(空パスワードは非推奨。前述の通り失敗します) | No |
APPLE_CERTIFICATE |
.p12 を base64 エンコードした文字列 |
No |
APPLE_CERTIFICATE_PASSWORD |
.p12 エクスポート時に設定したパスワード |
No |
APPLE_SIGNING_IDENTITY |
Developer ID Application: SHUNICHI TAKAHASHI(U6VL4BRVUM) |
Yes |
APPLE_ID |
Apple ID のメールアドレス | No |
APPLE_PASSWORD |
App-Specific Password | No |
APPLE_TEAM_ID |
U6VL4BRVUM |
Yes |
KEYCHAIN_PASSWORD |
CI 用の任意のパスワード(適当な文字列でOK) | No |
GitHub CLIを利用して保存します。
# minisign 秘密鍵
gh secret set TAURI_SIGNING_PRIVATE_KEY < ~/.tauri/agentoast.key
# minisign パスワード(冒頭にも書いた通り、パスワードが空の場合はうまくいかないので注意)
echo -n "<bun run tauri signer generateで設定した値>" | gh secret set TAURI_SIGNING_PRIVATE_KEY_PASSWORD
# Apple 証明書 (base64)
base64 -i ~/Downloads/証明書.p12 | gh secret set APPLE_CERTIFICATE
# Apple 証明書パスワード
gh secret set APPLE_CERTIFICATE_PASSWORD
# Apple Signing Identity
echo -n "Developer ID Application: SHUNICHI TAKAHASHI (U6VL4BRVUM)" | gh secret set APPLE_SIGNING_IDENTITY
# Apple ID
gh secret set APPLE_ID
# App-Specific Password
gh secret set APPLE_PASSWORD
# Apple Team ID
echo -n "U6VL4BRVUM" | gh secret set APPLE_TEAM_ID
# CI キーチェーンパスワード(任意の文字列)
# このパスワードは CIジョブ内で一時キーチェーンを作って壊すだけの使い捨てです。もし値を忘れても gh secret set KEYCHAIN_PASSWORD で新しい値を上書きすれば問題ありません
openssl rand -base64 32 | gh secret set KEYCHAIN_PASSWORD
CI/CDの設定
コード署名・公証・stapling・GitHub Release への成果物アップロードは tauri-apps/tauri-action が一括で処理してくれるため、環境変数で Secrets を渡すだけで済みます。自前で書いているのは以下の2点です。
- Apple Developer 証明書を CI の一時キーチェーンにインポートするステップ
- リリース後に Homebrew Cask を更新するステップ
CI/CDを実行する
初回前述のCIを回したところ、GitHub Actionsの上限6時間を使っても公証が完了しませんでした。提出時刻は 2026-02-21T12:01:24+09:00 で、2026-02-23T12:00+09:00 頃に Accepted を確認しました(少なくとも約2日)。初回以降はすぐにAcceptedになるため、コンパイル含めても5分程度でリリース可能です。

以下のコマンドで公証のステータスを確認できます。
notarytool history で提出履歴の一覧を取得します。この時点ではまだ In Progress でした。
$ xcrun notarytool history --apple-id "$APPLE_ID" --team-id "U6VL4BRVUM" --password "$APPLE_PASSWORD"
Successfully received submission history.
history
--------------------------------------------------
createdDate: 2026-02-21T03:01:24.865Z
id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
name: Agentoast.zip
status: In Progress
notarytool info に提出 ID を指定すると、個別の提出の詳細を確認できます。
$ xcrun notarytool info xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx \
--apple-id "$APPLE_ID" \
--team-id "U6VL4BRVUM" \
--password "$APPLE_PASSWORD"
Successfully received submission info
createdDate: 2026-02-21T03:01:24.865Z
id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
name: Agentoast.zip
status: In Progress
約2日後に再度 notarytool history を実行したところ、Accepted になっていました。(2/23 昼12時頃)
$ xcrun notarytool history --apple-id "$APPLE_ID" --team-id "U6VL4BRVUM" --password "$APPLE_PASSWORD"
Successfully received submission history.
history
--------------------------------------------------
createdDate: 2026-02-21T03:01:24.865Z
id: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
name: Agentoast.zip
status: Accepted
実行後、そのままエラーが起きずにアプリが起動すれば成功です!
$ brew install --cask shuntaka9576/tap/agentoast
==> Fetching downloads for: shuntaka9576/tap/agentoast
✔︎ Cask agentoast (0.20.0) Verified 3.6MB/ 3.6MB
==> Fetching downloads for: shuntaka9576/tap/agentoast
✔︎ Cask agentoast (0.20.0) Verified 3.6MB/ 3.6MB
==> Upgrading 1 outdated package:
shuntaka9576/tap/agentoast 0.18.1 -> 0.20.0
==> Upgrading agentoast
==> Backing App 'Agentoast.app' up to '/opt/homebrew/Caskroom/agentoast/0.18.1/Agentoast.app'
==> Removing App '/Applications/Agentoast.app'
==> Moving App 'Agentoast.app' to '/Applications/Agentoast.app'
==> Purging files for version 0.18.1 of Cask agentoast
🍺 agentoast was successfully upgraded!
# 起動
$ open /Applications/Agentoast.app
さいごに
Apple Developerプログラム登録は日曜に契約したこともあり約2日程度、アプリの初回の公証に約2日の合計4日ほど待ち時間がありました。macOSアプリをリリースする際には初回の公証にかかる時間を意識して余裕のあるスケージュールを組むのが良いと思います。
Notarizationが遅い事例はいくつか報告があります。これはApple側のサーバー側の話なので、初回は気長に待ちつつ思い出したら、前述のxcrun notarytoolコマンドを実行するのが良いと思います。
以上の手順で属性削除の手間をユーザーから減らすことが出来ます。そこそこ面倒かつ待ち時間が長いので、自動アップデートを想定していない場合は、属性削除でも良いとは思います。
次回のブログで自動アップデートの実装について紹介できたらと思います!










