最近学习了一下iOS代码签名机制 ,这里做个笔记记录,整理思路,加深理解。
想了解更多iOS签名相关,强烈推荐去看看原文,写的太棒了。原文出处 深度长文:细说iOS代码签名 。
背景
沙盒(Sandbox)技术是iOS安全体系中非常重要的一项技术,他的目的是通过各种技术手段限制App的行为,比如可读写的路径,允许访问的硬件,允许使用的服务等等,即使应用出现任意代码执行的漏洞,也无法影响到沙盒外的系统。(图来自Apple开发者网站)
Entitlements
通常所说的Entitlements(授权文件),也就是指iOS沙盒的配置文件,这个文件中声明了app所需的权限,如果app中使用到了某项沙盒限制的功能,但没有声明对应的权限,可能运行到相关的代码时会直接Crash。
授权机制也是按照 plist 文件格式来列出的。这个文件内部格式如下:
<?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>7TPNXN7G6K.ch.kollba.example</string>
<key>aps-environment</key>
<string>development</string>
<key>com.apple.developer.team-identifier</key>
<string>7TPNXN7G6K</string>
<key>com.apple.developer.ubiquity-container-identifiers</key>
<array>
<string>7TPNXN7G6K.ch.kollba.example</string>
</array>
<key>com.apple.developer.ubiquity-kvstore-identifier</key>
<string>7TPNXN7G6K.ch.kollba.example</string>
<key>com.apple.security.application-groups</key>
<array>
<string>group.ch.kollba.example</string>
</array>
<key>get-task-allow</key>
<true/>
</dict>
</plist>
在 Xcode 的 Capabilities 选项卡下选择一些选项之后,Xcode 就会生成这样一段 XML。 Xcode 会自动生成一个 .entitlements
文件,然后在需要的时候往里面添加条目。当构建整个应用时,Xcode 会将这个文件作为 --entitlements
参数的内容传给 codesign
,作为应用所需要拥有哪些授权的参考。这些授权信息必须都在开发者中心的 App ID 中启用,并且包含在配置文件中,稍后我们会详细讨论这一点。在构建应用时需要使用的授权文件可以在 Xcode build setting
中的 code signing entitlements
中设置。
在这个应用中我启用了 iCloud 键值对存储 (key-value storage) (com.apple.developer.ubiquity-kvstore-identifier
) ,以及 iCloud 文档存储 (com.apple.developer.ubiquity-container-identifiers
)。另外我还将应用添加进了一个 App Group (比如说为了与扩展 (extensions) 共享数据,com.apple.security.application-groups
), 最后开启了推送功能 (aps-environment
)。这是一个开发版本,我会有将它连接到调试器的需求,这就需要将 get-task-allow
设为 true
。另外,app id,也就是 bundle id 加上开发者 id,也被单独列出来了。
当然你并不能随心所欲的取得授权,你的应用能否得到某一项授权是有特定的规定的。举例来说,当 get-task-allow
被设定为 ture
时,应用只能在用于开发的证书签名下运行。你被允许使用的推送环境 (aps-environment) 也存在类似的限制。
实际上,这个文件的内容并非是全部的授权内容,因为缺省状态下,App默认会包含以下与Team ID及App ID相关的权限声明:
<dict>
<key>get-task-allow</key>
<true/>
<key>application-identifier</key>
<string>xxx.xxx.bundleID</string>
<key>com.apple.developer.team-identifier</key>
<string>xxxxxxxxxx</string>
</dict>
其中get-task-allow
代表是否允许被调试,它在开发阶段是必需的一项权限,而在进行Archive打包用于上架时会被去除。
进行代码签名时,会将这个Entitlements文件(如有)与上述缺省内容进行合并,得到最终的授权文件,并嵌入二进制代码中,作为被签名内容的一部分,由代码签名保证其不可篡改性。
可以尝试查看签名信息中具体包含了什么授权信息:$ codesign -d --entitlements - Example.app
$ codesign -d --entitlements - CodeSignTest.app
Executable=/Users/bytedance/Downloads/iOS签名/sign/CodeSignTest.app/CodeSignTest
[Dict]
[Key] application-identifier
[Value]
[String] JJHW9Jxxxx.com.xxxxxxx.xxxx
[Key] com.apple.developer.applesignin
[Value]
[Array]
[String] Default
[Key] com.apple.developer.team-identifier
[Value]
[String] JJHW9Jxxxx
[Key] get-task-allow
[Value]
[Bool] true
Provisioning Profile
这里引用一下苹果的原文解释
A provisioning profile is a collection of digital entities that uniquely ties developers and devices to an authorized iPhone Development Team and enables a device to be used for testing.
Provisioning Profile在这里就起到了一个对设备和开发者授权的作用,他将开发者账号、证书、entitlements文件以及设备进行了绑定。
在开发过程中,使用的 Provisioning Profile 都被存放在 ~/Library/MobileDevice/Provisioning\ Profiles/
路径下,以 UUID
格式命名。
由于这个文件是被苹果签过名的,所以我们没有办法伪造或者修改这个文件,它使用的是标准的CMS(Cryptographic Message Syntax)格式,可以通过 security
命令查看它的签名信息:
$ security cms -D -i xxxxxxxxxxx.mobileprovision -h 1 -n # 查看签名信息
SMIME: level=1.2; type=signedData; nsigners=1;
signer0.id="Apple iPhone OS Provisioning Profile Signing"; signer0.status=GoodSignature;
level=1.1; type=data;
Provisioning Profile 统一都是由 Apple iPhone OS Provisioning Profile Signing
进行签名的,机构名称言简意赅。
通过 security
命令将文件的内容提取出来:
$ security cms -D -i ea8585cd-c2da-4b08-81c2-e32b28c34871.mobileprovision -o provision.plist # 将内容导出
<?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>AppIDName</key>
<string>xxxxx</string>
...
<key>DeveloperCertificates</key>
<array>
<data>xxxxx</data>
<data>xxxxx</data>
<data>xxxxx</data>
</array>
<key>Entitlements</key>
<dict>
<key>get-task-allow</key>
<true/>
<key>application-identifier</key>
<string>xxxxx.xxx.bundleID</string>
<key>com.apple.developer.team-identifier</key>
<string>xxxxx</string>
<key>com.apple.developer.siri</key>
<true/>
</dict>
<key>ExpirationDate</key>
<date>2022-01-22T05:14:57Z</date>
<key>Name</key>
<string>iOS Team Provisioning Profile: xxxx</string>
<key>ProvisionedDevices</key>
<array>
<string>xxxxx</string>
<string>xxxxx</string>
<string>xxxxx</string>
</array>
...
</dict>
</plist>
明显可以看出这是一个xml格式的plist文件,里面的内容不难理解,最关键的是这几项
- DeveloperCertificates:允许使用的开发者证书,这是一个列表,一般包含生成这个Provisioning Profile文件时,当前开发者账号下所有有效的Development证书,以base64格式保存。
-
Entitlements:允许使用的权限列表,这些授权信息是你在开发者中心下载配置文件时在 App ID 中设置的。实际在App中使用的权限必须是这个列表的子集,否则安装时会无法通过校验而失败。如果曾经开启过某个功能,Xcode自动更新了Provisioning Profile,后来又关闭它,Xcode并不会将其从Provisioning Profile中删去,如示例中的
com.apple.developer.siri
。 -
ProvisionedDevices:允许安装的设备列表,如果目标设备的UUID不在这个列表中,会安装失败。对于这一项,普通开发者证书和企业级开发者证书的待遇是不同的。普通开发者证书使用Provisioning Profile的方式安装App到设备,只是出于测试和调试的需要,因此Apple只允许最多注册100台用于测试的设备,否则开发者就可以以测试的名义任意任意分发自己的App了。而对于企业级开发者来说,本身就有任意安装的需求,因此在分发时,这一项会被
ProvisionsAllDevices
取代,代表授权任意设备。
这些信息中有任何变动的时候,比如开发者证书有新增或者失效,在Capabilities中启用了当前App从未使用过的新功能,或是将新的iPhone连接到Xcode用于测试,Xcode都会自动重新申请Provisioning Profile。
Provisioning Profile会被内置在App中,置于App根目录下的 embedded.mobileprovision
。安装App时如果签名校验通过,这个文件会自动被拷贝到iOS设备的 /Library/MobileDevice/Provisioning\ Profiles/
路径下。由于该文件已被Apple官方签名,系统可以无条件信任它,并用它来校验App的签名、权限,以及本机的UUID等是否满足来自官方的授权。
通过这种方式,设备间接信任开发者的身份,让iOS设备可以运行非苹果官方签名的App。
假如你有一台越狱的设备,查看任意一个从AppStore上下载下来的App,里面都不会有embedded.mobileprovision这个文件,因为经过Apple重新签名以后,设备就不再需要它来信任开发者了。