公証したアプリが、配布先のMacで起動できるかどうかは、Gatekeeperの判定で決まる。spctl --assessを使うと、Gatekeeperの判定を、配布前に手元のMacで確認できる。

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

acceptedとsource=Notarized Developer IDが表示されれば、公証済みのアプリとして扱われる。実行例はXcode 27.0、macOS 27.0.1のもの。

spctlの判定を読む

spctl --assess --type executeは、アプリを実行してよいかをGatekeeperに問い合わせる。-vvを付けると、判定の理由が表示される。

終了コードは、acceptedのときが0、rejectedのときが3である。

$ spctl --assess --type execute [アプリ名].app
$ echo $?
0

署名の状態ごとの判定は、次のとおりである。

アプリの状態判定source
公証済みのDeveloper ID署名acceptedNotarized Developer ID
Developer ID署名で公証前rejectedUnnotarized Developer ID
Apple Development署名rejected表示されない

公証前のDeveloper ID署名のアプリは、署名があってもrejectedになる。

$ spctl --assess --type execute -vv [アプリ名].app
[アプリ名].app: rejected
source=Unnotarized Developer ID
origin=Developer ID Application: [名前] ([Team ID])
$ echo $?
3

Apple Developmentの証明書で署名したアプリは、sourceの行が表示されず、originだけが表示される。

$ spctl --assess --type execute -vv [アプリ名].app
[アプリ名].app: rejected
origin=Apple Development: [名前] ([ID])

公証していないコピーも公証済みと判定される場合がある

Gatekeeperは、アプリの署名のCDHashをもとに、Appleに公証の記録を問い合わせる。そのため、同じ署名内容のアプリは、公証に提出していなくても、公証済みと判定される。

Xcodeで同じアーカイブを2回エクスポートすると、署名内容が同じになり、CDHashが一致する。片方だけを公証に提出しても、もう片方もNotarized Developer IDと判定される。

$ codesign -dvvv [公証したアプリ].app 2>&1 | grep '^CDHash='
CDHash=[CDHash]
$ codesign -dvvv [提出していないコピー].app 2>&1 | grep '^CDHash='
CDHash=[CDHash]
$ spctl --assess --type execute -vv [提出していないコピー].app
[提出していないコピー].app: accepted
source=Notarized Developer ID
origin=Developer ID Application: [名前] ([Team ID])

codesignで署名し直すと、CDHashが変わる。署名し直したアプリは、公証し直さないとUnnotarized Developer IDになる。公証前後の判定を比較するときは、署名し直したアプリを使う。

stapleの有無は判定に影響しない

インターネットに接続できる環境では、stapleをしていない公証済みのアプリも、acceptedと判定される。stapleの前後で、spctlの出力は変わらない。

$ xcrun stapler validate [アプリ名].app
[アプリ名].app does not have a ticket stapled to it.
$ spctl --assess --type execute -vv [アプリ名].app
[アプリ名].app: accepted
source=Notarized Developer ID
origin=Developer ID Application: [名前] ([Team ID])

stapleは、オフラインの環境でも公証済みと判定されるようにするための操作である。

参考: 【notarytool】アプリを公証してstaple(公証チケットを添付)する

隔離属性とGatekeeperの初回起動

ブラウザでダウンロードしたファイルには、隔離属性(com.apple.quarantine)が付く。Gatekeeperは、隔離属性の付いたアプリを初めて起動するときに、確認のダイアログを表示する。

隔離属性は、xattr -lで確認できる。

$ xattr -l [アプリ名].app
com.apple.quarantine: 0083;[ダウンロードした時刻の16進数];Safari;[UUID]

ブラウザでダウンロードしたzipを展開した状態と同じ状態は、隔離属性を手動で付けると再現できる。

$ xattr -w com.apple.quarantine "0083;$(printf '%x' $(date +%s));Safari;$(uuidgen)" [アプリ名].app

spctl --assessの判定は、隔離属性の有無で変わらない。隔離属性を付けたアプリも、公証済みであればacceptedである。

公証済みのアプリを初めて起動する

隔離属性の付いた公証済みのアプリをopenで起動すると、次のダイアログが表示される。

“[アプリ名]”はインターネットからダウンロードされたアプリケーションです。開いてもよろしいですか?
このファイルは“Safari”により今日の[時刻]にダウンロードされました。 Appleによるチェックで悪質なソフトウェアは検出されませんでした。
[キャンセル] [開く]

「開く」を押すと、アプリが起動する。隔離属性の先頭の値は、0083から00c3に変わる。

$ xattr -l [アプリ名].app
com.apple.quarantine: 00c3;[ダウンロードした時刻の16進数];Safari;[UUID]

公証されていないアプリを初めて起動する

隔離属性の付いた、公証されていないDeveloper ID署名のアプリをopenで起動すると、アプリは起動せず、次のダイアログが表示される。

“[アプリ名]”は開いていません
Appleは、“[アプリ名]”にMacに損害を与えたり、プライバシーを侵害する可能性のあるマルウェアが含まれていないことを検証できませんでした。
[ゴミ箱に入れる] [完了]

配布したアプリが、この状態にならないように、配布前にspctlで判定を確認する。

隔離属性の付いたアプリはAppTranslocationから実行される

隔離属性の付いたアプリは、元の場所ではなく、AppTranslocationの下にコピーされて実行される。実行中のプロセスのパスは、次のようになる。

/private/var/folders/[...]/T/AppTranslocation/[UUID]/d/[アプリ名].app/Contents/MacOS/[アプリ名]

隔離属性を削除すると、元の場所のままで起動する。

$ xattr -d com.apple.quarantine [アプリ名].app

アプリが自分の場所を基準にファイルを探す場合は、実行中のパスが元の場所と異なる点に注意する。