macOS 应用公证 - 让用户信赖你的应用

Mac系统
823
0
0
2022-12-08

导语

macOS 下 AppStore 不是唯一能下载 App 的渠道,做为应用的开发者,我们也能把应用发布在网站上提供给用户下载安装。那么,我们如何让用户信任我们开发的软件呢?对此,苹果提供了公证的服务和结合操作系统的Gatekeeper,给用户提供了一层信心的保障。本文将介绍三种不同公证方式的选择。

公证

公证其实本质上是把(App、安装包)上传到苹果的公证服务进行公证,然后在安装的过程中Gatekeeper会去请求服务器,根据返回的数据判断App是否公证检验通过。或者在离线状态下读出可执行文件中内嵌的ticket判断。

为什么要对应用进行「公证」

从 macOS 10.15 之后,苹果系统要求App和工具需要进行工具才能正常的安装,不然会报“未知开发者应用,移除到废纸”,2020 年 1 月之后的公证也变得更加严格。

如果是一个没有经过公证的就会看到下面的提示:

img

如果是一个有经过公证的应用,就是这样的提示:

img

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

img

这种方式的官方有比较完善的文档,在本文章就不过多的展开了。

以工具notarytool方式下的公证

当直接使用 Xcode 的标准公证不能满足需求的时候,我们就得通过命令行工具来进行公证,比如这些情况:

  1. 公证已经发布了的 App 。
  2. 第三方软件的插件开发的公证。
  3. 对 Xcode 自定义编译的 targets,不是macOS app类型的这种情况下的公证。
  4. 发布 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 直接使用。

具体操作是这样的,输入一下命令:

xcrun notarytool store-credentials --apple-id "yourAppleID" --team-id "yourTeamID"

通过这个命令进行交互式的操作,在命令行中你将需要输入profile nameapp-specific password ,成功后将会看到以下信息:

img

如果不确定 --team-id 的值,可以使用命令xcrun altool --list-providers -u "yourAppleID" -p "app-specific-password" 查询。

编译注意事项

项目用开发者证书进行编译

img

开启 Enable Hardened Runtime

img

Info.plist 文件

-  关联 Info.plist 并且在 二进制的文件中创建 Info.plist 的 Section 段

timpstamp

往二进制文件中打入 timpstamp 字段。需要通过 Achive 才会有 timpstamp 字段

可以通过命令 codesign -ddv binary-file-path 检查

img

pkgbuild 进行打包

man pkgbuild

命令行示例:

% pkgbuild --root "your-binary-file-directory" \
		   --identifier "your-identify" \
           --version "1.0" \
           --install-location "/" \
           --sign "Developer ID Installer: Name (yourTeamID)" \
           app.pkg
打包后检查安装包的文件是否符合预期,双击打开pkg 安装包,在菜单栏文件 -> 显示文件中查看。

提交公证

通过上面的步骤操作下来,我们已经可以开始把pkg 文件提交公证了,操作命令为:

% xcrun notarytool submit app.pkg \ 
                   --keychain-profile "your-specified-profile-name" \ 
                   --wait

添加票据

发布前,还需要将票据添加到安装包中,这样才可以在没有网络下安装时能被Gatekeeper验证通过。

操作命令如下:

xcrun stapler staple app.pkg

验证是否添加成功的命令:

xcrun stapler validate app.pkg

最后做一个整体的验证:

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

POST https://appstoreconnect.apple.com/notary/v2/submissions

Request Body

body = { 
	"submissionName": "app.pkg", 
	"sha256": sha256, 
	"notifications": [{"channel": "webhook", "target": "https://example.com" }]
}

Response Body

{ "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 接口

GET https://appstoreconnect.apple.com/notary/v2/submissions/{submissionId}

submissionId (Required)

这里填入的值是在请求公证的接口返回的id。

Response

{ 
  "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

GET https://appstoreconnect.apple.com/notary/v2/submissions/{submissionId}/logs

submissionId (Required)

这里填入的值是在请求公证的接口返回的id。

{ 
  "data": { 
    "attributes": { 
      "developerLogUrl": "https://..." 
    }, 
    "id": "2efe2717-52ef-43a5-96dc-0797e4ca1041", 
    "type": "submissionsLog" 
  }, 
  "meta": { 
  }
} 

更多详细的接口信息参考官方文档

总结

以上介绍的三种公证方式,可以根据平时处理的项目的需求,选取一种最合适高效的公证方式,通过公证的应用不用走App Store 的上架流程,能更快的速度提供给用户主动跟新。公证的App 还允许不用沙盒化,不管怎么样,作为一个macOS app的开发者,公证都是一个必备了解的技能。

参考: