Apple 公證服務從 2023 年 11 月 1 日起不再接受 altool 或 Xcode 13 以前版本發起的上傳。 如果你遇到 notarytool 公證失敗,先保存 Submission ID,再讀取公證日誌;不要直接重傳同一個檔案。(Apple Developer:公證 macOS 軟體)
這篇適合三類人:
- 直接發布 DMG、PKG 或 ZIP 的 macOS 獨立開發者。
- 使用腳本或 CI 自動簽名、公證,但偶爾收到 Invalid 或長時間 In Progress 的維護者。
- 想把發布工作搬到遠端 Mac,並需要驗證憑證、Keychain 與公證流程的小型團隊。
01 先把「公證失敗」拆成四個不同故障層
公證、程式碼簽名、票據附加和 Gatekeeper 驗證不是同一件事。公證服務會檢查 Developer ID 簽名與提交內容;Gatekeeper 則在使用者安裝或首次啟動時,重新評估簽名、票據及其他安全資訊。公證成功,不代表每一個分發檔案都已完成 stapler,也不代表所有 macOS 版本都會以相同方式通過驗證。(Apple Developer:Developer ID 支援)
先用這個判斷表分層:
| 看到的狀態或現象 | 先判定哪一層 | 下一個動作 |
|---|---|---|
| Submit 指令直接回報認證或網路錯誤 | 上傳/認證 | 檢查憑據、網路與工具版本 |
| Submission 狀態為 In Progress | 公證掃描仍未完成 | 保存 ID,稍後查詢,不要重複提交 |
| 狀態為 Invalid | 公證掃描拒絕 | 讀取 log,按 issues 陣列逐項修復 |
| 狀態為 Accepted,但 stapler 失敗 | 票據附加 | 檢查檔案是否可寫、格式與工具環境 |
| stapler 成功,但使用者仍被 Gatekeeper 阻擋 | 本機驗證 | 用 codesign、spctl 和實際分發檔案重新驗證 |
notarytool 可以查詢提交狀態和取得日誌。遇到問題時,應先查看 status log,而不是只依賴終端機最後一行訊息。可參考 Apple 的 notarytool 工作流程文件。
02 第一步:保存 Submission ID,再讀取 Invalid 的具體日誌
提交時,命令中的檔案路徑、Team ID 與 Keychain Profile 都應使用你自己的值;排查紀錄則要移除帳戶識別資料。
xcrun notarytool submit /PATH/TO/RELEASE_FILE \
--keychain-profile "KEYCHAIN_PROFILE_NAME" \
--wait
如果指令已經回傳 Submission ID,先記下它,再執行:
xcrun notarytool info SUBMISSION_ID \
--keychain-profile "KEYCHAIN_PROFILE_NAME"
查看完整日誌:
xcrun notarytool log SUBMISSION_ID \
--keychain-profile "KEYCHAIN_PROFILE_NAME" \
/PATH/TO/NOTARIZATION_LOG.json
日誌中的 status、issues、severity、path 和 message 才是主要證據。若是 Invalid,應先按日誌指出的檔案路徑定位,不要把錯誤歸類成單純「Apple 不接受」。若仍是 In Progress,則代表掃描結果尚未完成或尚未能讀取完整日誌;Apple 官方文件沒有承諾固定處理時間,因此不要用固定分鐘數判斷服務是否異常。(Apple Developer:Notary API)
注意: 公證服務已接受檔案,只能證明上傳請求成立;它不等於軟體已通過公證,也不等於票據已附加到 DMG、PKG 或 ZIP。
03 Developer ID 簽名鏈要從內到外檢查
macOS 站外分發通常需要與用途匹配的 Developer ID 身份。Developer ID Application 用於 App、Plug-in 和其他程式碼;Developer ID Installer 用於安裝套件。不能只查看最外層 .app 的簽名,因為 Framework、XPC Service、Plug-in、命令列工具和第三方二進位檔也可能造成拒絕。
先檢查外層 App:
codesign --verify --deep --strict --verbose=4 \
/PATH/TO/App.app
再查看實際簽名身份:
codesign -dv --verbose=4 /PATH/TO/App.app
對每個巢狀元件重複執行,不要只依賴 --deep 的單一結果。你需要特別確認:
| 檢查項目 | 日誌或命令可能暴露的問題 | 修復方向 |
|---|---|---|
| 簽名身份 | 出現 Mac Development、Ad Hoc 或本機開發身份 | 改用與分發方式匹配的 Developer ID 身份 |
| 私鑰 | Keychain 找不到私鑰、簽名無法建立 | 在發布用 Keychain 匯入完整憑證與私鑰 |
| 憑證鏈 | 找不到中繼憑證或驗證鏈不完整 | 更新並保留 Apple 要求的中繼憑證 |
| 安全時間戳 | 簽名資料缺少 secure timestamp | 重新簽名並確認發布命令使用正確參數 |
| 內容被修改 | 簽名後檔案雜湊或封裝內容改變 | 將所有修改、壓縮和封裝動作放在簽名之前 |
Apple 的 Developer ID 文件確認,站外分發需要使用相應的 Developer ID 憑證;公證工作流程也要求程式碼具備 Hardened Runtime 與適當的簽名資訊。證書有效性、撤銷狀態和安裝套件所用的身份,會影響後續驗證。
如果你正在做 Developer ID 憑證遷移與 Keychain 安全配置,先確保新環境能看見私鑰,再開始測試公證。否則你可能花時間修復 entitlements,實際上卻是簽名身份根本沒有被正確載入。
04 Hardened Runtime 與 entitlements 不要只看「已啟用」
啟用 Hardened Runtime 只是符合公證要求的起點。真正要檢查的是:App 實際使用的能力,是否與簽名時寫入的 entitlements 一致。
常見問題可分成三類:
- 格式錯誤:entitlements 檔案編碼不正確、包含 BOM,或 key 的結構不符合要求。
- 不必要的權限:為了讓程式「先跑起來」而加入除錯、JIT 或未簽名可執行記憶體相關權限。
- 簽名後被修改:App 已簽名後,腳本、封裝工具或安裝器又更換了二進位檔。
可先檢查簽名內實際載入的 entitlements:
codesign -d --entitlements :- \
/PATH/TO/App.app
再查看完整簽名資訊:
codesign -dvvv /PATH/TO/App.app
不要把「本機可以啟動」當成公證通過證據。Apple 的常見問題文件指出,格式不正確的 embedded entitlements 可能導致程式碼簽名驗證錯誤;entitlements 檔案本身也必須使用正確的 ASCII 編碼,不能含有 Unicode BOM。(Apple Developer:解決常見公證問題)
05 第二步:按巢狀程式碼逐項驗證
外層 App 簽名通過,不代表內部程式碼合格。建議按以下順序展開:
Contents/Frameworks/Contents/PlugIns/Contents/XPCServices/Contents/MacOS/- 由 App 啟動的命令列工具
- 第三方 SDK、原生二進位檔與額外載入模組
對每一個元件執行:
codesign --verify --strict --verbose=4 \
/PATH/TO/NESTED_OBJECT
若你在簽名完成後才複製 Framework、替換資源、注入設定檔或重新壓縮二進位檔,外層簽名可能仍然存在,但內部完整性已被破壞。這也是「macOS App 已簽名,為什麼仍然無法通過公證」的常見答案:簽名結果只證明某一時刻的內容,不能替你驗證後續封裝是否又修改了檔案。
06 DMG、PKG 和 ZIP 要分開看待
三種格式都可以提交公證,但排查重點不同。Apple 的公證工作流程涵蓋 DMG、平面安裝套件和 ZIP;自訂第三方安裝器則可能需要分別處理內部 App 與外部安裝器。
| 分發格式 | 主要檢查對象 | 常見後續問題 |
|---|---|---|
| ZIP | ZIP 內的 App 與巢狀程式碼 | 解壓後內容與提交前不一致 |
| DMG | DMG 內的 App、映像檔可寫性 | 公證成功但無法 stapler |
| PKG | App、安裝腳本與 Developer ID Installer | 安裝套件簽名身份錯誤或封裝後被修改 |
因此,不能用「ZIP 成功」推導「DMG 和 PKG 一定成功」。你應對最後實際提供給使用者的檔案執行 stapler 與 Gatekeeper 驗證,而不是只驗證中間產物。
07 公證成功但 stapler 失敗,要先查檔案與工具
如果 notarytool info 顯示公證已接受,但:
xcrun stapler staple /PATH/TO/RELEASE_FILE
仍然失敗,先不要重新簽名。這時問題可能位於票據下載、工具版本、網路連線或目標檔案不可寫,而不是程式碼被公證服務拒絕。
完成後再驗證:
xcrun stapler validate /PATH/TO/RELEASE_FILE
若是 DMG,確認映像檔沒有以唯讀或鎖定方式掛載;若是 PKG,確認目前帳戶有權修改檔案;若是 ZIP,確認你操作的是最終發布檔,而不是尚未完成封裝的中間檔。Apple 的公證問題文件也把舊版工具、磁碟映像檔不可寫和網路問題列為需要分開處理的範圍。
08 Gatekeeper 驗證是最後一關,不是公證日誌的替代品
完成 stapler 後,在接近使用者實際環境的 Mac 上執行:
spctl --assess --type execute --verbose=4 \
/PATH/TO/App.app
對安裝套件則使用相應的評估類型。你要同時保留:
codesign --verify的結果。notarytool info和notarytool log。stapler validate的結果。spctl的 Gatekeeper 評估結果。
Gatekeeper 會檢查站外分發軟體的 Developer ID 資訊;票據可以附加在可執行檔上,也可能由 Gatekeeper 在線上尋找。因此,「公證成功」與「使用者端能正常開啟」必須分開驗證。
09 遠端 Mac 的公證環境,按條件決定是否可交付
如果你只偶爾發布一次,短期使用現有 Mac 修復問題通常足夠。如果你每週或每天都要簽名、公證和發布,就應把憑證、Keychain、Command Line Tools、腳本與日誌保存方式固化在同一台可持續運行的 Mac 上。
使用下面的決策條件:
- 若只需要一次性修復,且本地 Mac 能穩定保留私鑰與 Keychain Profile,則先在本地完成驗收。
- 若本地 Mac 會睡眠、多人共用、憑證經常遺失,則改用隔離的遠端 Mac 發布環境。
- 若需要長時間執行 CI,且每次都要保留 Submission ID、JSON 日誌與 stapler 結果,則把發布腳本和工具鏈固定在可持續運行的主機。
- 若團隊要求實體 USB、特定硬體或本機互動式除錯,則不要只依賴遠端 Mac;保留本地設備作為補充。
在遠端環境保存憑據時,不要把 Issuer ID、Key ID、私鑰內容或 Keychain 密碼直接寫進 Git。優先使用 notarytool 支援的 Keychain Profile,並限制登入帳戶、檔案權限及遠端連線權限。Apple 的遷移文件說明,notarytool 可使用 Keychain 儲存的憑據,並可透過 Command Line Tools 取得,不一定要在每台主機安裝完整 Xcode。(Apple Developer:遷移至最新公證工具)
若你需要把這套流程長期化,可再查看 遠端 Mac 自動構建 macOS App 與 macOS 發布伺服器驗收方向。重點不是「有沒有一台 Mac」,而是每次發布是否都能重現相同的簽名、提交、日誌、票據和 Gatekeeper 驗證結果。
10 發布前可勾選的最小驗收卡
- [ ]
xcode-select指向預期的 Xcode 或 Command Line Tools。 - [ ]
xcrun notarytool可以正常執行。 - [ ] App 使用 Developer ID Application 身份。
- [ ] PKG 使用 Developer ID Installer 身份。
- [ ] 所有 Framework、Plug-in、XPC Service 和命令列工具都已驗證。
- [ ] Hardened Runtime 與 entitlements 和實際功能一致。
- [ ] 簽名包含安全時間戳。
- [ ] 已保存 Submission ID。
- [ ] 已下載並保存脫敏後的公證 JSON 日誌。
- [ ] 最終 DMG、PKG 或 ZIP 已完成 stapler 驗證。
- [ ] 已在測試 Mac 上取得
spctl評估結果。 - [ ] 憑據沒有出現在原始碼庫、公開 CI 輸出或聊天紀錄中。
如果你目前的方案是偶爾借用同事的 Mac、依賴會睡眠的本地電腦,或把憑證和發布腳本散落在多台主機,常見缺點是環境不一致、Keychain 難以持久保存,以及失敗後缺少完整日誌。對需要重複發布的獨立開發者而言,CALMVPS 的遠端 Mac 能提供一個長時間在線、可保留工具鏈與發布腳本的 macOS 環境;但若你是長期滿載編譯、需要實體介面,或已經擁有穩定本地 Mac,直接自購設備可能更合適。你可以先完成上面的驗收卡,再判斷租用遠端 Mac 是否能真正解決目前的公證維護成本。