AWS KMS Signing
Configure a non-exportable AWS KMS key for signed OTA deployments
Set Up AWS KMS Signing
AWS KMS asymmetric keys
perform signing without returning the private key. Create an RSA key with
SIGN_VERIFY usage, export its public key, and give the deploy identity only
the permissions it needs to use that key.
Review the signing rollout before adding a signer to an existing Hot Updater project.
1. Create the signing key
The following example creates a 4096-bit RSA key and writes its public key as SPKI PEM. The public key is not secret.
mkdir -p keys
KEY_ARN=$(aws kms create-key \
--key-spec RSA_4096 \
--key-usage SIGN_VERIFY \
--description "Hot Updater bundle signing" \
--query KeyMetadata.Arn \
--output text)
aws kms get-public-key \
--key-id "$KEY_ARN" \
--query PublicKey \
--output text \
| openssl base64 -d -A \
| openssl pkey -pubin -inform DER -out keys/public-key.pemSave the immutable key ARN in deployment configuration. Do not use an alias that can silently move to another key: its public key would no longer match the key embedded in existing apps.
2. Keep the public key with the project
Keep local private material ignored and include only the public file:
keys/
keys/*
!keys/public-key.pemCommit keys/public-key.pem. Expo and native builds need this public file, and
there is no security benefit in hiding it. Replace an existing keys/ rule
with the rules above: an exception for a file inside an ignored directory is
not sufficient on its own.
3. Configure the provider
Install the optional AWS KMS SDK in the project that runs the deploy command:
npm install '@aws-sdk/client-kms@^3.1066.0' --save-devimport { defineConfig } from "hot-updater";
import { awsKmsSigning } from "hot-updater/signing";
export default defineConfig({
// ...other config
signing: awsKmsSigning({
// A key ARN is an identifier, not a secret. Pin the exact key.
keyId:
"arn:aws:kms:ap-northeast-2:123456789012:key/00000000-0000-0000-0000-000000000000",
region: "ap-northeast-2",
}),
});awsKmsSigning uses the AWS SDK default credential chain. Prefer a short-lived
workload identity instead of putting long-lived credentials in
hot-updater.config.ts. An explicit HTTPS endpoint is available for private
KMS endpoints; HTTP is accepted only for loopback testing.
Hot Updater verifies every provider signature against the public key returned by KMS before upload. It also compares that signer identity with the public key configured in the native app.
4. Embed the public key and release the app
Follow the shared steps for React Native CLI or Expo and EAS Build. The Expo config plugin reads the checked-in public key and does not need AWS credentials or private key material.
Build and release the native app before the first signed OTA deployment. For an existing signing key, follow Key Rotation before switching the signer used for installed apps.
5. Deploy with GitHub Actions OIDC
Use GitHub Actions OIDC to exchange the workflow identity for short-lived AWS credentials. Restrict the IAM trust policy to the expected repository and protected branch, tag, or GitHub environment.
For signing, the deploy role needs access only to the pinned key. Add storage and database permissions separately for the providers used by your project.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "kms:GetPublicKey",
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/00000000-0000-0000-0000-000000000000"
},
{
"Effect": "Allow",
"Action": "kms:Sign",
"Resource": "arn:aws:kms:ap-northeast-2:123456789012:key/00000000-0000-0000-0000-000000000000",
"Condition": {
"StringEquals": {
"kms:MessageType": "RAW",
"kms:SigningAlgorithm": "RSASSA_PKCS1_V1_5_SHA_256"
}
}
}
]
}on:
workflow_dispatch:
permissions:
contents: read
id-token: write
jobs:
deploy:
runs-on: ubuntu-latest
environment: production
steps:
- name: Check out the reviewed source
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6
- name: Set up Node.js
uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 # v6
with:
cache: npm
node-version: 22
- name: Install the locked dependency graph
run: npm ci
- name: Authenticate to AWS
uses: aws-actions/configure-aws-credentials@e6de054238d6b7531b4efff3b6587d9aade6a06c # v6
with:
role-to-assume: arn:aws:iam::123456789012:role/hot-updater-deploy
aws-region: ap-northeast-2
- name: Deploy signed bundle
run: npx --no-install hot-updater deploy -p iosUse environment protection rules and required reviewers for the signing job. The example pins actions to the commits behind their v6 releases; review and update those pins deliberately when upgrading.
KMS prevents private-key extraction, but it cannot decide whether code is safe.
Any dependency or script running with kms:Sign can request signatures. Treat
the build and deploy dependency graph as part of the trusted signing boundary,
and run the signing workflow only from reviewed protected refs. Avoid granting
signing credentials to pull-request workflows, especially
pull_request_target jobs that execute untrusted code.
The AWS provider uses the shared RSA-SHA256 protocol
with MessageType: RAW. See remote call behavior
for request caching and deployment failures.