Every bundle you deploy can carry an RSA signature that proves it came from your signing key. The server verifies it on upload, and each client verifies it again before applying the bundle.
Algorithm
| Property | Value |
|---|---|
| Key type | RSA, 4096-bit modulus |
| Signature scheme | RSASSA-PKCS1-v1_5 (not PSS) |
| Digest | SHA-256 |
| Signature encoding | Base64 |
| Public key format on the wire | PEM, X.509 SubjectPublicKeyInfo (-----BEGIN PUBLIC KEY-----) |
| Private key format on disk | PEM, PKCS#8 (-----BEGIN PRIVATE KEY-----) |
The signature is computed over the exact bytes that are uploaded. When bundle encryption is enabled, those bytes are the ciphertext — encryption runs before checksumming and signing. See Bundle encryption.
Generating keys
Run this from your project directory:
dya keys generate
It creates four files in the current working directory — two key pairs, one for signing and one for encryption:
| File | Purpose | Where it belongs |
|---|---|---|
dya-signing-private.pem |
Signs bundles during dya deploy |
Your machine or a CI secret. Never commit it |
dya-signing-public.pem |
Verifies signatures | Uploaded to the server, and set as the plugin's publicKey |
dya-encryption-public.pem |
Encrypts bundles during dya deploy |
Stays with the CLI; also uploaded to the server |
dya-encryption-private.pem |
Decrypts bundles on the device | Embedded in your app's native binary |
Both private key files are written with mode 0600.
If you are logged in and have an active organization and an app ID in your project config,
dya keys generate uploads both public keys to the server automatically. If that upload is
skipped or fails, run it explicitly:
dya keys upload --app-id com.example.app
Either path calls POST /api/orgs/:orgId/apps/:appId/keys with publicKey and, when the file
exists, encryptionPublicKey. The server stores them on the app record. It never receives a
private key.
dya keys generate refuses to run when dya-signing-private.pem already exists. Delete the
existing key files first if you intend to replace them.
Configuring the client
The server sends the signature to the device in the update-check response. The client only verifies it if you give the client the public key.
Capacitor — capacitor.config.json:
{
"plugins": {
"DeployYourApp": {
"appId": "com.example.app",
"publicKey": "-----BEGIN PUBLIC KEY-----\nMIICIjANBg...\n-----END PUBLIC KEY-----"
}
}
}
Electron — pass it to the constructor:
import { ElectronUpdateManager } from '@deploy-your-app/electron-update-manager';
const updater = new ElectronUpdateManager({
appId: 'com.example.app',
publicKey: process.env.DYA_PUBLIC_KEY,
});
Use the X.509 SPKI form — the contents of dya-signing-public.pem as generated. The Android
client parses the public key with X509EncodedKeySpec and will not accept a PKCS#1
(-----BEGIN RSA PUBLIC KEY-----) key.
Signing on deploy
dya deploy signs automatically when dya-signing-private.pem is present in the working
directory. The order is fixed:
Build web assets
|
v
Bundle and compress (zip)
|
v
Encrypt with AES-256-GCM (only if dya-encryption-public.pem exists)
|
v
SHA-256 checksum of the bytes to be uploaded
|
v
RSA-SHA256 signature over the same bytes
|
v
Upload bundle + checksum + signature (+ wrapped session key)
If no signing key is found, the CLI prints a warning and uploads the bundle with no signature
field.
Server-side verification
On POST /api/orgs/:orgId/apps/:appId/bundles the server:
- Checks whether the app has a registered signing public key. If it does and the upload has no signature, the upload is rejected with a message naming the missing key file.
- Verifies the signature over the uploaded bytes with
RSA-SHA256against the registered public key. An invalid signature is rejected. - Verifies the SHA-256 checksum using a timing-safe comparison. A mismatch is rejected.
An app with no registered public key accepts unsigned uploads. Register a key to close that path.
Client-side verification
All three clients verify in the same order, over the downloaded bytes, before any decryption or unzip:
- SHA-256 checksum against the
checksumfield from the update-check response. - RSA signature against the configured
publicKey, if one is configured. - Only then decrypt (when a
sessionKeywas returned) and unpack.
| Client | Verification API | Digest handling |
|---|---|---|
| iOS | SecKeyVerifySignature with .rsaSignatureDigestPKCS1v15SHA256 |
Pre-hashes with CC_SHA256 and passes the digest |
| Android | Signature.getInstance("SHA256withRSA") |
Passes the full data; the provider hashes |
| Electron | createVerify('SHA256') |
Passes the full data; Node hashes |
A failed signature check aborts the update, emits an update_verify_fail analytics event with
reason: "signature_invalid", and leaves the currently active bundle in place.
When verification is skipped
Signature verification is opt-in on the client. If publicKey is not set in the plugin or
updater configuration, none of the three clients verify a signature. The only remaining
integrity check is the SHA-256 checksum, and that checksum arrives in the same response as the
download URL — it protects against transport corruption, not against a compromised update
server. Set publicKey in production.
There is a second, narrower gap. If the server returns signature: null for a bundle that was
uploaded unsigned:
- iOS and Android skip verification and apply the bundle. Their condition requires both a
configured
publicKeyand a non-null signature. - Electron aborts the download, because its condition tests
publicKeyalone.
The practical consequence is that a client-side publicKey does not by itself guarantee every
bundle was signed. Register a signing public key on the app so the server refuses unsigned
uploads in the first place.
Key rotation
Rotate when a signing private key may have been exposed, or on whatever schedule your own policy sets.
- Delete
dya-signing-private.pemanddya-signing-public.pemfrom your working directory and from any CI secret store. - Run
dya keys generate. It creates a new pair and uploads the new public key, replacing the one on the app record. - Update the
publicKeyvalue in your Capacitor config or Electron updater config, and ship a native app release containing it. - Redeploy your bundles with
dya deployso they carry signatures from the new key.
Sequencing matters. The server verifies against a single registered public key, so bundles signed with the old key stop passing upload verification as soon as the new key is registered. Devices still running the old native binary hold the old public key and will reject bundles signed with the new one — so a rotation requires a native release, not only an OTA deploy.
Bundles already downloaded and applied on a device are not recalled by a rotation. If you are rotating because a key leaked, also publish a clean bundle and mark it mandatory.