1.3 包的安全性
6 分钟阅读
原文链接: 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 对象:
| |
- 包管理器用签名字符串(即
"<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 编码根证书放在可下载的位置,以便用户在需要时调整自己的设置。