使用 EAS 更新进行端到端代码签名
了解 EAS 更新中代码签名和密钥轮换的工作原理。
EAS 更新代码签名仅适用于订阅了 EAS 生产或企业计划的账户。了解更多。
expo-updates
库支持使用 公钥密码学 进行端到端代码签名。代码签名允许开发者使用自己的密钥对更新进行加密签名。然后在应用更新之前在客户端上验证签名,这确保 ISP、CDN、云提供商甚至 EAS 本身无法篡改应用运行的更新。
¥The expo-updates
library supports end-to-end code signing using public-key cryptography. Code signing allows developers to cryptographically sign their updates with their own keys. The signatures are then verified on the client before the update is applied, which ensures ISPs, CDNs, cloud providers, and even EAS itself cannot tamper with updates run by apps.
以下步骤将指导你完成生成私钥和相应证书、配置项目以使用代码签名以及发布应用的签名更新的过程。
¥The following steps will guide you through the process of generating a private key and corresponding certificate, configuring your project to use code signing, and publishing a signed update for your app.
1
Generate a private key and corresponding certificate
In this step, we will generate a key pair and corresponding code signing certificate for your app. Specify a directory outside of your source control for the --key-output-directory
flag to ensure the generated private key isn't accidentally added to source control.
-
npx expo-updates codesigning:generate \
--key-output-directory ../keys \
--certificate-output-directory certs \
--certificate-validity-duration-years 10 \
--certificate-common-name "Your Organization Name"
This command generated a key pair along with a code signing certificate to be included in the app:
-
../keys/private-key.pem
: the private key of the key pair. -
../keys/public-key.pem
: the public key of the key pair. -
certs/certificate.pem
: the code signing certificate configured to be valid for 10 years. This file should be added to source control (if applicable). -
The generated private key must be kept private and secure. The command above suggests generating and storing these keys in a directory outside of your source control to ensure they are not accidentally checked into source control. We recommend storing the private key in the same way you would store other sensitive information (KMS, password manager, and so on), and how you store it will vary the steps needed to publish an update in step (3).
-
The public key may be stored alongside the private key, but isn't sensitive.
-
The certificate should be included in the project (checked into source control). It contains the public key and a method for verifying code signatures. When a signed update is downloaded, the update's signature is verified using this certificate.
-
The certificate validity duration is a setting that may vary based on the security needs of your app.
- A shorter validity duration will require key rotation more frequently but is considered better practice since a compromised private key will have a sooner expiration which limits exposure.
- Shorter validity durations add overhead to your app's release process as the key must be rotated more frequently. Binaries with expired certificates won't apply new updates.
- For example, Expo sets this value to 20 years for the public Expo Go app, but only 1 year for internal apps with binaries that are distributed more frequently. We plan to rotate our keys every 10 years.
2
Configure your project to use code signing
-
npx expo-updates codesigning:configure \
--certificate-input-directory certs \
--key-input-directory ../keys
If you are using Continuous Native Generation (CNG) to generate your native projects, then the app.json configuration generated by the npx expo-updates codesigning configure
command is all that you need. The changes will be applied to the native projects the next time they are generated.
Configure code signing in app.json
After running the above command, your app.json will include additional configuration for code signing:
{
"expo": {
"updates": {
"codeSigningCertificate": "./certs/certificate.pem",
"codeSigningMetadata": {
"keyid": "main",
"alg": "rsa-v1_5-sha256"
}
}
}
}
If you are not using Continuous Native Generation (CNG) to generate your native projects, then you will need to configure code signing in your app AndroidManifest.xml and/or Expo.plist files.
Configure code signing in an Android native project
You will need to add two fields to the <application>
element in android/app/src/main/AndroidManifest.xml
Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of certs/certificate.pem and replace all of the \r
characters with 
and \n
with 

manually, or run the following script to do that for you:
-
node -e "console.log(require('fs').readFileSync('./certs/certificate.pem', 'utf8')\
.replace(/\r/g, '
').replace(/\n/g, '
'));"
Now add the following two fields, and replace the android:value
for the expo.modules.updates.CODE_SIGNING_CERTIFICATE
field with the XML-escaped certificate. You do not need to modify the value for expo.modules.updates.CODE_SIGNING_METADATA
entry.
<meta-data
android:name="expo.modules.updates.CODE_SIGNING_CERTIFICATE"
android:value="(insert XML-escaped certificate here)"
/>
<meta-data
android:name="expo.modules.updates.CODE_SIGNING_METADATA"
android:value="{"keyid":"main","alg":"rsa-v1_5-sha256"}"
/>
Configure code signing in an iOS native project
You will need to add two fields to the <dict>
element in ios/project-name/Supporting/Expo.plist.
Before doing that, we need to generate an XML-escaped version of your certificate. You can either copy the contents of certs/certificate.pem and replace all of the \r
characters with 
, or run the following script to do that for you:
-
node -e "console.log(require('fs').readFileSync('./certs/certificate.pem', 'utf8')\
.replace(/\r/g, '
'));"
Now add the following two fields, and replace the certificate value with the XML-escaped certificate. You do not need to update the EXUpdatesCodeSigningMetadata
field.
<key>EXUpdatesCodeSigningCertificate</key>
<string>-----BEGIN CERTIFICATE-----
(insert XML-escaped certificate, it should look something like this)
(spanning multiple lines with \r escaped but \n not escaped)
+-----END CERTIFICATE-----
</string>
<key>EXUpdatesCodeSigningMetadata</key>
<dict>
<key>keyid</key>
<string>main</string>
<key>alg</key>
<string>rsa-v1_5-sha256</string>
</dict>
With code signing configured, create a new build with a new runtime version. The code signing certificate will be embedded in this new build.
3
Publish a signed update for your app
-
eas update --private-key-path ../keys/private-key.pem
During an EAS Update publish using eas update
, the EAS CLI automatically detects that code signing is configured for your app. It then verifies the integrity of the update and creates a digital signature using your private key. This process is performed locally so that your private key never leaves your machine. The generated signature is automatically sent to EAS to store alongside the update.
4
Verify that the update is loaded
Download the update on the client (this step is done automatically by the library). The build from step (2) that is configured for code signing checks if there is a new update available. The server responds with the update published in step (3) and its generated signature. After being downloaded but before being applied, the update is verified against the embedded certificate and included signature. The update is applied if the certificate and signature are valid, and rejected otherwise.
附加信息
¥Additional information
密钥轮换
¥Key rotation
密钥轮换是更改用于签名更新的密钥对的过程。这在以下几种情况下最常见:
¥Key rotation is the process by which the key pair used for signing updates is changed. This is most commonly done in a few cases:
-
密钥过期。在上一节的步骤 (1) 中,我们将
certificate-validity-duration-years
设置为 10 年(尽管它可以配置为任何值)。这意味着 10 年后,使用证书对应的私钥签名的更新在被应用下载后将不再应用。在签名证书到期之前下载的更新将继续正常运行。在证书过期之前轮换密钥有助于预防任何潜在的密钥过期问题,并有助于保证所有用户在旧证书过期之前使用新证书。¥Key expiration. In step (1) from the section above, we set
certificate-validity-duration-years
to 10 years (though it can be configured to any value). This means that after 10 years, updates signed with the private key corresponding to the certificate will no longer be applied after being downloaded by the app. Updates downloaded before the expiration of their signing certificate will continue to function normally. Rotating keys well before the certificate expires helps to preempt any potential key expiration issues and helps to guarantee all users are using the new certificate before the old certificate expires. -
私钥泄露。如果用于签署更新的私钥意外暴露给公众,则不再被视为安全,因此无法再保证用它签名的更新的完整性。例如,恶意行为者可以制作恶意更新并使用泄露的私钥对其进行签名。
¥Private key compromise. If the private key used to sign updates is accidentally exposed to the public, it can no longer be considered secure and therefore the integrity of updates signed with it can no longer be guaranteed. For example, a malicious actor could craft a malicious update and sign it with the leaked private key.
-
安全最佳实践的密钥轮换。最佳实践是定期轮换密钥,以确保系统能够适应上述其他原因之一的手动密钥轮换。
¥Key rotation for security best practices. It is best practice to rotate keys periodically to ensure that a system is resilient to manual key rotation in response to one of the other reasons above.
在任何这些情况下,程序都是相似的:
¥In any of these cases, the procedure is similar:
-
备份上述步骤 (1) 中生成的旧密钥和证书。
¥Back up the old keys and certificate that were generated in step (1) above.
-
按照上述步骤从步骤 (1) 开始生成新密钥。为了帮助调试,你可能希望通过修改应用配置 (app.json) 中的
updates.codeSigningMetadata.keyid
字段来更改新密钥的keyid
。¥Generate a new key by following the steps above starting from step (1). To assist in debugging, you may wish to change the
keyid
of the new key by modifying theupdates.codeSigningMetadata.keyid
field in your app config (app.json). -
代码签名证书是应用运行时的一部分,因此应为使用此证书的构建设置新的运行时版本,以确保只有使用新密钥签名的更新才会在新构建中运行。
¥The code signing certificate is part of the app's runtime, so a new runtime version should be set for builds using this certificate to ensure that only updates signed with the new key run in the new build.
-
按照上述步骤 (3) 使用新密钥发布签名更新。
¥Publish signed updates using the new key by following step (3) above.
删除代码签名
¥Removing code signing
从应用中删除代码签名的过程与 密钥轮换 类似,可以视为 null
密钥的密钥轮换。
¥The process of removing code signing from an app is similar to key rotation and can be thought of as a key rotation to a null
key.
-
备份在上述步骤 (1) 中生成的旧密钥和证书。
¥Backup the old key and certificate that were generated in step (1) above.
-
从应用配置 (app.json) 中删除
updates.codeSigningMetadata
字段。¥Remove the
updates.codeSigningMetadata
field from your app config (app.json). -
新的无证书应用是一个新的独特运行时,因此应为构建设置新的运行时版本,以确保在新构建中仅运行未签名的更新。
¥The new certificate-less app is a new distinct runtime, so a new runtime version should be set for builds to ensure that only unsigned updates run in the new build.