個人事業主がmacOSアプリを直接配布するまでの道のり②:署名・公証・DMG化を実機で完遂

前回 は Developer ID Application 証明書の取得とバックアップまでをまとめました。今回は実際にこの証明書を使って、Flutter で作った macOS アプリ「電子帳簿リネーマー」を 販売できる状態の .dmg ファイルにするまでの全工程を共有します。

実際にやってみると、初見の用語がたくさん出てきて手こずる部分もあったので、これから同じことをやろうとしている方の参考になれば嬉しいです。

目次

全体の流れ

ざっくり、こんな順番です:

[1] App Store Connect APIキーを発行(公証認証用)
        ↓
[2] アプリの Entitlements を確認・必要なら追加
        ↓
[3] Flutter で Release ビルド
        ↓
[4] codesign でアプリに署名(Hardened Runtime + Entitlements)
        ↓
[5] zip化して xcrun notarytool で Apple に公証申請
        ↓
[6] 公証完了(5〜15分待ち)
        ↓
[7] xcrun stapler staple で公証チケットをアプリに「ホチキス止め」
        ↓
[8] create-dmg で DMG 作成(DMG自体にも署名と公証)
        ↓
[9] Gatekeeperチェック・実機での起動確認

工程は多いですが、一度確立してしまえば、次回からはシェルスクリプト1本で全部実行できます。最初だけ大変、というやつです。

[1] App Store Connect API キーの発行

公証申請には Apple との通信が必要で、その認証に「App Store Connect API キー」を使います。

https://appstoreconnect.apple.com/access/integrations/api にアクセスして、「+」ボタンから新規発行:

項目設定
名前SINQWELL Notary(用途が分かる名前にする)
アクセスDeveloper(公証申請にはこれで十分。最小権限の原則)

生成された .p8 ファイルは1回しかダウンロードできないので、必ずダウンロードして安全な場所に保管します。

私の場合は、月謝袋アプリで使っていた iOS用 API キーと同じ整理スタイルに合わせて、

~/Library/CloudStorage/Dropbox/flutterapp/申請関係/電子帳簿リネーマー/macos/notary_APIkey/AuthKey_XXXXXXXXXX.p8

という階層に保管しました。.p8 はパスワードと同等の機密情報なので、ダウンロードフォルダから安全な場所に移動した後は、Downloads から削除しています。

キーチェーンに認証情報を登録

このあと何度も公証申請することになるので、API キーの情報を Mac のキーチェーンに「プロファイル」として登録しておきます:

xcrun notarytool store-credentials "sinqwell-notary" \
  --key ~/Library/CloudStorage/Dropbox/flutterapp/申請関係/電子帳簿リネーマー/macos/notary_APIkey/AuthKey_XXXXXXXXXX.p8 \
  --key-id XXXXXXXXXX \
  --issuer XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX

成功すると Credentials validated. Credentials saved to Keychain. と表示されます。これで、今後の公証申請は --keychain-profile "sinqwell-notary" という短い指定で済むようになります。

[2] Entitlements に必要な権限を追加

「Entitlements」というのは、「このアプリは何を許可されているか」を宣言するファイルです。macOS の公証では Hardened Runtime(強化ランタイム) を有効にする必要があり、その上で必要最小限の権限を Entitlements で「例外」として宣言します。

電子帳簿リネーマーの場合、

macos/Runner/Release.entitlements
macos/Runner/DebugProfile.entitlements

の2ファイルがあります。元々はサンドボックスとファイルアクセスの権限だけが設定されていましたが、今回からはライセンスキー検証で外部の API に通信する必要があるため、com.apple.security.network.client を追加しました:

<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.files.user-selected.read-write</key>
<true/>
<key>com.apple.security.files.bookmarks.app-scope</key>
<true/>
<key>com.apple.security.network.client</key>  <!-- 追加 -->
<true/>

Debug 側にも同じく追加します(開発中にもAPI通信のテストができるように)。

[3] Flutter で Release ビルド

flutter clean
flutter pub get
flutter build macos --release

Mac mini で約3分。flutter clean した直後は CocoaPods のキャッシュも全部消えているので、初回は少し時間がかかります。

成功すると:

✓ Built build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app (55.3MB)

この時点では、アプリは「ad-hoc 署名」という仮の署名しかついていません。codesign -dvvv で確認するとこうなっています:

Signature=adhoc
TeamIdentifier=not set

これではダメ。次のステップで Developer ID Application 証明書を使って正式に署名します。

[4] codesign でアプリに署名

ここがコア処理です:

codesign --force --deep --options runtime \
  --entitlements macos/Runner/Release.entitlements \
  --sign "Developer ID Application: SINQWELL (WYLLM22A53)" \
  --timestamp \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app

オプションの意味:

オプション意味
--force既存の署名(ad-hoc)を上書き
--deepアプリ内の全フレームワーク・ライブラリも再帰的に署名
--options runtimeHardened Runtime を有効化(公証に必須)
--entitlementsRelease 用の権限ファイルを指定
--sign "..."使用する証明書名
--timestampApple のタイムスタンプサーバーで時刻を記録(公証に必須)

初回実行時にキーチェーンから(証明書の秘密鍵)を使うためのパスワードが聞かれるので、Mac のログインパスワードを入力。「常に許可」を選択すると、次回以降は確認なしで実行できます。これから何度も署名するので、ここはストレスを減らしたいところです。

署名が完了したか確認:

codesign -dvvv build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app

期待する出力:

flags=0x10000(runtime)     ← Hardened Runtime 有効
Authority=Developer ID Application: SINQWELL (WYLLM22A53)
Authority=Developer ID Certification Authority
Authority=Apple Root CA
Timestamp=Jun 12, 2026 at 10:45:29
TeamIdentifier=WYLLM22A53

特に flags=0x10000(runtime) の runtime が表示されていれば、Hardened Runtime が有効になっている証です。これが出ていないと、次の公証申請で確実に弾かれます。

[5] zip化して公証申請

公証は .zip または .dmg または .pkg 形式で Apple に提出します。今回はまず試運転として zip 形式で進めました。

⚠️ zip 化には ditto コマンドを使うのがポイントです。普通の zip コマンドだと、macOS 固有のメタデータ(拡張属性、シンボリックリンクなど)が壊れることがあり、公証で弾かれます。

ditto -c -k --keepParent \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.zip

そして公証申請:

xcrun notarytool submit \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.zip \
  --keychain-profile "sinqwell-notary" \
  --wait

--wait を付けると、コマンドが公証完了まで待機してくれます。

時間目安:

  • アップロード:30秒〜1分
  • Apple 側の処理:通常 5〜15分
  • 私の場合、1〜2分で完了することもあれば、もう少しかかることも

待ち時間中はターミナルが止まったように見えますが、裏で Apple が処理しています。Ctrl+C で止めないこと。

成功時の出力:

Current status: Accepted...........
Processing complete
  id: 03d10498-3965-4b3b-9a76-bd549cad4799
  status: Accepted

status: Accepted が出れば公証成功です🎉

[6] 公証チケットを staple

公証成功した時点では、Apple のサーバーが「このアプリは公証済み」と知っているだけで、アプリ自体には何も埋め込まれていません。

stapler で公証チケットをアプリに「ホチキス止め」します。これをやらないと、ユーザーが初回起動時に Apple のサーバーに公証確認しに行く必要があり、オフラインだと起動できなくなることがあります。

xcrun stapler staple \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app

成功時:

The staple and validate action worked!

[7] Gatekeeper チェックで最終確認

実機の Gatekeeper(macOS 標準のセキュリティ機構)に、このアプリを通してみます:

spctl -a -vvv -t install \
  build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app

期待する出力:

build/.../electronic_bookkeeping_renamer.app: accepted
source=Notarized Developer ID
origin=Developer ID Application: SINQWELL (WYLLM22A53)

accepted と Notarized Developer ID が両方出れば、世界中の Mac から「SINQWELL が署名し、Apple が公証した信頼できるアプリ」として認識される状態になります。

念のため、.app をダブルクリックで起動して、警告ダイアログが出ないことも確認しました。

[8] DMG ファイルを作って配布形式にする

最後のステップは、配布用の .dmg ファイル(Disk Image)を作ること。.dmg をダブルクリックすると、よくある「アプリを Applications フォルダにドラッグするだけ」のあのウィンドウが出る、というやつです。

これには create-dmg という Homebrew で入れるツールが便利です:

brew install create-dmg

そして実行:

mkdir -p ~/Desktop/dmg_staging
cp -R build/macos/Build/Products/Release/electronic_bookkeeping_renamer.app \
   ~/Desktop/dmg_staging/

create-dmg \
  --volname "電子帳簿リネーマー" \
  --window-size 600 400 \
  --icon-size 100 \
  --icon "electronic_bookkeeping_renamer.app" 150 200 \
  --app-drop-link 450 200 \
  --codesign "Developer ID Application: SINQWELL (WYLLM22A53)" \
  --notarize "sinqwell-notary" \
  ~/Desktop/electronic_bookkeeping_renamer.dmg \
  ~/Desktop/dmg_staging/

create-dmg のすごいところは、DMG の作成・署名・公証・staple まで全部自動でやってくれることです。--codesign と --notarize オプションを指定するだけで、内部的に:

  1. DMG ファイル作成
  2. アイコン配置の調整(AppleScript で Finder を操作)
  3. DMG ファイル自体に署名
  4. DMG ファイル自体を公証申請
  5. 完了したら staple

までを一気に実行してくれます。

途中で「ターミナル.app が Finder.app を制御するアクセスを要求しています」というプライバシー確認のダイアログが出ますが、これは AppleScript で Finder にアイコン配置を指示するための正当な要求なので「許可」を選択。

完了すると:

Stapling the notarization ticket
The disk image is now notarized
Disk image done

~/Desktop/electronic_bookkeeping_renamer.dmg ができていれば成功。Gatekeeper チェックも合格しました:

$ spctl -a -vvv -t open --context context:primary-signature ~/Desktop/electronic_bookkeeping_renamer.dmg
~/Desktop/electronic_bookkeeping_renamer.dmg: accepted
source=Notarized Developer ID
origin=Developer ID Application: SINQWELL (WYLLM22A53)

ダブルクリックすると、警告なしでマウントされ、アプリと Applications ショートカットが並んだウィンドウが開きます。これで配布可能な DMG の完成です。

やってみての感想

iOS / Android アプリのリリースに比べると、確かに工程は多いです。ただ、個々のステップ自体はそんなに難しくない、というのが実感です。慣れない用語(CSR、Hardened Runtime、staple、notarytool、etc.)が並ぶので最初は怖く感じますが、一度通すと体系が見えてきます。

特に良かったのは:

  • Apple の公式ツールが揃っている:codesign、xcrun notarytool、xcrun stapler、spctl、全部 macOS 標準(Xcode に同梱)で、サードパーティのツールに頼らなくて済む
  • create-dmg の自動化が強力:DMG 作成から公証まで1コマンドで終わる
  • 公証申請が思ったより早い:5〜15分と聞いていたが、実際には1〜2分で終わることも多かった

逆に気をつけたほうがいい点:

  • 証明書のバックアップは絶対に取る(紛失したら復元不可)
  • .p8 ファイルも1回しかダウンロードできないので、ダウンロード直後に安全な場所に移動
  • キーチェーンは「ログイン」キーチェーンを必ず指定(iCloud に入れると秘密鍵とペアにならない)
  • zip ではなく ditto を使う(公証で弾かれることがある)

次のステップ

これで「アプリを配布できる形」にはなりましたが、まだ完成形ではありません。残るタスクは:

  • アプリ側でのライセンスキー検証実装(Polar.sh の API を使う)
  • 無料体験版の制限ロジック(25ファイルまで無料)
  • 細かい UI 文言の整理(販売バージョン v1.0 仕上げ)

ここまで来れば、あとはアプリ内のコードを書くだけ。配布インフラの不安はなくなりました。

販売開始まで、もう一息です。


関連リンク:

目次