公証の結果がInvalidになったときは、notarytool logで失敗の理由を調べる。提出のIDを指定すると、公証のログがJSONで表示される。ログのissuesに、問題のあるファイルとエラーメッセージが記録されている。

$ xcrun notarytool log [提出ID] --keychain-profile "[プロファイル名]"

実行例はXcode 27.0、macOS 27.0.1のもの。認証情報(--keychain-profile)は、事前にnotarytool store-credentialsで保存しておく。

提出のIDを調べる

ログの取得には、提出のID(Submission ID)が必要である。notarytool submit --waitの出力に表示されたidが、提出のIDである。

$ xcrun notarytool submit [アプリ名].zip --keychain-profile "[プロファイル名]" --wait
...
Current status: In Progress...Current status: In Progress....Current status: Invalid.....Processing complete
  id: [提出ID]
  status: Invalid

提出のIDを控えていない場合は、notarytool historyで過去の提出の一覧を表示して、status: Invalidの提出を探す。

$ xcrun notarytool history --keychain-profile "[プロファイル名]"

historyの使い方は、認証情報を保存する記事でも紹介している。

参考: 【notarytool】App用パスワードで公証用の認証情報を保存する

公証のログを取得する

notarytool logは、提出のIDを引数に取る。結果は整形されたJSONで、標準出力に表示される。ファイルへ保存するときは、提出のIDの後ろで出力先のパスを指定する。

$ xcrun notarytool log [提出ID] --keychain-profile "[プロファイル名]" log.json

Hardened Runtimeを無効のまま提出したアプリのログは、次のとおりである。

{
  "logFormatVersion": 1,
  "jobId": "[提出ID]",
  "status": "Invalid",
  "statusSummary": "Archive contains critical validation errors",
  "statusCode": 4000,
  "archiveFilename": "[アプリ名].zip",
  "uploadDate": "[提出した日時]",
  "sha256": "[zipのSHA-256]",
  "ticketContents": null,
  "issues": [
    {
      "severity": "error",
      "code": null,
      "path": "[アプリ名].zip/[アプリ名].app/Contents/MacOS/[アプリ名]",
      "message": "The executable does not have the hardened runtime enabled.",
      "docUrl": "https://developer.apple.com/documentation/security/notarizing_macos_software_before_distribution/resolving_common_notarization_issues#3087724",
      "architecture": "arm64"
    }
  ]
}

主な項目は次のとおりである。

項目内容
status公証の結果。Invalidは失敗
statusSummary結果の要約。失敗ではArchive contains critical validation errors
statusCode結果のコード。今回の検証では、成功が0、失敗が4000
ticketContents発行された公証チケットの内容。失敗ではnull
issues見つかった問題の一覧。成功ではnull

issuesの各要素には、次の項目が含まれる。

項目内容
severity重大度。今回の検証ではerror
path問題のあるファイルのパス。提出したzipの名前から始まる
messageエラーメッセージ
docUrlAppleのドキュメントの該当箇所へのリンク
architecture問題のあるバイナリのアーキテクチャ

jqで問題を一覧にする

issuesが多いときは、jqで絞り込むと読みやすい。

$ jq -r '.issues[] | "\(.severity): \(.path): \(.message)"' log.json
error: [アプリ名].zip/[アプリ名].app/Contents/MacOS/[アプリ名]: The executable does not have the hardened runtime enabled.

よくある失敗とその対処法

公証がInvalidになる原因と、issuesに記録されるメッセージを紹介する。

Hardened Runtimeが有効でない

The executable does not have the hardened runtime enabled.

署名時にHardened Runtimeが有効になっていない。XcodeでHardened Runtimeを有効にして、アーカイブからやり直す。codesignで署名するときは、--options runtimeを付ける。

参考: 【Xcode】公証のためにHardened Runtimeを有効にする

セキュアタイムスタンプがない

The signature does not include a secure timestamp.

署名にセキュアタイムスタンプが含まれていない。codesignで署名するときは、--timestampを付ける。--timestamp=noneを指定すると、このエラーになる。

参考: 【codesign】署名の内容と整合性を検証する

get-task-allowのentitlementが付いている

The executable requests the com.apple.security.get-task-allow entitlement.

デバッグ用のcom.apple.security.get-task-allowが、署名のentitlementsに含まれている。entitlementsから外して、署名し直す。署名後のentitlementsは、codesign -d --entitlements -で確認できる。

内部のバイナリに署名がない

アプリの内部にあるフレームワークや実行ファイルの署名が欠けていたり、無効だったりすると、そのバイナリごとにエラーが記録される。

The binary is not signed.
The signature of the binary is invalid.

SparkleのAutoupdateの署名を外して再署名したアプリを提出すると、次のように、内部のバイナリのパスとメッセージが一覧になる。

$ jq -r '.issues[] | "\(.severity)\t\(.path)\t\(.message)"' log.json
error	[アプリ名].zip/[アプリ名].app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate	The binary is not signed.
error	[アプリ名].zip/[アプリ名].app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate	The signature does not include a secure timestamp.
error	[アプリ名].zip/[アプリ名].app/Contents/Frameworks/Sparkle.framework/Versions/B/Autoupdate	The executable does not have the hardened runtime enabled.
error	[アプリ名].zip/[アプリ名].app/Contents/Frameworks/Sparkle.framework/Versions/B/Sparkle	The signature of the binary is invalid.
error	[アプリ名].zip/[アプリ名].app/Contents/MacOS/[アプリ名]	The signature of the binary is invalid.

同じ内容が、重複して記録される場合がある。内部のバイナリの署名を変更すると、外側のアプリの署名も無効になる。内部のバイナリから順に署名して、最後に外側のアプリに署名する。Xcodeのエクスポートであれば、内部のバイナリにも同じ署名が自動で付く。

対処したら、署名したアプリを提出し直す。再提出では、新しい提出のIDが発行される。

submitの終了コードは失敗でも0になる

notarytool submit --waitは、公証の結果がInvalidでも、終了コードが0である。

$ xcrun notarytool submit [アプリ名].zip --keychain-profile "[プロファイル名]" --wait > submit.out
$ echo $?
0
$ tail -2 submit.out
  id: [提出ID]
  status: Invalid

CIなどで公証の失敗を検出するときは、終了コードだけでは判定できない。notarytool infoをJSON形式で実行し、statusを確認する。

$ xcrun notarytool info [提出ID] --keychain-profile "[プロファイル名]" -f json
{"status":"Invalid","createdDate":"[提出した日時]","id":"[提出ID]","name":"[アプリ名].zip","message":"Successfully received submission info"}
$ xcrun notarytool info [提出ID] --keychain-profile "[プロファイル名]" -f json | jq -r .status
Invalid

statusがAcceptedでなければ、公証に失敗している。

公証されていないアプリにstapler stapleを実行すると、チケットが見つからず失敗する。終了コードは65である。公証の後にstapler stapleを続けて実行すると、公証の失敗を、ここで検出できる。

$ xcrun stapler staple [アプリ名].app
Processing: [アプリ名].app
CloudKit query for [アプリ名].app ([チケットの識別子]) failed due to "Record not found".
Could not find base64 encoded ticket in response for [チケットの識別子]
The staple and validate action failed! Error 65.
$ echo $?
65