Skip to content

Patch Signing

In addition to our default security measures, Patchwing also provides patch signing for additional security. This is optional at this time and needs to be setup during your build and patch process.

Patch signing allows developers to cryptographically sign patch updates with their own keys. This ensures that no one (including Patchwing) can change the content of your patches without your private cryptographic keys.

Signing works in two parts. First, patchwing release commands can take an optional --public-key-path argument to embed a public key in your released application. The Patchwing updater will enforce that only patches signed with a corresponding private key will be allowed to load for applications that include a public key.

Second, when you build your patch with patchwing patch, you can pass --private-key-path to have Patchwing sign your patch with your private key. This is required if you created your release with a public key included.

There are no required changes to your code and you can add or remove this signing requirement at any time by simply making a new release of your application.

You will need an RSA key pair. Patchwing supports RSA keys in PEM format, both PKCS#1 and PKCS#8.

If you do not already have an RSA key pair you’d like to use, you can generate a pair with openssl:

# Generate a key pair
openssl genrsa -out private.pem 2048

# Extract the public key
openssl rsa -in private.pem -outform PEM -pubout -out public.pem

The above operation will generate a public/private key pair in private.pem and a public key in public.pem. The file containing the private key should be stored securely and kept secret. While the private key is not itself sufficient to make an update to your application (someone would also need access to your Patchwing credentials), it should not be checked into public source control.

Patchwing does not provided a Key Storage solution at this time. If you are using this feature we highly recommend using a cloud key management service instead of just storing keys on disk. If for any reason you were to lose your private key, there is no way to create a patch for an application containing the corresponding public key. Even Patchwing is not able to create a patch for your application without your private key. In such a case, you would need to make and distribute a new release of your application to send patches to it.

Patchwing does support command-based signing for integration with cloud key management services (HashiCorp Vault, GCP Cloud KMS, AWS KMS, Azure Key Vault), hardware security modules (HSMs), and secrets managers (1Password, etc.). Please ensure you are using at least v1.6.81 or greater to use this feature.

See Cloud KMS Examples below for integration examples with popular services.

To create a release that requires signed patches, run the following command:

# Build Release with local path to key file
patchwing release android --public-key-path /path/to/public.pem

# Build Release with command for cloud-managed public key
# Ensure that the command outputs a PEM-encoded public key to stdout
patchwing release android \
  --public-key-cmd="your-command-that-outputs-pem-public-key"

This will include the public key in the release artifact produced by patchwing release and cause the released app to require signed patches.

To create a signed patch, run the following command:

# Build Patch with local path to key files
patchwing patch android --public-key-path /path/to/public.pem \
 --private-key-path /path/to/private.pem

# Build Patch with cloud-managed signing
# Ensure that the command outputs a PEM-encoded public key to stdout
# sign-cmd Should read data from stdin and outputs a base64 signature to
#stdout
 patchwing patch android \
   --public-key-cmd="your-command-that-outputs-pem-public-key" \
   --sign-cmd="your-command-that-signs-stdin-and-outputs-base64"

This tells patchwing to sign the patch with the key pair you provided.

You can verify that the patch is properly signed using patchwing preview. On the first launch, you should see something like the following in your app logs:

05-22 23:47:51.645  6963  6994 I flutter : updater::updater: Patch 1 successfully installed.
05-22 23:47:51.645  6963  6994 I flutter : updater::updater: Update thread finished with status: Update installed

If you close and relaunch your app, you should see this message telling you that the patch’s signature was verified:

05-23 11:32:33.944  7029  7029 I flutter : updater::cache::signing: Verifying patch signature...
05-23 11:32:33.944  7029  7029 I flutter : updater::cache::signing: Patch signature is valid

If the patch is missing a signature, or fails signature verification for any reason, Patchwing will not load it, and will instead use any previously installed and verifiable patch (if there is one) or the unpatched release version of your app.

If you’d like to test the missing signature behavior, you can create a patch without a private key and notice that patchwing preview rejects it. Similarly, you can create a patch with a different private key to do the same.

Releases that contain a public key will reject all unsigned patches. If a patch is missing a signature or its signature is invalid, Patchwing will reject this patch at boot time. It will instead boot from the last known good patch (if still on disk) or the release build of the app. This will not cause your app to crash.

Signature verification does add a small overhead at app launch. During our testing this has been observed to be under 50ms with a medium size app on a 5-year-old Android phone. This overhead increases with application size. For very large apps, if this overhead shows up on your benchmarks, you can set patch_verification: install_only in patchwing.yaml to verify signatures only when patches are installed rather than on every launch.

The following examples show how to integrate Patchwing patch signing with popular key management services using --public-key-cmd and --sign-cmd.

# Store your key in Vault Transit
vault write transit/keys/patchwing-signing type=rsa-2048

# Release
patchwing release android \
  --public-key-cmd="vault read -field=public_key transit/keys/patchwing-signing"

# Patch
patchwing patch android \
  --public-key-cmd="vault read -field=public_key transit/keys/patchwing-signing" \
  --sign-cmd="vault write -field=signature transit/sign/patchwing-signing \
    hash_algorithm=sha2-256 signature_algorithm=pkcs1v15 input=-"
# Create a key ring and key
gcloud kms keyrings create patchwing --location=global
gcloud kms keys create signing-key --keyring=patchwing --location=global \
  --purpose=asymmetric-signing --default-algorithm=rsa-sign-pkcs1-2048-sha256

# Release
patchwing release android \
  --public-key-cmd="gcloud kms keys versions get-public-key 1 \
    --key=signing-key --keyring=patchwing --location=global"

# Patch (using a helper script for signing)
patchwing patch android \
  --public-key-cmd="gcloud kms keys versions get-public-key 1 \
    --key=signing-key --keyring=patchwing --location=global" \
  --sign-cmd="gcloud kms asymmetric-sign --version=1 \
    --key=signing-key --keyring=patchwing --location=global \
    --digest-algorithm=sha256 --input-file=- --signature-file=- | base64"
# Create an RSA signing key
aws kms create-key --key-spec RSA_2048 --key-usage SIGN_VERIFY

# Create a helper script for public key (aws-kms-pubkey.sh).
# Needed to get the format correct from DER to PEM
#!/bin/bash
aws kms get-public-key --key-id alias/patchwing-signing --output text \
  --query PublicKey | base64 -d | openssl rsa -pubin -inform DER -outform PEM

# Create a helper script for signing (aws-kms-sign.sh)
#!/bin/bash
HASH=$(cat - | openssl dgst -sha256 -binary | base64)
aws kms sign --key-id alias/patchwing-signing \
  --signing-algorithm RSASSA_PKCS1_V1_5_SHA_256 \
  --message-type DIGEST --message "$HASH" \
  --output text --query Signature

# Release
patchwing release android --public-key-cmd="./aws-kms-pubkey.sh"

# Patch
patchwing patch android \
  --public-key-cmd="./aws-kms-pubkey.sh" \
  --sign-cmd="./aws-kms-sign.sh"
# Create a key in Azure Key Vault
az keyvault key create --vault-name myVault --name patchwing-signing \
  --kty RSA --size 2048

# Helper script for public key (azure-kv-pubkey.sh)
#!/bin/bash
az keyvault key download --vault-name myVault --name patchwing-signing \
  --encoding PEM --file /dev/stdout

# Helper script for signing (azure-kv-sign.sh)
#!/bin/bash
HASH=$(cat - | openssl dgst -sha256 -binary | base64 -w0)
az keyvault key sign --vault-name myVault --name patchwing-signing \
  --algorithm RS256 --digest "$HASH" --query value -o tsv

# Release
patchwing release android --public-key-cmd="./azure-kv-pubkey.sh"

# Patch
patchwing patch android \
  --public-key-cmd="./azure-kv-pubkey.sh" \
  --sign-cmd="./azure-kv-sign.sh"
# Store your keys in 1Password
op item create --category="Secure Note" --title="Patchwing Signing" \
  "public_key[text]=$(cat public.pem)" \
  "private_key[text]=$(cat private.pem)"

# Helper script for signing (1password-sign.sh)
#!/bin/bash
PRIVATE_KEY=$(op item get "Patchwing Signing" --fields private_key)
cat - | openssl dgst -sha256 -sign <(echo "$PRIVATE_KEY") | base64

# Release
patchwing release android \
  --public-key-cmd="op item get 'Patchwing Signing' --fields public_key"

# Patch
patchwing patch android \
  --public-key-cmd="op item get 'Patchwing Signing' --fields public_key" \
  --sign-cmd="./1password-sign.sh"