大多数时候代码签名看上去就像是一个难以理解的神秘黑盒。最近在做iOS自动化打包,有许多相似但又不同的APP,一个个打包耗时挺长,浪费人力,如何“快速自动化生成”这样的需求也就应运而生了。

此篇是iOS自动化打包的第一篇,主要介绍一些重新签名需要了解的一些理论知识。

  • 证书:内容是公钥或私钥,由其他机构对其签名组成的数据包;
  • Entitlements:包含了 App 权限开关列表;
  • CertificateSigningRequest:本地公钥;
  • p12:本地私钥,可以导入到其他电脑;
  • Provisioning Profile:包含了 证书 / Entitlements 等数据,并由苹果后台私钥签名的数据包;

一、证书和密匙

iOS 开发者使用的 Mac 应该已经有一个证书,一个公钥,以及一个私钥。这些是代码签名机制的核心。

在 OS X 上,证书都是由一个叫钥匙串访问的工具来进行管理。打开 Mac 的钥匙串访问应用,选择类别选项下的“我的证书(My Certificates)”,你可以看到所有你持有的私钥相对应的证书。

私钥是你在为组成应用的二进制文件进行签名时派上用场的。没有私钥,你就无法用证书和公钥对任何东西设置签名。如果你拥有一个证书的私钥,你可以展开证书并将它的私钥显示出来:

一个证书是一个公钥加上许多附加信息,这些附加信息都是被某个认证机构(Certificate Authority 简称 CA)进行签名认证过的,认证这个证书中的信息是准确无误的。对于 iOS 开发来说这个认证机构就是苹果的认证部门 Apple Worldwide Developer Relations CA。

二、授权机制

授权机制决定了哪些系统资源在什么情况下允许被一个应用使用。简单的说它就是一个沙盒的配置列表,上面列出了哪些行为被允许,哪些会被拒绝。

Xcode 会将这个文件作为 - -entitlements 参数的内容传给 codesign ,这个文件内部格式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?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>application-identifier</key>
<string>Z48KTT3VSL.com.example.app</string>
<key>aps-environment</key>
<string>production</string>
<key>com.apple.developer.team-identifier</key>
<string>Z48KTT3VSL</string>
<key>get-task-allow</key>
<false/>
</dict>
</plist>

查看授权信息:

1
$ codesign -d --entitlements - Example.app

在 Xcode 的 Capabilities 选项卡下选择一些选项之后,会自动生成一个XML格式 .entitlements 文件,然后在需要的时候往里面添加条目。当构建整个应用时,这个文件也会提交给 codesign 作为应用所需要拥有哪些授权的参考。这些授权信息必须都在开发者中心的 App ID 中启用,并且包含在配置文件中。在构建应用时需要使用的授权文件可以在 Xcode build setting 中的 code signing entitlements 中设置。

三、配置文件

在整个代码签名和沙盒机制中有一个组成部分将签名,授权和沙盒联系了起来,那就是配置文件 (provisioning profiles)。

一个配置文件是一组信息的集合,这组信息决定了某一个应用是否能够在某一个特定的设备上运行。配置文件可以用于让应用在你的开发设备上可以被运行和调试,也可以用于内部测试 (ad-hoc) 或者企业级应用的发布。同一个证书可以拥有多个不同的配置文件,Xcode 会将你在 project setting 中选择的配置文件打包进应用。

配置文件存放路径:

1
~/Library/MobileDevice/Provisioning Profiles

配置文件是一个根据密码讯息语法 (Cryptographic Message Syntax) 加密的文件(下文中会简称 CMS)。采用 CMS 格式进行加密使得配置文件可以被设置签名,所以在苹果给你这个文件之后文件就不能被改变了。配置文件的签名和应用的签名不是一回事,它是由苹果直接在开发者中心 (developer portal) 中设置好了的。

幸运的是命令行工具 security 也可以解码这个 CMS 格式:

1
$ security cms -D -i example.mobileprovision

这个命令会输出签名信息中的内容,一个 XML 格式的 plist 文件。列表中的内容是 iOS 用于判断你的应用是否能运行在某个设备上真正需要的配置信息,每一个配置文件都有它自己的 UUID 。Xcode 会用这个 UUID 来作为标识,记录你在 build settings 中选择了哪一个配置文件。

文件内容都是以键值对(Key-Value)的形式展示的:

  • DeveloperCertificates:这一项是一个列表,包含了可以为使用这个配置文件的应用签名的所有证书。如果你用了一个不在这个列表中的证书进行签名,无论这个证书是否有效,这个应用就无法运行;
  • Entitlements:对应的 Value 是一个字典,包含了应用的所有授权信息,键值就和之前在授权那节看到的一模一样(正常情况下一模一样)。

四、重新签名

我们知道苹果在打包的最后步骤就是进行签名,如果对于一个已经签名的包,我们还可以进行重新签名,签名的命令主要是 codesign

设置签名:

1
$ codesign -s 'iPhone Distribution: Hangzhou ChuangJiang Technology Co., Ltd. (P7KXERWG7Q)' Example.app

如果你想为某一个 app 程序包重新设置签名,你必须带上 -f 参数,有了这个参数,codesign 会用你选择的签名替换掉已经存在的那一个。

重新签名:

1
$ codesign -f -s 'iPhone Distribution: Hangzhou ChuangJiang Technology Co., Ltd. (P7KXERWG7Q)' Example.app

列出一些有关 Example.app的签名信息:

1
$ codesign -vv -d Example.app

签名信息:

1
2
3
4
5
6
7
8
9
10
11
12
Identifier=com.chuangjiang.app
Format=app bundle with Mach-O universal (armv7 arm64)
CodeDirectory v=20200 size=33048 flags=0x0(none) hashes=1025+5 location=embedded
Signature size=4763
Authority=iPhone Distribution: Hangzhou ChuangJiang Technology Co., Ltd. (P7KXERWG7Q)
Authority=Apple Worldwide Developer Relations Certification Authority
Authority=Apple Root CA
Signed Time=2017113日 上午11:45:17
Info.plist entries=39
TeamIdentifier=P7KXERWG7Q
Sealed Resources version=2 rules=13 files=205
Internal requirements count=1 size=216

主要关注以 Authority 开头的那三行。这三行告诉你到底是哪一个证书(iPhone Distribution: Hangzhou ChuangJiang Technology Co., Ltd. (P7KXERWG7Q))为这个 app 设置了签名。这个证书则是被 Apple Worldwide Developer Relations Certification Authority 设置了签名的,依此类推这个证书则是被证书 Apple Root CA 设置了签名。

Identifier 是在 Xcode 中设置的 Bundle identifier。TeamIdentifier 用于标识工作组(系统会用这个来判断应用是否是由同一个开发者发布)。

验证签名是否完好,若无任何输出则说明签名完好

1
$ codesign --verify Example.app

在 iOS 和 OS X 的应用和框架中,可执行文件和包含其中的资源都需要设置签名。这些资源包括图片和不同的语言文件,也包括很重要的应用组成部分例如 XIB/NIB 文件,存档文件(archives),甚至是证书文件。所以为一个程序包设置签名时,这个包中的所有资源文件也都会被设置签名。

为了达到为所有文件设置签名的目的,签名的过程中会在程序包中新建一个叫做 _CodeSignatue/CodeResources 的文件,这个文件中存储了被签名的程序包中所有文件的签名。