Tauri製macOSアプリのコード署名・公証・自動アップデート署名を自動化し、GitHub Releasesで配信する

Tauri製macOSアプリのコード署名・公証・自動アップデート署名を自動化し、GitHub Releasesで配信する

2026.02.23

はじめに

TauriはRust製のデスクトップアプリケーションフレームワークです。フロントエンドにOS標準WebViewを使うためWeb技術(HTML/CSS/JS)が利用でき、バックエンドはRustで書けます。Electronと比較してアプリの軽量化・高速化が図れることから、近年注目を集めているフレームワークです。

https://v2.tauri.app

macOSでアプリを配布するにはAppleのコード署名と公証(notarization)が必要です。署名・公証されていないアプリはGatekeeperによって「xxxxは壊れているため開けません。」のようにブロックされ、ユーザーが起動できません。

CleanShot 2026-02-23 at 15.15.45

これはmacOSはインターネットからダウンロードしたアプリに com.apple.quarantine 属性を付与し、Gatekeeperは起動時にこの属性を検出して、Appleの署名 + 公証がないアプリをブロックします。ワークアラウンドとして以下のコマンドで com.apple.quarantine 属性を除去すれば起動できますが、ユーザーにこの手順を求めるのは現実的ではありません。

xattr -cr /Applications/Agentoast.app

話は変わり、TauriにはUpdaterプラグインによる自動アップデート機能があり、将来的にこれを利用した自動アップデートの導入も見据えています。Updaterはアップデートバイナリの改ざん検知にminisign署名を使うため、本記事でその鍵ペア生成とCIへの組み込みまで済ませておきます。自動アップデートの実装自体は別記事でフォロー予定です。

https://v2.tauri.app/plugin/updater/

本記事では、コード署名・公証済みのDMGとTauriUpdater署名済みアーティファクトをGitHub Actionsで生成、brew tapで配信するところまでを扱います。

前提

  1. Apple Developer Programに加入済みであること

私が加入した際の手順を紹介しておきます。

https://shuntaka.dev/shuntaka/articles/20260215-apple-developer

  1. このアプリはストアに配信しません

GitHub Release経由で配信します。種類として Developer ID Application に該当します。またbrew tap --cask で配信するため、CIにはbrew tapで配信する設定がされています。brew tapに関しては、記事の長さの都合上割愛するため、気になる場合は後述のリポジトリを参考にしてください。

  1. パッケージマネージャはbunを利用しています。適宜読み替えて実行してください

  2. 本記事の内容はすべて以下のリポジトリで実際に運用しているもので、困った際はリポジトリを参考にしてください

https://github.com/shuntaka9576/agentoast

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 の作成

キーチェーンアクセスを開き、メニューバーから証明書アシスタント>「認証局に証明書を要求」を選択します。
CleanShot 2026-02-21 at 08.50.35@2x

それぞれ以下を選択します。

  1. ユーザのメールアドレス: Apple ID のメールアドレス
  2. 通称: そのままでOK
  3. 要求の処理: 「ディスクに保存」を選択
    CleanShot 2026-02-21 at 08.53.57@2x

デフォルトの CertificateSigningRequest.certSigningRequest という名前で保存します。
CleanShot 2026-02-21 at 08.56.47@2x

作成されていることを確認します。
CleanShot 2026-02-21 at 08.58.01@2x

Developer ID 証明書の作成

証明書リストを管理するページに遷移します。 https://developer.apple.com/account/resources/certificates/list です。
CleanShot 2026-02-21 at 08.40.02 2@2x

+ボタンを押下します。
CleanShot 2026-02-21 at 09.00.33@2x

今回はストア配信せず、GitHub Releaseから配信するため、Developer ID Application を選択 → Continue
CleanShot 2026-02-21 at 09.04.12@2x

Previous Sub-CA がデフォルトになっている場合もありますが、今回は古い macOS をサポートする必要がないので G2 Sub-CA を選択します。

Sub-CA 説明
G2 Sub-CA 新しい中間認証局。SHA-256 ベース。今後の標準
Previous Sub-CA 古い中間認証局。macOS 10.12 以前との互換性用

CleanShot 2026-02-21 at 09.07.10@2x

続いて先ほど作成した CertificateSigningRequest.certSigningRequest ファイルをアップロードします。
CleanShot 2026-02-21 at 09.11.32@2x

アップロードが完了したら以下のような画面になり、Continueが活性化するので押下して、次に進みます。
CleanShot 2026-02-21 at 09.14.08@2x

証明書が作成され、Download で .cer ファイルを保存します。
CleanShot 2026-02-21 at 09.15.24@2x

G2 中間証明書がキーチェーンに登録されているか確認します。キーチェーンの右上の検索ボックスに Developer ID Certification Authority と入力します。

以下の画像で表示されたのは1件でした。有効期限が 2027/02/02 の場合 Previous Sub-CA のもののため、G2 の中間証明書のインストールが必要です。

CleanShot 2026-02-21 at 09.33.38@2x

以下のコマンドでG2の中間証明書をインストールします。

curl -O https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer && open DeveloperIDG2CA.cer

キーチェーンへの追加ダイアログが表示されるので、追加します。
CleanShot 2026-02-21 at 09.40.30@2x

インストールできました。
CleanShot 2026-02-21 at 09.42.48@2x

先ほど Apple Developer Portalで作成し、ダウンロードした .cer ファイルをダブルクリックします。
CleanShot 2026-02-21 at 09.18.52@2x

キーチェーンに証明書を登録します。
CleanShot 2026-02-21 at 09.44.22@2x

登録した証明書を確認します。コード署名用の証明書一覧を取得するコマンドです。基本的にはデフォルトで入っていないので、先ほど登録した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) を右クリック → 書き出す
CleanShot 2026-02-21 at 09.51.45@2x

フォーマット .p12 で保存を選択します。
CleanShot 2026-02-21 at 09.54.08@2x

パスワードを設定します。このパスワードはメモしてください。後ほど1Passwordに証明書と一緒に保存します
CleanShot 2026-02-21 at 09.56.49@2x

無事証明書が作成されました。
CleanShot 2026-02-21 at 09.58.39@2x

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

App-Specific Password の生成

macOS アプリの配布では、コード署名 → 公証 → stapling の3ステップを踏みます。

    1. コード署名
      Developer ID 証明書でアプリのバイナリに署名し、「誰が作ったか」と「改ざんされていないか」を保証する
    1. 公証(notarization)
      署名済みアプリを Apple のサーバーにアップロードし、Apple がマルウェアスキャンを実施。問題なければ公証チケットを発行する
    1. stapling
      発行された公証チケットをアプリに埋め込む。これにより、ユーザーがオフラインでも Gatekeeper が公証済みであることを検証できる

公証と stapling では Apple のサーバーに Apple ID でアクセスする必要がありますが、CI 環境では 2FA の認証コード入力ができません。App-Specific Password を使うと、2FA をバイパスして CI からプログラム的に Apple のサーバーにアクセスできます。

https://account.apple.com/account/manage にいきます。サインインとセキュリティ → App 用パスワードを選択します。
CleanShot 2026-02-21 at 10.07.57@2x

画面が開きます。
CleanShot 2026-02-21 at 10.10.49@2x

ラベルには、agentoast-ci としました。CIで利用するため、アプリ名-ciとしています。
CleanShot 2026-02-21 at 10.13.39@2x

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

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点です。

  1. Apple Developer 証明書を CI の一時キーチェーンにインポートするステップ
  2. リリース後に Homebrew Cask を更新するステップ

https://github.com/shuntaka9576/agentoast/blob/bdff3dd1b22628b50617c4c87125d75a4f54828d/.github/actions/release-app/action.yaml#L1-L142

CI/CDを実行する

初回前述のCIを回したところ、GitHub Actionsの上限6時間を使っても公証が完了しませんでした。提出時刻は 2026-02-21T12:01:24+09:00 で、2026-02-23T12:00+09:00 頃に Accepted を確認しました(少なくとも約2日)。初回以降はすぐにAcceptedになるため、コンパイル含めても5分程度でリリース可能です。

CleanShot 2026-02-23 at 14.48.24@2x

以下のコマンドで公証のステータスを確認できます。

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コマンドを実行するのが良いと思います。

https://github.com/orgs/tauri-apps/discussions/8630

以上の手順で属性削除の手間をユーザーから減らすことが出来ます。そこそこ面倒かつ待ち時間が長いので、自動アップデートを想定していない場合は、属性削除でも良いとは思います。

次回のブログで自動アップデートの実装について紹介できたらと思います!

この記事をシェアする

FacebookHatena blogX

関連記事