导语
macOS 下 AppStore 不是唯一能下载 App 的渠道,做为应用的开发者,我们也能把应用发布在网站上提供给用户下载安装。那么,我们如何让用户信任我们开发的软件呢?对此,苹果提供了公证的服务和结合操作系统的Gatekeeper
,给用户提供了一层信心的保障。本文将介绍三种不同公证方式的选择。
公证
公证其实本质上是把(App、安装包)上传到苹果的公证服务进行公证,然后在安装的过程中Gatekeeper
会去请求服务器,根据返回的数据判断App是否公证检验通过。或者在离线状态下读出可执行文件中内嵌的ticket
判断。
为什么要对应用进行「公证」
从 macOS 10.15 之后,苹果系统要求App和工具需要进行工具才能正常的安装,不然会报“未知开发者应用,移除到废纸”,2020 年 1 月之后的公证也变得更加严格。
如果是一个没有经过公证的就会看到下面的提示:
如果是一个有经过公证的应用,就是这样的提示:
Apple checked it for malicious and none was detected.
用户看到这句话,就不会怀疑你的应用了。
公证能支持以下这几种:
- macOS apps
- Non-app bundles, such as kernel extensions
- Disk images (UDIF format)
- Flat installer packages
接下来我们看看公证需要准备哪些东西和条件。
公证前的准备
开发者证书
个人和企业证书都可以,免费的个人证书不行。
Application Specific Password
如果不是通过 Xcode Archive 进行公证,而是通过自动化工作流来实现公证的话,就需要使用苹果的application specific password来进行认证。
官方使用 app-specific passwords 介绍
以 Xcode 发布方式下的公证
App 形式的公证可以直接在 Xcode 的发布中完成,在 Xcode 的 Product 菜单栏中点击 Archive,Archive 完成后,在 Xcode's Organizer window 中选择对应的 Archived 文件,然后点击 Distribute App
这种方式的官方有比较完善的文档,在本文章就不过多的展开了。
以工具notarytool
方式下的公证
当直接使用 Xcode 的标准公证不能满足需求的时候,我们就得通过命令行工具来进行公证,比如这些情况:
- 公证已经发布了的 App 。
- 第三方软件的插件开发的公证。
- 对 Xcode 自定义编译的 targets,不是macOS app类型的这种情况下的公证。
- 发布 disk image(dmg 后缀) 或 installer packages(pkg 后缀)安装包下的公证。
接下来以发布一个命令行工具进行举例,因为苹果公证服务不能直接对一个binary excutable 进行公证(支持 zip、dmg、pkg 文件类型),我们需要先把它打成 pkg 安装包,然后再对这个.pkg
文件进行公证,这个时候就没办法在Xcode 中通过点 Archive 完成操作,需要自己通过 notarytool
这个工具来完成公证。
保存 Credentials
首先,不同于 xcrun altool --notarization-info
以前的公证方式,notarytool
的公证步骤更加简洁,credentials
是一个 notarytool
读取公证 app-specific-password
等信息的一个文件。所以我们在前面已经生成了app-specific-password
,接下来把这个密钥保存到keychain
中来,以便后续 notarytool
直接使用。
具体操作是这样的,输入一下命令:
代码语言:txt复制xcrun notarytool store-credentials --apple-id "yourAppleID" --team-id "yourTeamID"
通过这个命令进行交互式的操作,在命令行中你将需要输入profile name
和 app-specific password
,成功后将会看到以下信息:
如果不确定 --team-id 的值,可以使用命令
xcrun altool --list-providers -u "yourAppleID" -p "app-specific-password"
查询。
编译注意事项
项目用开发者证书进行编译
开启 Enable Hardened Runtime
Info.plist 文件
代码语言:txt复制- 关联 Info.plist 并且在 二进制的文件中创建 Info.plist 的 Section 段
timpstamp
往二进制文件中打入 timpstamp
字段。需要通过 Achive 才会有 timpstamp
字段
可以通过命令 codesign -ddv binary-file-path
检查
pkgbuild 进行打包
man pkgbuild
命令行示例:
代码语言:shell复制% pkgbuild --root "your-binary-file-directory"
--identifier "your-identify"
--version "1.0"
--install-location "/"
--sign "Developer ID Installer: Name (yourTeamID)"
app.pkg
打包后检查安装包的文件是否符合预期,双击打开pkg 安装包,在菜单栏文件 -> 显示文件中查看。
提交公证
通过上面的步骤操作下来,我们已经可以开始把pkg 文件提交公证了,操作命令为:
代码语言:shell复制% xcrun notarytool submit app.pkg
--keychain-profile "your-specified-profile-name"
--wait
添加票据
发布前,还需要将票据添加到安装包中,这样才可以在没有网络下安装时能被Gatekeeper
验证通过。
操作命令如下:
代码语言:txt复制xcrun stapler staple app.pkg
验证是否添加成功的命令:
代码语言:txt复制xcrun stapler validate app.pkg
最后做一个整体的验证:
代码语言:txt复制spctl --assess -vv --type install app.pkg
以 web 方式下的公证
以上的两种公证的方式都比较依赖 macOS 的操作系统,但是如果你的公证的自动化流程中希望不要依赖 macOS 的操作系统,那么就可以采用苹果提供的 notary service'REST API 进行公证。
JSON Web Token(JWT)
App Store Connect API 是通过 JWTs 来对每一次请求进行验证,每次请求都得包含这个token("Authorization: Bearer <token>"
)
% curl -v -H "Authorization: Bearer <token>" "https://appstoreconnect.apple.com/notary/v2/submissions"
需要到 App Store Connect 下载 Private Key然后保存好。 JWT需要用到 Private Key 来进行签名,具体格式看jwt.io上的Encode&Decode。
请求公证
公证URL
代码语言:txt复制POST https://appstoreconnect.apple.com/notary/v2/submissions
Request Body
代码语言:json复制body = {
"submissionName": "app.pkg",
"sha256": sha256,
"notifications": [{"channel": "webhook", "target": "https://example.com" }]
}
Response Body
代码语言:json复制{ "data": {
"attributes": {
"awsAccessKeyId": "ASIAIOSFODNN7EXAMPLE",
"awsSecretAccessKey": "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
"awsSessionToken": "AQoDYXdzEJr...",
"bucket": "EXAMPLE-BUCKET",
"object": "EXAMPLE-KEY-NAME"
},
"id": "2efe2717-52ef-43a5-96dc-0797e4ca1041",
"type": "submissionsPostResponse"
},
"meta": {
}
}
从 Response 拿到的信息能在下一步中将pkg 上传到 Amazon S3 endpoint。
上传pkg
官方推荐使用 Amazon 提供的 boto3
Library 进行上传,如下代码片段:
import boto3
aws_info = output["data"]["attributes"]
bucket = aws_info["bucket"]
key = aws_info["object"]
sub_id = output["data"]["id"]
s3 = boto3.client(
"s3",
aws_access_key_id=aws_info["awsAccessKeyId"],
aws_secret_access_key=aws_info["awsSecretAccessKey"],
aws_session_token=aws_info["awsSessionToken"],
config=Config(s3={"use_accelerate_endpoint": True})
)
resp = s3.upload_file("app.pkg", bucket, key)
检查公证结果
获取 Submission Status 接口
代码语言:txt复制GET https://appstoreconnect.apple.com/notary/v2/submissions/{submissionId}
submissionId (Required)
这里填入的值是在请求公证的接口返回的id。
Response
代码语言:json复制{
"data": {
"attributes": {
"createdDate": "2022-06-08T01:38:09.498Z",
"name": "OvernightTextEditor_11.6.8.zip",
"status": "Accepted"
},
"id": "2efe2717-52ef-43a5-96dc-0797e4ca1041",
"type": "submissions"
},
"meta": {
}
}
如果认证失败,想看失败原因,可以调用日志接口
URL
代码语言:txt复制GET https://appstoreconnect.apple.com/notary/v2/submissions/{submissionId}/logs
submissionId (Required)
这里填入的值是在请求公证的接口返回的id。
代码语言:txt复制{
"data": {
"attributes": {
"developerLogUrl": "https://..."
},
"id": "2efe2717-52ef-43a5-96dc-0797e4ca1041",
"type": "submissionsLog"
},
"meta": {
}
}
更多详细的接口信息参考官方文档
总结
以上介绍的三种公证方式,可以根据平时处理的项目的需求,选取一种最合适高效的公证方式,通过公证的应用不用走App Store 的上架流程,能更快的速度提供给用户主动跟新。公证的App 还允许不用沙盒化,不管怎么样,作为一个macOS app的开发者,公证都是一个必备了解的技能。
参考:
- 苹果官方文档
- 公证常见问题
- scripingox.com