macOS:在脚本中公证? [英] macOS: Notarize in Script?

查看:186
本文介绍了macOS:在脚本中公证?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

由于Xcode的代码签名和存档非常耗时,枯燥且有问题,因此我一直使用命令行工具xcodebuildcodesign等对我的开发人员ID签名的macOS应用进行代码签名,存档和交付.通过我自己的脚本.公证看起来将是一个巨大的痛苦.可以在我的脚本中添加公证吗?

Because the codesigning and archiving by Xcode is time-consuming, boring and problematic, I've always codesigned, archived and shipped my Developer ID signed macOS app using the command-line tools xcodebuild, codesign, etc. via my own script. Notarizing looks like it is going to be a major pain. Is it possible to add notarizing to my script?

推荐答案

是.不幸的是,官方答案留下了一些松懈的结局,例如,这奎因(Quinn)爱斯基摩人"的重要花絮.这是操作方法:

Yes. Unfortunately, the official answer leaves some loose ends, for example this important tidbit from Quinn "the Eskimo". Here is how to do it:

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

Decide on a name for your "app" of notarizing apps. I use the name of my product-shipping script, SSYShipProduct.pl because this is the "app" which will use this password. We shall refer to whatever name you compose as your-notarizing-name.

浏览到 https://appleid.apple.com/account/manage ,滚动到安全> 应用专用密码,并为名为您的应用生成应用专用密码密码-notarizing-name .复制它提供给您的密码.我们将其称为 app-specific-password .

Browse to https://appleid.apple.com/account/manage, scroll to Security > App-Specific Password, and generate an App-Specific password for an app named your-notarizing-name. Copy the password that it gives you. We shall call that app-specific-password.

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

Run this command to add the password you just created to your keychain:

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

-s参数是此项目将在您的钥匙串中使用的名称.我认为您实际上可以使用其他名称,但是在我看来,也可以在此处使用your-notarizing-name.

The -s parameter is the name that this item will have in your Keychain. I think you could actually use a different name, but in my mind it makes sense to use your-notarizing-name here too.

您可以通过在 Keychain Access 应用程序中进行搜索来验证其是否有效.但是,请注意,直到退出并重新启动后,新项目才会在钥匙串访问中列出.

You can verify that it worked by searching in the Keychain Access application. However, be aware that new items are not listed in Keychain Access until after you quit and relaunch it.

如果您的Apple ID与一个以上的Apple Developer Connection团队相关联(例如,如果您从事合同工作),则需要该应用程序应经过公证的团队的 itc_provider .

If your Apple ID is associated with more than one Apple Developer Connection team (such as if you do contract work), you will need the itc_provider of the team for which this app should be notarized.

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

To find the itc_provider of your team, execute this command:

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

滚动到此命令输出的输出的末尾,并查看 Provider listing 表.复制所需团队的简称.我们将其称为"developer-team-itc-provider".

Scroll to the end of the output printed by this command and look at the Provider listing table. Copy the Short Name of the desired team. We shall call this "developer-team-itc-provider".

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

If you sign components of your app using the /usr/bin/codesign command line tool, each invocation of codesign must have the following new argument parameter , which tells codesign to sign with the so-called hardened runtime:

 `--options runtime`

相反,如果您的应用程序已使用Xcode登录,则必须在所有可执行组件目标中将Xcode 10或更高版本中可用的构建设置 Hardened运行时设置为.

Conversely if your app is signed in Xcode, you must set the Build Setting Hardened runtime, available in Xcode 10 or later, to Yes in all executable component targets.

除此之外,与公证前的日子一样,您的脚本应在Release配置中创建应用的构建并对其进行代码签名.

Other than that, your script should create a build of your app in Release configuration and codesign it, same as in pre-notarization days.

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

Your script should then archive your app to a .zip or .dmg. Note that this is an interim file which will only be uploaded to the Apple Notary service, not shipped.

然后,您的脚本应组成一个主捆绑包ID值,该值将是您应用程序的捆绑包标识符,并附加.zip.dmg.例如:您的出价值 = com.mycompany.YourApp.zip.

Then, your script should compose a primary bundle ID value, which will be your app's bundle identifier with .zip or .dmg appended. Example: your-pbid-value = com.mycompany.YourApp.zip.

接下来,您的脚本将使用altool,这是Apple在 Application Loader Tool 中的名称.

In what follows, your script will use altool, which is Apple's name for Application Loader Tool.

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

Your script should then run this command to get your .zip or .dmg notarized:

/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的插值.

(Note that, in the above command, oddly, all argument names are preceded by two dashes except -itc_provider is preceded by only one dash. Also, if the scripting language you are using interpolates @ characters in strings, code it to prevent interpolation of @keychain).

大约一分钟后,xcrun将退出并打印到标准输出一些XML,如果您的提交被接受(请注意:尚未得到批准),它将看起来像这个例子:

After a minute or so, xcrun will exit and print to stdout some XML which, if your submission was accepted (note: not approved yet), will look like this example:

<?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.



All you really need out of there is that RequestUUID value. However, since I ship four apps often, and because it ruins my day when my shipping script fails without providing helpful error information, and because (see below) you are going to make another call which also returns interesting XML, I invested some time in adding to my script a subroutine which takes two parameters, XML and a key path, and returns the value of the XML at a given key path. In the case above, I call this subroutine to get the RequestUUID, and then again to get the success-message.

(我的脚本在Perl中.尽管CPAN中有一个名为 XML :: Simple 的模块,可以在一两行中进行此解析,但维护者将其标记为不是因此,为避免需要使用 real XML解析器进行安装和使用,我选择使用@chuttun的注释中建议的PlistBuddy,这也有些痛苦,因为,不幸的是,altool没有将其输出写入文件的选项,并且没有记录PlistBuddy接受stdin.因此,我的子例程将altool的stdout写入一个临时文件,然后将该临时文件传递给该文件.文件到PlistBuddy的路径.有点令人作呕,但可以.)

(My script is in Perl. Although there is available in CPAN a module named XML::Simple which can do this parsing in a line or two, it is marked by the maintainer as not for use in new designs. So, to avoid needing to install and wrangle with a real XML parser, I opted instead to use PlistBuddy as suggested in the comment by @khuttun. This was slightly painful also because, unfortunately, altool does not have an option to write its output to a file, and PlistBuddy is not documented to accept stdin. So my subroutine writes the stdout from altool to a temporary file, and then passes that temporary file's path to PlistBuddy. Kind of disgusting, but it works.)

在这一点上,我建议您的脚本删除其上载的.zip.dmg文件.原因:该文件是从尚未将您的公证证书装订在其上的产品中存档的.在脚本的最后,您将通过具有票证的修改后的应用程序创建一个新的.zip.dmg.立即删除该文件可防止您误寄出未装订的应用程序.

At this point, I recommend that your script delete the .zip or .dmg file which it uploaded. Reason: That file was archived from a product which does not yet have your notarization ticket stapled to it. At the end of your script, you will create a new .zip or .dmg from a modified app which has the ticket. Deleting the file immediately prevents you from shipping an un-stapled app by mistake.

然后,通过循环运行以下命令并进行一些睡眠,您的脚本即可开始为您的最终结果困扰Apple的服务器:

Your script can then start pestering Apple's server for your final results, by running this command in a loop along with with some sleep:

`/usr/bin/xcrun altool --notarization-info --username"your-apple-id-email" –密码"@keychain:your-notarizing-name"-输出格式"xml"

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

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

If your script runs this command immediately, it will get returned in stdout some xml which will look something like this example:

<!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将恢复到您的脚本stdout中的另一个xml,如下所示:


The significant key path in there is notarization-info:Status, whose value in progress means that Apple is still working on your submission. After a few minutes usually (Apple says "should be less than an hour", but I experienced times of up to three and a half hours on the USA holiday afternoon of 2019-Jul-04), altool will retturn to your script a different xml in stdout, something like this:

<?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>


经过一些反向工程之后,您会看到,在每次循环迭代中,只要Status的值不是in progress的值,或者(如果愿意)已定义.或者,如果您更喜欢电子邮件触发器,则脚本可以查找主题行为的Apple发出的电子邮件.您现在可以分发Mac软件..


After some reverse-engineering, you see that, in each loop iteration, your script should parse the XML and break out of the loop whenever the value of Status is something other than in progress, or if you prefer, when LogFileURL is defined. Or if you prefer email triggers, your script can look for an email from Apple with subject line You can now distribute your Mac software..

更新2019-11-02

UPDATE 2019-11-02

在我最后几次发货中都遇到了这一步骤的麻烦之后,今天又一次,我现在已经确认了Apple的公证服务中的一个错误.该错误是altool --notarization-info命令将在1-5小时内失败,返回非零退出代码,并在stdout中显示错误代码1519找不到RequestUUID",如以下示例stdout中所示:

After having trouble with this step in my last couple shipments, and again today, I have now confirmed a bug in Apple's Notary Service. The bug is that the altool --notarization-info command will fail for 1-5 hours, returning nonzero exit codes, and in stdout an error code 1519 "Could not find the RequestUUID", as in the following example stdout:

<?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>


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


This is a bug because, of course my script did submit the Request UUID which it just received from Apple Notary Service, Apple should be able to find it and, furthermore, when I kept sending the command manually, after about 2 hours, suddenly, the command returned Success and continued to return Success with subsequent commands, and I got the Success email from Apple. This delay happened today with 7 different good Request UUIDs, the longest was 5 hours. Possibly, at this time, there is a 1-5 hour delay between Apple Notary Service creating and sending you a Request UUID, and it appearing in the database which Apple Notary Service uses to respond to notarization-info requests, so you get this false error. Very sad.

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

Since I have no control over when Apple assigns people to fix bugs, I have modified this stage of my script to parse the response from Apple and die only if the command returns nonzero exit status and the code of the first (index=0) product-errors array entry is not 1519. If you are using PlistBuddy to parse XML as I am, the key path for that is code should be product-errors:0:code. The loop in my script prints each time Error 1519 is received, so I can see what is going on, and of course, I've modified its while condition to not exit if the error code is 1519.

修复了脚本之后,我有几个应用程序要发布. Apple Notary Service很好地对待了第一个:两分钟后,No Error 1519和 Success 成功.但是,下一个需要我脚本的这一新功能.在时间09:54(HH:mm),我的脚本收到了Apple的Request UUID. 20秒后,它发送了第一个altool --notarization-info查询.响应是错误的错误1519.随后的查询也返回错误的错误1519,持续了将近3个小时,直到12:44.然后,在12:45,突然它收到了in progress响应.在另外5个in progress响应之后,终于在12:47,成功.

After so fixing my script, I had several apps to ship. Apple Notary Service treated the first one nicely: No Errors 1519, and Success after about two minutes. The next one, however, needed this new feature of my script. At time 09:54 (HH:mm) my script received the Request UUID from Apple. 20 seconds later, it sent the first altool --notarization-info query. The response was a false Error 1519. Subsequent queries also returned false Errors 1519, for almost 3 hours, through 12:44. Then, at 12:45, all of a sudden it received an in progress response. After 5 more in progress responses, at 12:47, finally, Success.

离开本主题之前的另一件事:该请求成功执行一个小时,没有出现错误1519,一个小时前的先前请求突然开始返回in progress,然后几分钟后,成功返回 .结论:绕开到错误1519混乱中的请求UUID不会与以后的请求UUID一起排队在FIFO中,这可能会避免错误1519绕道.因此,更好的解决方法可能是在收到另一个错误1519响应后放弃请求UUID,然后通过将应用程序重新上载到Apple Notary Service重新开始,并获取另一个您希望能更好地工作的请求UUID.当然,在接下来的几个小时内,您将收到许多电子邮件,因为您放弃的所有请求UUID最终都会成功.

One more thing before leaving this topic: An hour after that request succeeded with no Errors 1519, a prior request from an hour ago suddenly started returning in progress and then a few minutes later, Success. Conclusion: Request UUIDs which get detoured into the Error 1519 morass are not queued FIFO with later Request UUIDs which might, by chance, avoid the Error 1519 detour. So, a better workaround might be to abandon a Request UUID after receiving one more Error 1519 responses and start over by re-uploading the app to Apple Notary Service and getting another Request UUID which you hope will work better. Of course, you'll get many emails during the next few hours as all of the Request UUIDs which you abandoned eventually succeed.

无论如何,现在,继续进行脚本的下一步……

At any rate, now, on to the next step in the script…

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

Your script should parse out the value of the LogFileURL so it can check the log, because because even if notarization succeeds the Log file created by Apple might contain warnings. To get the Log file your script should, of course,

curl <LogFileURL-Value>

日志文件显然是JSON.警告或错误以数组形式显示,它是键issues的值.因此,您的脚本应使用JSON解析器解析curl的输出,并且如果键issues的值是JSON空或空数组,请继续发货.

The Log file is apparently JSON. Warnings or Errors are presented as an array, which is the value of key issues . So your script should parse that curl output with a JSON parser and if the value of key issues is a JSON null or an empty array, continue shipping.

这一步很简单……

xcrun stapler staple /path/to/YourApp.app

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

Running this command will add to your app's package a new file: YourApp.app/Contents/CodeResources. This is apparently your notarization ticket. Note that this file is in addition to the file YourApp.app/Contents/_CodeSignature/CodeResources which is still there, and contains the code signature, the same as in pre-notarization days.

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

But there is a better way to verify that your app now has a good ticket. Your script should now run (or re-run) a Gatekeeper check:

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

在stderr中的结果应该是

The result, in stderr, should be,

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

除插入 Notarized 之外,与预公证的结果相同.如果未检测到上述单词,则精明的脚本将解析该stderr并中止发货.

which is the same result as pre-notarization, except for the insertion of Notarized. Astute scripts will parse that stderr and abort shipping if the above words are not detected.

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

Now that the ticket has been added, your script can zip or dmg your .app again, but this time, ship it.

这篇关于macOS:在脚本中公证?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆