1.3 包的安全性

原文链接: https://docs.swift.org/latest/documentation/packagemanagerdocs/packagesecurity/

1.3 包的安全性

了解包管理器实现的安全特性。

首次使用即信任

包管理器会记录已下载包版本的指纹,以便实施首次使用即信任(TOFU):也就是说,某个包版本第一次被下载时,包管理器信任它下载到的内容是正确的,并要求之后下载同一个包版本时必须具有相同的指纹。如果指纹发生变化,可能意味着该包已被篡改,包管理器会给出警告或返回错误。

根据包版本的下载来源不同,用作指纹的值也不同:

包版本来源指纹
Git 仓库该修订版的 Git 哈希
包注册表源码归档的校验和

包管理器把每个包的版本指纹集中保存在 ~/.swiftpm/security/fingerprints 目录下的单个文件中。

  • 对于来自 Git 仓库的包,指纹文件名形如 {PACKAGE_NAME}-{REPOSITORY_URL_HASH}.json(例如 LinkedList-5ddbcf15.json)。
  • 对于来自注册表的包,指纹文件名形如 {PACKAGE_ID}.json(例如 mona.LinkedList.json)。

对于从注册表获取的包,包管理器期望所有注册表为它们托管的包提供一致的指纹。如果归档是第一次下载,包管理器会获取包版本的元数据以获得预期的校验和;否则,包管理器会把校验和与本地存储(~/.swiftpm/security/fingerprints/)中此前下载时保存的值比较。

如果注册表之间的指纹相互冲突,包管理器会报错。你可以把构建选项 --resolver-fingerprint-checking 设为 warn,把该错误降级为警告(默认值为 strict)。

包签名

来自注册表的已签名包

注册表可以支持或要求签名。要给包版本签名,包作者需要设置 swift package-registry publish 子命令的 signing-identity 选项(从操作系统的身份存储读取,例如 macOS 的钥匙串),或者 private-key-path 与 cert-chain-paths 选项(从文件读取),这样包管理器才能找到签名密钥和证书。

如果证书链的根证书和中间证书是包管理器已知的,包作者只需在 cert-chain-paths 中提供叶子签名证书。

否则,包作者应当把整条证书链作为 cert-chain-paths 提供,使所有证书都包含在签名中,以便包管理器之后能够重建证书链进行验证。这一点对 signing-identity 同样适用:也就是说,你可以把 signing-identity 与 cert-chain-paths 组合使用,提供整条证书链。

如果签名证书的根证书不在包管理器的默认信任存储中,包作者有责任告知包使用者把根证书加入本地的信任根目录,否则下载时签名验证可能因为签名证书不受信任而失败。

关于已签名注册表包的更多内容,参见发布到注册表。

验证已签名的包

包管理器通过检查 HTTP 响应中是否存在 X-Swift-Package-Signature-Format 和 X-Swift-Package-Signature 头,判断下载到的归档是否已签名。

随后它会根据用户的安全配置执行一系列验证:

  • 如果归档未签名,包管理器根据 signing.onUnsigned 配置报错、提示、警告或放行。
  • 如果归档已签名,包管理器验证签名和签名证书链。
受信任与不受信任的证书

如果一张证书能链接到包管理器信任存储中的任一根证书,它就是受信任的;信任存储包括:

  • 当 signing.includeDefaultTrustedRootCertificates 为 true 时,包管理器的默认信任存储。
  • 位于 signing.trustedRootCertificatesPath 所配置信任根目录中的自定义根证书。证书必须是 DER 编码的。

否则,该证书不受信任,会按照 signing.onUntrustedCertificate 配置处理。如果用户选择继续使用不受信任的证书,包管理器会像对待未签名包那样继续处理。

证书策略

包管理器要求所有用于包签名的证书都带有"代码签名"扩展密钥用法扩展,并且必须满足 RFC 5280 的核心策略(由 swift-certificates 实现)。

用户可以通过 signing.validationChecks.certificateExpiration 和 signing.validationChecks.certificateRevocation 配置分别控制证书过期检查和吊销检查。注意吊销检查隐式要求启用过期检查。

如果签名证书无效,包管理器在从注册表或包集合下载时会拒绝该归档。

已签名的包集合

包集合发布者可以给集合签名以保护其内容不被篡改。如果集合已签名,包管理器在导入之前会检查签名是否有效;只要下列任何一项不满足就返回错误:

  • 文件内容(不含签名部分)必须与生成签名时所用的内容一致。换句话说,这是检查集合在签名之后是否被改动过。
  • 签名证书必须满足全部要求。

由于给包集合签名是可选的,包管理器在用户添加未签名集合之前会提示用户确认。

package-collection-sign 可以帮助发布者给包集合签名。要生成签名,你需要提供:

  • 待签名的包集合文件。
  • 一份 DER 编码的代码签名证书。
  • 该证书私钥的 PEM 编码。
  • 该证书完整的证书链。

已签名的包集合会多出一个 signature 对象:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
{
  ...,
  "signature": {
    "signature": "<SIGNATURE>",
    "certificate": {
      "subject": {
        "commonName": "Jane Doe",
        ...
      },
      "issuer": {
        "commonName": "Sample CA",
        ...
      }
    }
  }
}
  • 包管理器用签名字符串(即 "<SIGNATURE>")来验证集合文件的内容在签名之后没有被篡改。当用户添加该集合到已配置的集合列表时,包管理器会验证签名。签名中包含了证书的公钥和证书链。
  • certificate 包含从签名证书中提取的细节。subject.commonName 应与发布者名称一致,以便用户辨认。证书的根证书必须已安装并被用户机器信任。

关于添加已签名包集合的更多内容,参见已签名的包集合。

对签名证书的要求

用于给包集合签名的证书必须满足下列要求,这些要求在签名生成(发布者)和验证(Swift Package Manager 用户)时都会被检查并强制执行:

  • 签名/验证发生的时间必须落在签名证书的有效期内。
  • 证书的"扩展密钥用法"扩展必须包含"代码签名"。
  • 证书必须使用 256 位 EC 密钥(出于更高安全性推荐)或 2048 位 RSA 密钥。
  • 证书不得已被吊销。证书颁发机构必须支持 OCSP,也就是说证书必须带有"证书颁发机构信息访问"扩展,且其中把 OCSP 列为一种方式并指定响应者的 URL。
  • 证书链有效,且根证书必须受信任。

来自 developer.apple.com 的未过期、未吊销的 Swift Package Collection 证书满足上述全部条件。

受信任的根证书

由于生成集合签名需要证书,签名检查的一部分工作就是验证证书及其链条,并确认根证书受信任。

在 Apple 平台上,随操作系统预装的所有根证书都自动受信任。用户可以把额外的证书放入 ~/.swiftpm/config/trust-root-certs 目录来增加受信任的证书。

在非 Apple 平台上,默认没有受信任的根证书,只有随证书固定配置一起提供的那些可以信任;除此之外,只有 ~/.swiftpm/config/trust-root-certs 中的证书受信任。这意味着除非设置好 trust-root-certs 目录,否则签名检查总会失败。

用户可以明确表示信任某个发布者及其发布的全部集合:取得该发布者的根证书并保存到 ~/.swiftpm/config/trust-root-certs 即可。根证书必须是 DER 编码的。由于包管理器信任某个根证书之下的所有证书链,取决于安装了哪些根证书,某些发布者可能已被隐式信任,用户无需逐个显式指定。

使用 package-collection-sign 工具时,作为签名输入提供的根证书会自动受信任。不过当包管理器用户尝试添加该集合时,根证书必须要么随操作系统预装(仅 Apple 平台),要么位于 ~/.swiftpm/config/trust-root-certs 目录中(所有平台),要么随证书固定配置一起提供,否则签名检查会失败。集合发布者应当把自己使用的 DER 编码根证书放在可下载的位置,以便用户在需要时调整自己的设置。