首页 > 解决方案 > macOS:在脚本中公证?

问题描述

由于 Xcode 的代码设计和归档非常耗时、无聊且有问题,因此我一直使用命令行工具等通过我自己的脚本对我的 Developer ID 签名的 macOS 应用程序进行代码签名、归档和xcodebuild发布codesign。公证看起来将是一个很大的痛苦。是否可以在我的脚本中添加公证?

标签: macosnotarize

解决方案


是的。不幸的是,官方的回答留下了一些松散的结局,例如奎因“爱斯基摩人”的这个重要花絮。这是如何做到的:

一次性设置

获取应用专用密码

确定公证应用程序的“应用程序”名称。我使用我的产品运输脚本的名称,SSYShipProduct.pl因为这是将使用此密码的“应用程序”。我们将您编写的任何名称称为your-notarizing-name

浏览到https://appleid.apple.com/account/manage,滚动到Security > App-Specific Password,然后为名为your-notarizing-name的应用程序生成一个App-Specific密码。复制它给你的密码。我们称之为app-specific-password

将应用专用密码添加到您的 macOS 钥匙串

运行此命令将您刚刚创建的密码添加到您的钥匙串中:

security add-generic-password -a "your-apple-ID-email" -w "app-specific-password" -s "your-notarizing-name"

参数是该-s项目在您的钥匙串中的名称。我认为您实际上可以使用不同的名称,但在我看来,在这里使用也是有意义的your-notarizing-name

您可以通过在Keychain Access应用程序中搜索来验证它是否有效。但是,请注意,在您退出并重新启动之前,新项目不会在Keychain Access中列出。

也许,获取相关的itc-provider

如果您的 Apple ID 与多个 Apple Developer Connection 团队相关联(例如,如果您从事合同工作),您将需要该应用程序应为其公证的团队的itc_provider 。

要查找您团队的itc_provider,请执行以下命令:

/Applications/Xcode.app/Contents/Applications/Application\ Loader.app/Contents/itms/bin/iTMSTransporter -m provider -u "your-apple-ID-email" -p "app-specific-password"

滚动到此命令打印的输出的末尾并查看Provider 列表。复制所需团队的简称。我们称其为“开发者-团队-itc-提供者”。

对于每批货物(可编写脚本!)

如果您使用命令行工具对应用程序的组件进行签名/usr/bin/codesign,则 codesign 的每次调用都必须具有以下新参数 parameter ,它告诉 codesign 使用所谓的强化运行时进行签名:

 `--options runtime`

相反,如果您的应用在 Xcode 中登录,则必须在所有可执行组件目标中将 Xcode 10 或更高版本中可用的 Build Setting Hardened runtime设置为Yes 。

除此之外,您的脚本应该在发布配置中创建您的应用程序的构建并对其进行代码设计,这与公证前的日子相同。

上传至 Apple Notary 服务

然后,您的脚本应将您的应用程序归档为 .zip 或 .dmg。请注意,这是一个临时文件,只会上传到 Apple Notary 服务,不会发货。

然后,您的脚本应该构成一个主要的捆绑 ID 值,这将是您的应用的捆绑标识符,带有.zip.dmg附加。示例:您的 pbid 值= com.mycompany.YourApp.zip

在下文中,您的脚本将使用altool,这是 Apple 对Application Loader Tool的名称。

然后,您的脚本应运行此命令以使您的 .zip 或 .dmg 公证:

/usr/bin/xcrun altool --notarize-app --primary-bundle-id "your-pbid-value" --username "your-apple-id-email" --password "@keychain:your-notarizing-name" -itc_provider "developer-team-itc-provider" --file /path/to/YourApp.zip/or/YourApp.dmg --output-format "xml"

(请注意,在上面的命令中,奇怪的是,所有参数名称前面都有两个破折号,除了-itc_provider前面只有一个破折号。另外,如果您使用的脚本语言@在字符串中插入字符,请对其进行编码以防止插入@keychain)。

大约一分钟后,xcrun将退出并打印到标准输出一些 XML,如果您的提交被接受(注意:尚未批准),将如下例所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>notarization-upload</key>
    <dict>
        <key>RequestUUID</key>
        <string>2ab59b26-19ec-4a30-84cf-6d2cb8d3c97e</string>
    </dict>
    <key>os-version</key>
    <string>10.15.0</string>
    <key>success-message</key>
    <string>No errors uploading 'path/to/YourApp.zip'.</string>
    <key>tool-path</key>
    <string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
    <key>tool-version</key>
    <string>1.1.1138</string>
</dict>
</plist>



你真正需要的就是那个RequestUUID价值。但是,由于我经常发布四个应用程序,并且因为我的发布脚本失败而没有提供有用的错误信息,这会毁了我的一天,而且因为(见下文)您将进行另一个调用,它也会返回有趣的 XML,所以我投入了一些时间向我的脚本添加一个子例程,该子例程接受两个参数,XML 和一个键路径,并返回给定键路径处 XML 的值。在上面的例子中,我调用这个子程序来获取RequestUUID,然后再次获取success-message.

(我的脚本在 Perl 中。虽然在 CPAN 中有一个名为XML::Simple的模块,它可以在一两行内完成此解析,但维护者将其标记为不用于新设计。因此,为了避免需要为了安装和使用真正的XML 解析器, 我选择使用PlistBuddy@khuttun 评论中的建议. 这有点痛苦, 不幸的是,altool没有将其输出写入文件的选项, 并且PlistBuddy没有记录接受标准输入。所以我的子程序将标准输出写入altool一个临时文件,然后将该临时文件的路径传递给 PlistBuddy。有点恶心,但它有效。)

删除未装订的 zip 包

此时,我建议您的脚本删除它上传的.zipor文件。.dmg原因:该文件是从尚未装订公证票的产品中存档的。在脚本结束时,您将创建一个新的.zip.dmg从具有票证的修改后的应用程序。立即删除文件可防止您错误地发送未装订的应用程序。

循环等待苹果的回应

然后,您的脚本可以开始纠缠 Apple 的服务器以获得最终结果,方法是在循环中运行此命令以及一些睡眠:

`/usr/bin/xcrun altool --notarization-info --username "your-apple-id-email" --password "@keychain:your-notarizing-name" --output-format "xml"

如果您的脚本立即运行此命令,它将在标准输出中返回一些 xml,类似于以下示例:

<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>notarization-info</key>
    <dict>
        <key>Date</key>
        <date>2019-08-07T01:17:37Z</date>
        <key>RequestUUID</key>
        <string>4ba71353-9d99-4b52-b579-37f384717130</string>
        <key>Status</key>
        <string>in progress</string>
    </dict>
    <key>os-version</key>
    <string>10.15.0</string>
    <key>success-message</key>
    <string>No errors getting notarization info.</string>
    <key>tool-path</key>
    <string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
    <key>tool-version</key>
    <string>1.1.1138</string>
</dict>
</plist>


其中重要的关键路径是notarization-info:Status,其值in progress意味着 Apple 仍在处理您的提交。通常几分钟后(Apple 说“应该不到一个小时”,但我在 2019 年 7 月 4 日的美国假日下午经历了长达三个半小时的时间),altool将返回到你的脚本一个不同的 xml在标准输出中,是这样的:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>notarization-info</key>
    <dict>
        <key>Date</key>
        <date>2019-08-06T23:28:25Z</date>
        <key>LogFileURL</key>
        <string>https://osxapps-ssl.itunes.apple.com/itunes-assets/Enigma113/v4/f6/09/be/f609bee3-b031-323a-0987-d1f620a78758/developer_log.json?accessKey=1565410613_1722173034418364591_TvycjBAzd6FRTYGKZEFU6EwDfsws8Wa1MV%2FYnTiJ1zyOZamc%2FoeO5RMeIzZN669ZQJgO2Q4W48ipKNFO%2BQGuq%2FITXN8MQAetbNe90w9ogzqXbrzTHg%2FgYK89yvEFmiiRxhaVlZqLI93NBpY0hwBqXv2bvvlg%2FRCc%2BVaCNRJ%2BrnE%3D</string>
        <key>RequestUUID</key>
        <string>07fc3745-b0ff-4d1a-9b15-37f384717130</string>
        <key>Status</key>
        <string>success</string>
        <key>Status Code</key>
        <integer>0</integer>
        <key>Status Message</key>
        <string>Package Approved</string>
    </dict>
    <key>os-version</key>
    <string>10.15.0</string>
    <key>success-message</key>
    <string>No errors getting notarization info.</string>
    <key>tool-path</key>
    <string>/Applications/Xcode.app/Contents/Applications/Application Loader.app/Contents/Frameworks/ITunesSoftwareService.framework</string>
    <key>tool-version</key>
    <string>1.1.1138</string>
</dict>
</plist>


在进行一些逆向工程之后,您会看到,在每次循环迭代中,您的脚本应该解析 XML 并在 的值Status不是的时候跳出循环in progress,或者如果您愿意,LogFileURL也可以在定义的时候跳出循环。或者,如果您更喜欢电子邮件触发器,您的脚本可以查找来自 Apple 的带有主题行的电子邮件您现在可以分发您的 Mac 软件。.

更新 2019-11-02

在我的最后几批货物中这一步遇到问题之后,今天又一次,我现在确认了 Apple 公证服务中的一个错误。错误是altool --notarization-info命令将失败 1-5 小时,返回非零退出代码,并在标准输出中出现错误代码 1519“找不到 RequestUUID”,如以下示例标准输出中所示:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
    <key>os-version</key>
    <string>10.15.1</string>
    <key>product-errors</key>
    <array>
        <dict>
            <key>code</key>
            <integer>1519</integer>
            <key>message</key>
            <string>Could not find the RequestUUID.</string>
            <key>userInfo</key>
            <dict>
                <key>NSLocalizedDescription</key>
                <string>Could not find the RequestUUID.</string>
                <key>NSLocalizedFailureReason</key>
                <string>Apple Services operation failed.</string>
                <key>NSLocalizedRecoverySuggestion</key>
                <string>Could not find the RequestUUID.</string>
            </dict>
        </dict>
    </array>
    <key>tool-path</key>
    <string>/Applications/Xcode.app/Contents/SharedFrameworks/ContentDeliveryServices.framework/Versions/A/Frameworks/AppStoreService.framework</string>
    <key>tool-version</key>
    <string>4.00.1181</string>
</dict>
</plist>


这是一个错误,因为当然我的脚本确实提交了它刚刚从 Apple Notary Service 收到的请求 UUID,Apple应该能够找到它,此外,当我继续手动发送命令时,大约 2 小时后,突然,命令返回Success并继续返回Success后续命令,我收到了来自 Apple 的Success电子邮件。这个延迟发生在今天,有 7 个不同的好请求 UUID,最长的是 5 小时。可能此时,Apple Notary Service 创建和向您发送 Request UUID 之间存在 1-5 小时的延迟,并且它出现在 Apple Notary Service 用于响应notarization-info请求的数据库中,因此您会收到此错误错误。很伤心。

由于我无法控制 Apple 何时指派人员修复错误,因此我修改了脚本的此阶段以解析来自 Apple 的响应,并且仅当命令返回非零退出状态code一个 (index=0)product-errors数组条目的不是 1519。如果您像我一样使用 PlistBuddy 解析 XML,那么代码的关键路径应该是product-errors:0:code. 每次收到错误 1519 时,我的脚本中的循环都会打印出来,所以我可以看到发生了什么,当然,while如果错误代码是 1519,我已经修改了它的条件以不退出。

在修复了我的脚本之后,我有几个应用程序要发布。Apple Notary Service 很好地处理了第一个:No Errors 1519,大约两分钟后成功。然而,下一个需要我的脚本的这个新功能。在 09:54 (HH:mm) 我的脚本收到了来自 Apple 的请求 UUID。20 秒后,它发送了第一个altool --notarization-info查询。响应是错误的错误 1519。随后的查询也返回错误的错误 1519,持续了将近 3 个小时,直到 12:44。然后,在 12 点 45 分,它突然收到了in progress回复。在 12:47再回复 5 次后in progress,终于,Success

离开本主题之前还有一件事:该请求成功后一小时没有错误 1519,一小时前的先前请求突然开始返回in progress,然后几分钟后,Success。结论:绕道进入错误 1519 沼泽的请求 UUID 不会与后来的请求 UUID 排队 FIFO,这可能会偶然避免错误 1519 绕道。因此,更好的解决方法可能是在收到更多错误 1519 响应后放弃请求 UUID,并通过将应用程序重新上传到 Apple Notary Service 并获取另一个您希望效果更好的请求 UUID 重新开始。当然,在接下来的几个小时内,您会收到很多电子邮件,因为您放弃的所有请求 UUID 最终都会成功。

无论如何,现在,进入脚本的下一步……</p>

检查 Apple 的日志文件

您的脚本应该解析出 的值,LogFileURL以便它可以检查日志,因为即使公证成功,Apple 创建的日志文件也可能包含警告。要获取日志文件,您的脚本当然应该:

curl <LogFileURL-Value>

日志文件显然是 JSON。警告或错误以数组的形式呈现,即 key 的值issues。因此,您的脚本应该curl使用 JSON 解析器解析该输出,如果 key 的值为issuesJSON null 或空数组,则继续发送。

将票装订到您的应用程序

这一步很简单……</p>

xcrun stapler staple /path/to/YourApp.app

运行此命令将在您的应用程序包中添加一个新文件:YourApp.app/Contents/CodeResources. 这显然是你的公证票。请注意,该文件是对YourApp.app/Contents/_CodeSignature/CodeResources仍然存在的文件的补充,并且包含代码签名,与公证前的日子相同。

验证票装订

但是有一种更好的方法来验证您的应用程序现在是否有一张好票。您的脚本现在应该运行(或重新运行)Gatekeeper 检查:

spctl -a -v /path/to/YourApp.app

结果,在标准错误中,应该是,

/path/to/YourApp.app: accepted
source=Notarized Developer ID

除了插入Notarized之外,这与 pre-notarization 的结果相同。如果未检测到上述单词,精明的脚本将解析该标准错误并中止发送。

邮编和运输

现在已经添加了票证,您的脚本可以再次压缩或 dmg 您的 .app,但这一次,发送它。


推荐阅读