iOS代码签名官方文档翻译

1,372 阅读8分钟

原文地址

关于代码签名

代码签名是一种macOS安全技术,你用来证明一个app是由你创建。一个应用一旦被签名,系统可以检测这个应用的任何改变,不管这个改变是因意外或者恶意代码引起。

注意:在多数情况下,你可以依赖XCode的自动代码签名,这只需要在编译设置中为你的项目指定一个代码签名标识。这个文档适用于超越自动代码签名的读者--也许是为了解决不寻常的问题,或者是为了集成签名工具到编译系统。

代码签名的好处

在安装了一个签名过的新版本的应用之后,用户不再会被允许访问钥匙串和与之类似的提示所困扰。只要新版本用的是同样的数字签名,macOS会将这个新版本看作和之前版本完全一样。

其它MacOS安全特性,例如应用沙盒和家长控制,也依赖于代码签名。具体来说,代码签名允许操作系统:

  • 确保一段代码自被签名之后,未被修改过。系统甚至可以检测到最小的改动,不管它是有意的(例如来自恶意的攻击者)还是偶然的(一个文件被损坏)。当代码签名完好无损时,系统可以确保代码符合签名者的意图。
  • 识别来自指定的来源(开发者或者签名者)的代码。代码签名包含明确指向特定作者的加密信息。
  • 决定代码是否值得信任于某一特定目的。此外,开发者可以用代码签名声明更新版本的应用应该被系统看作与之前的版本无异。

代码签名的局限性

代码签名是完整的安全解决方案的一个组件,和其它技术协同工作。它不能解决所有的安全问题。例如,代码签名不能:

  • 保证代码没有漏洞。
  • 保证应用不会加载不安全的或者被改动过的代码——例如执行过程中未信任的插件。
  • 提供数字版权管理(DRM)或复制保护技术。代码签名不会以任何方式隐藏或者模糊被签名代码内容。

请参阅

阅读安全概览以理解代码签名在macOS安全图里的位置

有关执行代码签名的命令行工具的说明,请参阅codesign和csreq

理解代码签名

各种各样的代码都能被签名,包括工具,应用,脚本,库,插件和其它类似代码的数据。另外,你可以创建签名安装包,和签名磁盘镜像。在大多数情况下,代码签名由三个部分组成:

  • 密封。这是代码各部分的校验或哈希值的集合,有代码签名软件创建。密封可以用于在验证阶段检测改动。
  • 电子签名。代码签名软件通过使用签名者的身份加密密封以创建数字签名。这保证了密封的完整性。
  • 代码要求。这些是管理代码签名验证的规则。有些是验证者固有的(取决于其目标)。其他由签名者指定并用其余代码密封。

密封

代码签名机器通过运行你的最终包(应用,库,或者框架)中不同的部分以创建密封,包括可执行文件,资源文件,info.plist文件,代码要求,等等,通过一单向的哈希算法。这产生一系列的数字,或者校验,这些是数字组成的短字符串,标记了输入块的唯一性,但是不能被用于恢复原来的输入。

一个校验实体包括正在校验的代码和对应的哈希值的集合,对校验代码执行和签名者相同的哈希算法,并且和原来的以保存好的哈希值比较,以查看是否有任何改变。即使代码中的少量修改也会导致不同的哈希值,这表明存在篡改或损坏。但是,此验证的可信度依赖于存储的哈希的可靠性。而数字签名保证了这一点。

数字签名

安全概述中所述,数字签名使用公钥加密来确保数据完整性。就像在纸上用墨水书写的签名一样,可以使用数字签名来识别和验证签名者。然而,数字签名更难以伪造,进一步说:它可以确保签名数据没有被更改。这有点像设计纸质支票或汇票,如果某人改变了书面金额,则在纸上可以看到带有“无效”字样的水印。

在代码签名的上下文中,签名软件通过用签名者的私钥加密密封的哈希来创建数字签名。因为只有签名者拥有私钥,所以只有签名者才能执行此加密。正是这个加密的哈希集合,签名者存储在应用程序(或框架,存档或其他签名对象)中,以及匹配的证书,它们共同代表数字签名。

为了验证签名,验证软件在各种代码和数据块上计算相同的哈希集。然后,它使用嵌入在证书中的签名者的公钥来解密代码附带的加密哈希,从而获得由签名者计算的原始哈希。如果两个哈希匹配,则数据未被修改,因为它是由拥有签名者私钥的人签名的。

虽然不严格要求代码签名过程严格进行,但证书本身通常由受信任的证书颁发机构签名。如果没有,验证者可以确定证书从一个版本到另一个版本的稳定性,而不是其来源的稳定性。如果是,证书颁发机构(通常是Apple)将担保签名者的身份。

签名代码可能包含多个不同的数字签名:

  • 如果代码是通用的,则每种架构的目标代码将单独签名。此签名存储在二进制文件本身中。
  • 应用程序包的各种数据组件(例如Info.plist文件,如果有的话)也会被签名。这些签名存储在捆绑包中名为_CodeSignature / CodeResources的文件中。
  • 嵌套代码(例如库,辅助工具和嵌入在应用程序中的其他代码)本身已经过签名,其签名也存储在捆绑包中的_CodeSignature / CodeResources中。

签名要求

代码要求是macOS用于评估代码签名的规则。执行评估的系统决定在评估时应用哪些代码要求,具体取决于其目标。例如,Gatekeeper有一条规则,即在允许第一次启动应用程序之前,它必须由Mac App Store或开发者ID证书签名。另一个例子是,应用程序可以强制执行代码要求,该应用程序使用的所有插件都应该由Apple签名。

签名者指定并作为代码签名的一部分包含的代码要求称为内部要求。这些可用于验证代码签名的系统,但系统可以选择是否使用它们。上一个示例中的应用程序插件可能有自己的内部要求,但是由评估系统(使用插件的应用程序)决定是否应用它们。由于密封涵盖了规范要求,只要签字有效,内部要求也必定是完整的。

最重要的内部要求是指定要求或DR。此规则告诉评估系统如何识别特定的代码段。具有(并成功验证)相同DR的任何两段代码被认为是相同的代码。这允许代码签名者发布被视为相同应用程序的新版本的应用程序。例如,Apple Mail的DR可能是“由Apple签署并具有标识符com.apple.Mail”。发布新版本的应用程序时,只要新版本的应用程序具有相同的DR,它仍然被视为Apple Mail,即使二进制可执行文件完全不同。此外,只有Apple可以签署Apple,因此没有其他人可以制作伪装成Mail应用程序的应用程序。

程序标识符或整个指定的要求可由签名者明确指定。通常,签名机制使用在其Info.plist文件中找到的程序的名称作为CFBundleIdentifier以及保护代码签名的签名链自动构建指定的需求。

实际上,代码要求是以专用语言编写的脚本,描述了代码必须满足的条件(限制)才能被接受用于某些目的。有关代码要求脚本语言的详细说明,请参阅代码签名要求语言。

代码签名评估

当macOS子系统需要确定为某种目的信任该代码是否安全时,它会根据一组需求对签名代码进行验证。如数字签名中所述,内部要求,尤其是指定要求,来自代码签名本身。其他要求是进行验证的系统所固有的。

表2-1给出了macOS中不同子系统如何使用代码签名来强制执行特定于某种系统资源的信任策略的具体示例。请注意,这些是默认行为;您可以使用spctl(8)命令修改macOS的许多代码签名策略。

表2-1验证代码有效性的macOS子系统示例