Turbopush
CLI

Code Signing

Protect your OTA updates with code signing. Learn how to generate key pairs and sign bundles to prevent man-in-the-middle attacks.

Code signing creates digital signatures for bundles, verified on client before installation. Prevents man-in-the-middle attacks.

How It Works

First, the developer generates an asymmetric key pair: the private key will be used for signing bundles; the public key for bundle signature verification. The Turbopush CLI then uses the private key to sign bundles during release-react command. The public key is shipped with the mobile application. Control over the generation and management of keys is in the hands of the developer.

At the end of release command, the cli computes the bundle's content hash and places this value into a JWT signed with the private key. When the Turbopush plugin downloads a bundle to a device, it checks the release file containing the JWT and validates the JWT signature using the public key. If validation fails, the update is not installed.

Key Generation

openssl genrsa -out private.pem
openssl rsa -pubout -in private.pem -out public.pem

The private key (private.pem) is kept on your machine and passed to the CLI when releasing. The public key (public.pem) is shipped inside the app binary.

Setup

Expo (managed projects)

If you use Expo, configure the public key through the Turbopush config plugin instead of editing native files manually. Add CodePushPublicKey alongside CodePushDeploymentKey in your app.json / app.config.ts:

{
  "plugins": [
    [
      "@turbopush/turbopush-expo-plugin",
      {
        "android": {
          "CodePushDeploymentKey": "YOUR_ANDROID_CODE_PUSH_KEY",
          "CodePushPublicKey": "YOUR_PUBLIC_KEY"
        },
        "ios": {
          "CodePushDeploymentKey": "YOUR_IOS_CODE_PUSH_KEY",
          "CodePushPublicKey": "YOUR_PUBLIC_KEY"
        }
      }
    ]
  ]
}

During expo prebuild, the plugin injects CodePushPublicKey into Info.plist (iOS) and strings.xml (Android).

The SDK accepts the public key with or without the -----BEGIN PUBLIC KEY----- / -----END PUBLIC KEY----- headers, it strips both before decoding. You can paste the full public.pem content as-is.

The private key used to sign releases is passed to the CLI with --privateKeyPath (or -k) — it is not part of the plugin config.

Bare React Native

Add the public key to your native code. The SDK reads it from the app config at runtime, so no JS change is needed:

Androidandroid/app/src/main/res/values/strings.xml:

<resources>
  <string name="app_name">my_app</string>
  <string name="CodePushPublicKey">-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----</string>
</resources>

iOSios/MyApp/Info.plist:

<key>CodePushPublicKey</key>
<string>-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA...
-----END PUBLIC KEY-----</string>

Note: Before releasing a signed update, make sure the targeting version of your app already includes the public key configuration. Otherwise, devices running older versions will not be able to install signed updates, as they cannot validate the signature.

Releasing

Sign the update by passing the private key with --privateKeyPath (or -k). It works with both release commands:

# Bare React Native
turbopush release-react myapp-ios ios -k ./private.pem

# Expo managed projects
turbopush release-expo myapp-ios ios "1.0.0" -k ./private.pem

FAQ

ScenarioResult
Updated CLI but don't want code signingNo impact. Feature is optional.
Public key configured but forgot to sign releaseUpdate rejected. Release again with private key.
Signed update but app has outdated/no public keyUpdate rejected. Ensure app has matching public key.
Lost private keyGenerate new key pair, release new binary with new public key, then release updates with new private key.

How is this guide?