使用 EAS Update 的端到端代码签名
编辑页面
了解代码签名和密钥轮换在 EAS Update 中的工作原理。
For the complete documentation index, see llms.txt. Use this Use this file to discover all available pages.
EAS Update 代码签名仅对订阅了 EAS Production 或 Enterprise 方案的账户可用。了解更多。
expo-updates 库支持使用公钥密码学进行端到端代码签名。代码签名允许开发者使用自己的密钥对更新进行加密签名。随后会在客户端应用更新之前验证这些签名,这确保了 ISP、CDN、云服务提供商,甚至 EAS 本身都无法篡改由应用运行的更新。
以下步骤将引导你完成生成私钥及对应证书、配置项目以使用代码签名,以及为应用发布已签名更新的过程。
1
生成私钥及对应证书
在这一步中,我们将为你的应用生成一对密钥和对应的代码签名证书。请为 --key-output-directory 参数指定一个位于源代码管理之外的目录,以确保生成的私钥不会意外被加入源代码管理。
- npx expo-updates codesigning:generate \ --key-output-directory ../keys \ --certificate-output-directory certs \ --certificate-validity-duration-years 10 \ --certificate-common-name "Your Organization Name"此命令会生成一对密钥以及一个要包含在应用中的代码签名证书:
-
../keys/private-key.pem:该密钥对的私钥。 -
../keys/public-key.pem:该密钥对的公钥。 -
certs/certificate.pem:配置为有效期 10 年的代码签名证书。此文件应加入源代码管理(如果适用)。 -
生成的私钥必须妥善保密并安全存放。上面的命令建议在源代码管理之外的目录中生成并存储这些密钥,以确保它们不会被意外提交到源代码管理中。我们建议以与你存储其他敏感信息相同的方式(KMS、密码管理器等)存储私钥,而你如何存储它将会影响你在第 (3) 步发布更新所需的步骤。
-
公钥可以与私钥一起存放,但它并不敏感。
-
证书应包含在项目中(提交到源代码管理)。它包含公钥以及用于验证代码签名的方法。当下载已签名更新时,会使用此证书验证更新的签名。
-
证书有效期是一个可根据应用安全需求而变化的设置。
- 较短的有效期会要求更频繁地进行key rotation,但通常被认为是更好的做法,因为一旦私钥泄露,其过期时间会更早,从而限制影响范围。
- 较短的有效期会增加应用发布流程的开销,因为密钥需要更频繁地轮换。证书过期的二进制文件将无法应用新的更新。
- 例如,Expo 将此值为公开的 Expo Go 应用设置为 20 年,但对于更频繁分发二进制文件的内部应用则仅设置为 1 年。我们计划每 10 年轮换一次密钥。
2
配置你的项目以使用代码签名
- npx expo-updates codesigning:configure \ --certificate-input-directory certs \ --key-input-directory ../keys如果你正在使用 Continuous Native Generation (CNG) 来生成原生项目,那么 npx expo-updates codesigning configure 命令生成的 app.json 配置就是你所需要的全部内容。相关更改会在下次生成原生项目时应用到这些项目中。
在 app.json 中配置代码签名
运行上述命令后,你的 app.json 将包含额外的代码签名配置:
{ "expo": { "updates": { "codeSigningCertificate": "./certs/certificate.pem", "codeSigningMetadata": { "keyid": "main", "alg": "rsa-v1_5-sha256" } } } }
如果你没有使用 Continuous Native Generation (CNG) 来生成原生项目,那么你需要在应用的 AndroidManifest.xml 和/或 Expo.plist 文件中配置代码签名。
在 Android 原生项目中配置代码签名
你需要在 android/app/src/main/AndroidManifest.xml 中的 <application> 元素添加两个字段。
在此之前,我们需要先生成证书的 XML 转义版本。你可以手动复制 certs/certificate.pem 的内容,并将所有 \r 字符替换为 
,将 \n 替换为 
,也可以运行以下脚本来自动完成:
- node -e "console.log(require('fs').readFileSync('./certs/certificate.pem', 'utf8')\.replace(/\r/g, '
').replace(/\n/g, '
'));"现在添加以下两个字段,并将 expo.modules.updates.CODE_SIGNING_CERTIFICATE 字段的 android:value 替换为 XML 转义后的证书。你不需要修改 expo.modules.updates.CODE_SIGNING_METADATA 条目的值。
<meta-data android:name="expo.modules.updates.CODE_SIGNING_CERTIFICATE" android:value="(在此处插入 XML 转义后的证书)" /> <meta-data android:name="expo.modules.updates.CODE_SIGNING_METADATA" android:value="{"keyid":"main","alg":"rsa-v1_5-sha256"}" />
在 iOS 原生项目中配置代码签名
你需要在 ios/project-name/Supporting/Expo.plist 中的 <dict> 元素添加两个字段。
在此之前,我们需要先生成证书的 XML 转义版本。你可以手动复制 certs/certificate.pem 的内容,并将所有 \r 字符替换为 
,也可以运行以下脚本来自动完成:
- node -e "console.log(require('fs').readFileSync('./certs/certificate.pem', 'utf8')\.replace(/\r/g, '
'));"现在添加以下两个字段,并将证书值替换为 XML 转义后的证书。你不需要更新 EXUpdatesCodeSigningMetadata 字段。
<key>EXUpdatesCodeSigningCertificate</key> <string>-----BEGIN CERTIFICATE-----
 (插入 XML 转义后的证书,它看起来应该类似这样)
 (跨越多行,\r 已转义但 \n 未转义)
 +-----END CERTIFICATE-----
 </string> <key>EXUpdatesCodeSigningMetadata</key> <dict> <key>keyid</key> <string>main</string> <key>alg</key> <string>rsa-v1_5-sha256</string> </dict>
配置好代码签名后,使用新的 runtime version 创建一个新构建。代码签名证书将被嵌入到这个新构建中。
3
4
其他信息
密钥轮换
密钥轮换是指更改用于签名更新的密钥对。最常见于以下几种情况:
- 密钥过期。在上文第 (1) 步中,我们将
certificate-validity-duration-years设置为 10 年(尽管它可以配置为任意值)。这意味着 10 年后,使用与证书对应的私钥签名的更新,在被应用下载后将不再被应用。在证书过期之前下载的更新将继续正常运行。在证书过期前提前轮换密钥,有助于预先避免任何潜在的证书过期问题,并有助于确保所有用户在旧证书过期前都已使用新证书。 - 私钥泄露。如果用于签名更新的私钥意外暴露给公众,那么它将不再能被视为安全,因此用它签名的更新的完整性也无法再得到保证。例如,恶意行为者可能构造一个恶意更新并使用泄露的私钥对其签名。
- 为安全最佳实践而进行的密钥轮换。定期轮换密钥是最佳实践,以确保系统能够在面对上述其他原因之一时,具备手动轮换密钥的韧性。
在任何这些情况下,流程都类似:
- 备份上面第 (1) 步生成的旧密钥和证书。
- 按照上述步骤从第 (1) 步开始生成新密钥。为了便于调试,你可能希望通过修改应用配置(app.json)中的
updates.codeSigningMetadata.keyid字段来更改新密钥的keyid。 - 代码签名证书属于应用的 runtime,因此使用此证书的构建应设置新的 runtime version,以确保新构建中只运行使用新密钥签名的更新。
- 按照上面的第 (3) 步,使用新密钥发布已签名更新。
移除代码签名
从应用中移除代码签名的过程与key rotation类似,可以将其视为将密钥轮换到一个 null 密钥。
- 备份上面第 (1) 步生成的旧密钥和证书。
- 从应用配置(app.json)中移除
updates.codeSigningMetadata字段。 - 不含证书的新应用是一个新的独立 runtime,因此应为构建设置新的 runtime version,以确保新构建中只运行未签名的更新。