背景

最近接到需要编写一个Unity版本的SDK供游戏工程师使用,并上架应用到Mac App Store。之前从未有过Unity开发和Mac App上架的经验,所以写文章记录自己整个开发过程,以备需要时查看。

我们都知道在Mac OS上安装的应用,有两个下载渠道:

  • 官方渠道:Mac App Store
  • 非官方渠道:公司官网等非Mac App Store来源

我们本身也有在iOS App Store上架的应用,根据苹果官方文档的描述,我们可以直接将iOS 应用平滑迁移到Mac上,仅仅只需要在【App Store Connect – App – 价格与销售范围】上勾选 “提供此App 至 mac os”上,见下图:

通过Mac App Store分发应用之签名过程

但我们希望能够获得Mac App Store的官方流量和推荐,所以选择为当前应用增加mac os平台,发布完全适配Mac平台的新应用。于是,就有了Unity接入Mac内购和Mac签名的开发过程。

这篇文章主要讲述了我帮助游戏客户端进行Mac App的代码和安装包签名,以及对苹果相关官方文档的学习,希望可以帮助到大家。

1、签名过程

注意:如果使用Xcode直接上传到Mac App Store则不用签名和公证。

1.1、使用unity编译后修改info.plist文件

1)App Uses Non-Exempt Encryption :设置为 NO,这样在Apple Connect Strore – App – 构建版本处,就不需要每次都确认“出口合规证明”;

  <key>ITSAppUsesNonExemptEncryption</key>
  <false/>

2)检查 Bundle OS Type code 的值:其value值是一个四字母的代码,用于指示包类型。对于应用程序,代码是APPL;对于框架,是FMWK;对于捆绑包,则是BNDL。默认值是从捆绑扩展名中推导出来的,如果无法推导,则默认值是BNDL。

  <key>CFBundlePackageType</key>
  <string>APPL</string>

3)App Category(LSApplicationCategoryType) :设置应用程序的类型

  <key>LSApplicationCategoryType</key>
  <string>public.app-category.games</string>

4)LSMinimumSystemVersion:设置App在 macOS 中运行所需的最低操作系统版本。

  <key>LSMinimumSystemVersion</key>
  <string>13.0.0</string>

1.2、创建并修改.entitlements文件

使用Xcode创建一个和你项目同名的Mac工程,不需要配置该项目的描述文件和证书,仅仅只需要添加App Sandbox Capability。

我们项目是一个游戏项目,所以按照项目功能,勾选了网络和文件读写。

通过Mac App Store分发应用之签名过程
打开生成的App.entitlements,添加TeamID和AppID

  <key>com.apple.developer.team-identifier</key>
  <string>TeamID</string>
  <key>com.apple.application-identifier</key>
  <string>AppID</string>

补充:上传到Mac App Store应用都必须开启沙盒功能,验证签名后的app是否开启沙盒功能,苹果提供了两种方式

1)活动监视器查看

通过Mac App Store分发应用之签名过程

2)命令行查看

% codesign -dvvv --entitlements - <path to your app>

通过Mac App Store分发应用之签名过程

1.3、在.app/Contents路径下添加发布环境的.provisionprofile文件

注意: 必须.app/Contents路径下,文件名也必须是embedded.provisionprofile

从Mac App Store 或 TestFlight下载后的应用,.app/Contents就不会显示embedded.provisionprofile文件了。

通过Mac App Store分发应用之签名过程

1.4、签名指令

# 1. 对~/XXX.app/Contents/PlugIns路径下的bundle进行代码签名,注意不可以使用--entitlements选项,注意这里使用的是:3rd Party Mac Developer Application证书
# codesign -f -s <CertificationName> <BundlePath>
$ codesign -f -s '3rd Party Mac Developer Application: AAA' "XXX.app/Contents/PlugIns/unitypurchasing.bundle"
# 终端会打印:XXX.app/Contents/PlugIns/unitypurchasing.bundle: replacing existing signature# 2. 对~/XXX.app/Contents/Frameworks路径下的库文件进行代码签名,注意不可以使用--entitlements选项,注意这里使用的是:3rd Party Mac Developer Application证书
# codesign -f -s <CertificationName> <FrameworkPath>
$ codesign -f -s '3rd Party Mac Developer Application: AAA' "XXX.app/Contents/Frameworks/libmonobdwgc-2.0.dylib"
# 终端会打印:XXX.app/Contents/Frameworks/libmonobdwgc-2.0.dylib: replacing existing signature# 3. 对.app进行代码签名,注意这里使用的是:3rd Party Mac Developer Application证书
# codesign -f -s <CertificationName> --entitlements <EntitlementsPath> <AppPath>
$ codesign -f -s '3rd Party Mac Developer Application: AAA' --entitlements "/Users/XXX/Desktop/App.entitlements" "/Users/XXX/Desktop/XXX.app"
# 终端会打印:XXX.app: replacing existing signature# 4. 生成安装包并进行安装包签名,注意这里使用的是:3rd Party Mac Developer Installer证书
# productbuild --component <AppPath> /Applications --sign <CertificationName> <PkgPath>
$ productbuild --component /Users/XXX/Desktop/XXX.app /Applications --sign "3rd Party Mac Developer Installer: AAA" /Users/XXX/Desktop/XXX.pkg
​
# 终端会打印:
productbuild: Adding component at /Users/XXX/Desktop/XXX.app
productbuild: Signing product with identity "3rd Party Mac Developer Installer: AAA" from keychain /Users/XXX/Library/Keychains/login.keychain-db
productbuild: Adding certificate "Apple Worldwide Developer Relations Certification Authority"
productbuild: Adding certificate "Apple Root CA"
productbuild: Wrote product to /Users/XXX/Desktop/XXX.pkg
productbuild: Supported OS versions: [Min: 13.0.0, Before: None]

补充: 是在 “钥匙串访问“ 中复制粘贴的证书名称

通过Mac App Store分发应用之签名过程

2、 签名过程遇到的问题

1、Asset validation failed (90869) Invalid bundle. The “NTSDK.app” bundle supports arm64 but not Intel-based Mac computers. Your build must include the x86_64 architecture to support Intel-based Mac computers. To support arm64 only, your macOS deployment target must be 12.0 or higher. For details, view: developer.apple.com/documentati…. (ID: 3d0f10fb-1a41-4d45-8b40-d1227cdd3f4b)

原因: Apple要求如果应用只支持Apple Silicn,macOS deployment target必须 >=12.0,而我们Unity Build的Mac app设置是10.13.0。

解决: 修改Info.plist文件中, LSMinimumSystemVersion 对应的value值,按照需求设置,只要大于12.0就行。

    <key>LSMinimumSystemVersion</key>
    <string>13.0.0</string>

2、Asset validation failed (90236) Missing required icon. The application bundle does not contain an icon in ICNS format, containing both a 512×512 and a 512×512@2x image. For further assistance, see the Human Interface Guidelines at developer.apple.com/design/huma…. (ID: e0440ff7-4601-41cc-aeb4-7ea1e32560d2)

原因: 缺失应用图标

解决: 按照要求添加即可,在这里推荐一款 “免费生成iPhone、iPad、Mac OS平台应用Icon的Mac App:AppIcon Maker(xcAsset Creator)

3、ITMS-90889: Cannot be used with TestFlight because the bundle at ‘MyApp.app’ is missing a provisioning profile. Main bundles are expected to have provisioning profiles in order to be eligible for TestFlight.

原因: .app/Contents路径下缺失发布环境的.provisionprofile文件。

解决:

1)在.entitlements文件中添加TeamID和AppID

通过Mac App Store分发应用之签名过程
2)在XXX.app/Contents/ 路径下添加 embedded.provisionprofile 文件

下面是我对苹果官方文档的学习和理解:

3、了解Mac 签名

3.1 公证Mac App

1)通过Mac App Store 分发的 Mac App,无须公证,因为在提交过程App Store已经对其进行了相同的安全检查。

通过Mac App Store分发应用之签名过程

2)非Mac App Store 分发的 Mac App,需要公证,否则会提示用户“应用可能会损害电脑”等,

通过Mac App Store分发应用之签名过程

3)问答:Mac 公证服务团队答疑

3.2 代码签名

  1. 所有的Mac App,都必须进行代码签名。

    • 如果使用Unity 编译导出.app文件,则需要手动为代码签名,也就是为.app进行签名
  2. 确定可执行文件

    根据file 指令,查看对应的文件是否为main executable,A main executable says Mach-O … executable.

    $ file /Applications/XXX.app/Contents/PlugIns/AkSoundEngine.bundle/Contents/MacOS/AkSoundEngine
    /Applications/XXX.app/Contents/PlugIns/AkSoundEngine.bundle/Contents/MacOS/AkSoundEngine: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit bundle x86_64] [arm64]
    /Applications/XXX.app/Contents/PlugIns/AkSoundEngine.bundle/Contents/MacOS/AkSoundEngine (for architecture x86_64): Mach-O 64-bit bundle x86_64
    /Applications/DynastyLegends2.app/Contents/PlugIns/AkSoundEngine.bundle/Contents/MacOS/AkSoundEngine (for architecture arm64):  Mach-O 64-bit bundle arm64
    ​
    # 根据打印,可知AkSoundEngine不是main executable
    
  3. 决定签名顺序

    需要从内向外签署代码,也就是如果组件 A 依赖于组件 B,则先签署 B,然后再签署 A。

    举例:DaemonWithApp 产品有以下内容: ConfigApp.app, Core.framework, Share.appex, and Daemon,签名顺序应为:Core.frameworkShare.appexConfigApp.app,daemon是独立的,签名顺序随意。

  4. 配置权限文件

    代码签名可以包含App权限文件 .entitlements ,当 macOS 运行一个进程时,entitlements文件会授予该进程可执行文件在代码签名所包含的权限。

  5. 嵌入distribution provisioning profile

    在entitlements声明的权限,一般必须都需要在distribution provisioning profile中进行授权。当然也有一些不需要在distribution provisioning profile授权,就可以在entitlements中应用的权限。

    注意:一定要将distribution provisioning profile文件放在 XXX.app/Contents/ 路径下,且修改文件名为embedded.provisionprofile

通过Mac App Store分发应用之签名过程

  1. 确定代码签名的正确性

    % codesign -d -vv <filePath>
    ​
    codesign -d -vv /Applications/NTSDK.app/Contents/PlugIns/unitypurchasing.bundle
    Executable=/Applications/NTSDK.app/Contents/PlugIns/unitypurchasing.bundle/Contents/MacOS/unitypurchasing
    Identifier=com.unity.purchasing.unitypurchasing
    Format=bundle with Mach-O universal (x86_64 arm64)
    CodeDirectory v=20500 size=1200 flags=0x10000(runtime) hashes=28+5 location=embedded
    Signature size=4844
    Authority=TestFlight Beta Distribution
    Authority=Apple Worldwide Developer Relations Certification Authority
    Authority=Apple Root CA
    Info.plist entries=26
    TeamIdentifier=7A34BX8C5J
    Runtime Version=11.0.0
    Sealed Resources version=2 rules=13 files=1
    Internal requirements count=1 size=116
    
  2. 为每一种代码类型签名

    % codesign -s <CodeSigningIdentity> <PathToExecutable>
    # 举例子:
    % codesign -f -s '3rd Party Mac Developer Application: HK Taihe Interactive Limited (7A34BX8C5J)' "/Users/taihe/Desktop/NTSDK.app/Contents/Frameworks/libmonobdwgc-2.0.dylib"
    /Users/taihe/Desktop/NTSDK.app/Contents/Frameworks/libmonobdwgc-2.0.dylib: replacing existing signature
    % codesign -f -s '3rd Party Mac Developer Application: HK Taihe Interactive Limited (7A34BX8C5J)' "/Users/taihe/Desktop/NTSDK.app/Contents/Frameworks/UnityPlayer.dylib"
    /Users/taihe/Desktop/NTSDK.app/Contents/Frameworks/UnityPlayer.dylib: replacing existing signature
    % codesign -f -s '3rd Party Mac Developer Application: HK Taihe Interactive Limited (7A34BX8C5J)' "/Users/taihe/Desktop/NTSDK.app/Contents/Frameworks/libMonoPosixHelper.dylib"
    % codesign -f -o runtime -s '3rd Party Mac Developer Application: HK Taihe Interactive Limited (7A34BX8C5J)' --entitlements "/Users/taihe/Desktop/App.entitlements" "/Users/taihe/Desktop/NTSDK.app"
    /Users/taihe/Desktop/NTSDK.app: replacing existing signature
    

    说明:

    1、不能对库代码进行包含权限文件(.entitlements)的代码签名,它会阻止可执行文件的程序运行,导致mac app打开就crash。

    2、不能使用sudo 运行 codesign,因为codesign在签署代码时依赖用户账户信息,使用sudo等工具会更改用户账户,从而造成一些意外情况.

    3、如果要签署包含权限的可执行文件,需要添加 --entitlements <entitlementsPath>,其中 <entitlementsPath> 是为该可执行文件创建的权限文件的路径。

    4、如果使用Developer ID 进行代码签名,需添加 --timestamp 选项,以包含安全时间戳。

    5、如果使用Developer ID 进行代码签名,需添加 -o runtime 选项,启用加固运行时。

  3. 避免使用--deep

    签署代码时,不要在 codesign 中使用--deep 选项。--deep会对其签署的每个代码项应用相同的entitlements。例如,如果你有一个内嵌命令行工具的应用程序,而应用程序和工具需要不同的权限,那么 codesign –deep 就会对两者应用相同的权限。

3.3 决定以什么样的格式分发Mac App

在Mac 上安装的应用支持四种格式:

  1. 从 Mac App Store上下载的直接就是.app

  2. 从非 Mac App Store上下载安装的可能是:

    以下3种格式可以嵌套

4、Entitlements

描述可执行文件的权限信息的文件,我一般叫它权限文件,内容以key-value形式展示。在对应用程序进行代码签名时,Xcode 会将权限文件、开发者账户中的信息以及其他项目信息结合起来,为应用程序应用一组最终的权限。

4、参考链接:

Packaging Mac software for distribution

Resolving Library Loading Problems

Creating Distribution-Signed Code for Mac

TestFlight, Provisioning Profiles, and the Mac App Store