Signing every firmware artifact: how we do it
May 12, 2026 · OTA-Pulse team
An unsigned firmware update landing on a production fleet is a scenario most embedded teams would rather never encounter. The risk is straightforward: if a device installs arbitrary code delivered over the network, a compromised server, a rogue access point, or a supply-chain incident can turn a routine maintenance window into a mass brick event. Signing every artifact before it leaves the server is the first line of defense, and it is not optional.
This post walks through how OTA-Pulse approaches artifact signing — the algorithms we chose, how keys are managed, and what the device actually does when it receives a signed package.
Two algorithms, one verifier
OTA-Pulse supports two signing algorithms:
- RSA-4096 with PSS padding and SHA-256 — industry-standard, widely supported by OpenSSL, hardware security modules, and every major Linux keystore. PSS padding is probabilistic, which removes the determinism weakness present in PKCS#1 v1.5.
- ECDSA on the P-384 curve with SHA-384 — produces a smaller signature (~96 bytes vs ~512 bytes for RSA-4096) and is faster to verify on constrained hardware. P-384 provides 192-bit security, well above the 128-bit floor for new deployments.
Both algorithms are handled by the same verification interface on the device. A fleet that wants to rotate from RSA to ECDSA can run both key types in parallel during the transition — the verifier tries each loaded key and accepts the artifact if any valid key produces a successful verification.
Key management: server holds the secret, device holds the check
The private signing key never leaves the server environment. It is generated in the OTA-Pulse backend, optionally stored in an HSM or secret vault, and used only to produce signatures at deploy time. The corresponding public key is the only thing that travels to the device.
Public keys are embedded in the firmware image itself during the Yocto build. The Yocto recipe installs them to a read-only path on the device:
/etc/soc-monitoring/signing-keys/active/
├── production-rsa-public.pem
└── production-ecdsa-public.pem
The active/ directory is owned by root and mounted read-only by the init system. An attacker who gains user-level shell access cannot substitute a key and wait for the next update. The only path to replacing a key is a full signed firmware update — which requires the existing key to be valid in the first place.
The server configuration that controls which algorithms are required looks like this:
{
"signing_enabled": true,
"algorithms": ["RSA-4096", "ECDSA-P384"],
"require_signatures": true
}
Setting require_signatures: true causes the server to reject deployment requests for any artifact that has not been signed. Unsigned artifacts are blocked at the API layer before any device ever sees them.
Verification flow on the device
When the OTA agent receives a deployment notification, it follows this sequence:
- Download the firmware artifact.
- Download the detached
.sigfile from the same distribution endpoint. - Load all public keys found in
/etc/soc-monitoring/signing-keys/active/. - For each loaded key, attempt to verify the signature against the artifact hash.
- If any key produces a valid verification result, proceed with installation.
- If no key produces a valid result, abort and report the failure.
The agent never writes the artifact to the final installation partition until step 5 succeeds. There is no “try and see” — a failed verification triggers an immediate abort followed by deletion of the downloaded artifact from the staging area.
Failure modes
Three categories of failure are handled explicitly:
Tampered artifact — the artifact was modified in transit. The SHA digest computed on the device will not match the digest that was signed, so every verification attempt fails. The agent logs the failure with the artifact hash and reports back to the server.
Missing signature — the .sig file is absent or empty. The agent treats this as a verification failure rather than a skip. If require_signatures is enabled on the server, this artifact should never have reached the device, but the device-side enforcement is independent of the server check.
Wrong key — the artifact was signed with a key that is not installed on this device. This occurs legitimately during key rotation: old devices carrying only the previous key, new devices carrying only the new key. The transition period must overlap both keys in the active/ directory to serve both cohorts simultaneously.
The on-device security log at /var/log/soc-ota-security.log records every verification attempt — successful or not — with the artifact identifier, the algorithm used, the key fingerprint, and the outcome.
Looking ahead: rotation and multi-org isolation
Key rotation is the next operational concern after initial deployment. The plan is to support rolling rotation: generate a new key pair, add the new public key to a firmware update, deploy to the fleet, and then retire the old key from the server once all devices have the updated firmware. The device will accept signatures from either key during the overlap window.
Multi-organisation isolation is a related goal. A fleet managed by one customer should not be reachable by another customer’s signing key, even if both organisations share the same OTA-Pulse instance. Scoping keys to organisations at the database layer, combined with per-org key directories in the Yocto recipe, provides that isolation without requiring separate infrastructure.
Both capabilities are on the roadmap. In the meantime, the two-layer verification architecture — server validates before deploy, device validates before install — means a compromised distribution endpoint alone is not sufficient to push malicious code to a device.