macOSアプリをMac App Store外で配布するときは、Appleに提出して公証(notarization)を受ける。公証に通ったアプリには、Appleが公証チケットを発行する。このチケットをアプリに添付する操作がstaple(ステープル)である。チケットを添付したアプリは、オフラインの環境でも、Gatekeeperが公証済みであると判断できる。

公証からstapleまでは、次の流れである。

$ ditto -c -k --keepParent [アプリ名].app [アプリ名].zip
$ xcrun notarytool submit [アプリ名].zip --keychain-profile "[プロファイル名]" --wait
$ xcrun stapler staple [アプリ名].app

stapleした後は、配布用のzipを作り直す。実行例はXcode 27.0、macOS 27.0.1のもの。認証情報(--keychain-profile)は、事前にnotarytool store-credentialsで保存しておく。

公証に提出するzipを作る

notarytool submitに渡せるのは、zip、.pkg、.dmgのいずれかである。.appはディレクトリのため、そのままでは提出できない。dittoでzipにまとめる。

$ ditto -c -k --keepParent [アプリ名].app [アプリ名].zip

--keepParentを付けると、zipの中に[アプリ名].appディレクトリが含まれる。

参考: 【ditto】コマンドでzipファイルを作成する

.appを直接提出すると、エラーになる。

$ xcrun notarytool submit [アプリ名].app --keychain-profile "[プロファイル名]"
Conducting pre-submission checks for [アプリ名].app and initiating connection to the Apple notary service...
Error: [アプリ名].app must be a zip archive (.zip), flat installer package (.pkg), or UDIF disk image (.dmg), use 'notarytool submit --force' to skip this validation and submit anyway.

公証を提出して結果を待つ

notarytool submitで、zipをAppleのノータリーサービスに提出する。--waitを付けると、公証の処理が終わるまで待つ。

$ xcrun notarytool submit [アプリ名].zip --keychain-profile "[プロファイル名]" --wait
Conducting pre-submission checks for [アプリ名].zip and initiating connection to the Apple notary service...
Submission ID received
  id: [提出ID]
Successfully uploaded file
  id: [提出ID]
  path: [アプリ名].zip
Waiting for processing to complete.
Current status: In Progress...Current status: In Progress....Current status: Accepted.....Processing complete
  id: [提出ID]
  status: Accepted

status: Acceptedになれば、公証は成功である。今回の検証用の小さなアプリでは、提出から結果が出るまで約20秒だった。アプリの大きさや混雑によって、所要時間は変わる。

提出のIDは、後から結果を確認するために使う。notarytool infoで、提出の状態を確認できる。

$ xcrun notarytool info [提出ID] --keychain-profile "[プロファイル名]"
Successfully received submission info
  createdDate: [提出した日時]
  id: [提出ID]
  name: [アプリ名].zip
  status: Accepted

statusには、処理中のIn Progress、成功のAccepted、失敗のInvalidなどがある。Invalidになった場合の調べ方は、公証のログの記事で解説する。

参考: 【notarytool】公証に失敗した理由をログから調べる

stapleで公証チケットを添付する

xcrun stapler stapleで、公証チケットをアプリに添付する。対象は.appである。

$ xcrun stapler staple [アプリ名].app
Processing: [アプリ名].app
Processing: [アプリ名].app
The staple and validate action worked!

zipにはstapleできない。

$ xcrun stapler staple [アプリ名].zip
Processing: [アプリ名].zip
Stapler is incapable of working with ZIP archive files.

stapleの前後でアプリの状態を確認する

チケットが添付されているかどうかは、stapler validateで確認できる。stapleの前は、チケットがないと表示される。

$ xcrun stapler validate [アプリ名].app
Processing: [アプリ名].app
[アプリ名].app does not have a ticket stapled to it.

stapleの後は、検証に成功する。

$ xcrun stapler validate [アプリ名].app
Processing: [アプリ名].app
The validate action worked!

公証チケットは、アプリのContents/CodeResourcesファイルとして保存される。stapleの前のContentsには、このファイルがない。

$ ls [アプリ名].app/Contents
_CodeSignature
Info.plist
MacOS
PkgInfo
Resources
$ xcrun stapler staple [アプリ名].app
$ ls [アプリ名].app/Contents
_CodeSignature
CodeResources
Info.plist
MacOS
PkgInfo
Resources

codesign -dvにも、チケットの有無が表示される。

$ codesign -dv --verbose=4 [アプリ名].app 2>&1 | grep Notarization
Notarization Ticket=stapled

stapleした後に配布用のzipを作り直す

公証に提出したzipには、チケットが含まれない。提出したzipは、stapleする前のアプリを固めたものだからである。

stapleしたアプリを配布するには、stapleの後に、もう一度zipを作る。

$ ditto -c -k --keepParent [アプリ名].app [アプリ名]-[バージョン].zip

作り直したzipには、公証チケットのContents/CodeResourcesが含まれる。

$ unzip -l [アプリ名]-[バージョン].zip | grep 'Contents/CodeResources'
     1653  10-04-2026 09:13   [アプリ名].app/Contents/CodeResources

提出に使ったzipには、このファイルがない。配布するのは、stapleの後に作ったzipである。

stapleしなくても公証済みと判定される

spctlでGatekeeperの判定を確認すると、stapleの前でも、公証済みのアプリはNotarized Developer IDと判定される。インターネットに接続できる環境では、GatekeeperがAppleのサーバーに公証の記録を問い合わせる。

参考: 【spctl】Gatekeeperの判定を配布前に確認する

$ spctl --assess --type execute -vv [アプリ名].app
[アプリ名].app: accepted
source=Notarized Developer ID
origin=Developer ID Application: [名前] ([Team ID])

stapleしておくと、問い合わせができないオフライン環境でも、同じ判定になる。配布するアプリには、stapleを実行する。