SWUpdate: build, sign & install a minimal .swu that updates only an app + config
Published at July 26, 2025 · 6 min read · Tags: swupdate embedded-linux yocto secure-boot cpio
SWUpdate: build, sign & install a minimal .swu that updates only an app + config
Published at July 26, 2025 · 6 min read · Tags: swupdate embedded-linux yocto secure-boot cpio
Produce a small, signed .swu
that installs only a binary and its config file, with hash verification, public‑key signature, and optional Lua/postinstall hooks — and know how to fix every error you’ll hit on the way.
Below is the recommended structure of your update package.
update/
├── sw-description
├── sw-description.sig # created after signing
├── app/
│ └── my_app
├── config/
│ └── my_app.conf
└── lua/
└── app_update.lua # optional: preinst/postinst/on_failure
software = {
version = "1.0";
files: (
{
filename = "app/my_app";
path = "/usr/local/bin/my_app";
type = "raw";
sha256 = "REPLACE_WITH_SHA256_OF_app/my_app";
},
{
filename = "config/my_app.conf";
path = "/etc/my_app/my_app.conf";
type = "raw";
sha256 = "REPLACE_WITH_SHA256_OF_config/my_app.conf";
}
);
/* Optional Lua hooks inside the .swu */
lua = {
preinst = "lua/app_update.lua:preinst";
postinst = "lua/app_update.lua:postinst";
on_failure = "lua/app_update.lua:on_failure";
};
}
sha256sum app/my_app config/my_app.conf
# Put these values into sw-description
local function sh(cmd)
local ok, _, code = os.execute(cmd)
return (ok == true or ok == 0), code
end
function preinst(version, files)
sh("mkdir -p /etc/my_app")
return true
end
function postinst(version, files)
sh("chmod 0755 /usr/local/bin/my_app")
-- Try to restart if systemd exists
sh("systemctl try-restart my_app.service 2>/dev/null || true")
return true
end
function on_failure(reason)
-- Add your rollback logic if you keep backups
return true
end
If your build does not support lua = { ... }
, place it under scripts:
with type="lua"
.
openssl genrsa -out priv.pem 2048
openssl rsa -in priv.pem -pubout -out pub.pem
openssl dgst -sha256 -sign priv.pem -out sw-description.sig sw-description
cd update
(echo sw-description; echo sw-description.sig; find app config lua -type f | sort) | cpio -ov -H crc > my_update.swu
openssl dgst -sha256 -verify ../pub.pem -signature sw-description.sig sw-description
../swupdate/swupdate -i my_update.swu -k ../pub.pem
Sign first, then encrypt (signatures must cover plaintext). Easiest: symmetric AES.
# Encrypt
openssl enc -aes-256-cbc -salt -in my_update.swu -out my_update.enc.swu
# Decrypt
openssl enc -d -aes-256-cbc -in my_update.enc.swu -out my_update.swu
swupdate -i my_update.swu -k pub.pem
Error | Fix |
---|---|
unable to load key filename cert.pem | Try pub.pem (raw) first; if still failing, use cert.pem with –cert-purpose codeSigning |
EVP_DigestVerifyFinal failed | Re-sign sw-description after final edits |
feature ‘raw’ (or ‘file’, ‘config’) … is absent! | Rebuild SWUpdate with that handler or switch to raw |
Hash not set for … Type raw | Add sha256="…" in each files entry |
I cannot open /tmp/app/my_app 2 | Match filename exactly to archive path or flatten filenames |
Required image file my_app missing…aborting ! | Run cpio -itv < my_update.swu and fix paths |
File specified in CONFIG_EMBEDDED_LUA_HANDLER_SOURCE=… not found. | Disable it, or create the file it points to |
#!/usr/bin/env bash
set -euo pipefail
UPDATE_DIR="${1:-update}"
PRIV=priv.pem
PUB=pub.pem
cd "$UPDATE_DIR"
[ -f "../$PRIV" ] || openssl genrsa -out "../$PRIV" 2048
openssl rsa -in "../$PRIV" -pubout -out "../$PUB" >/dev/null 2>&1
hash() { sha256sum "$1" | awk '{print $1}'; }
APP_HASH=$(hash app/my_app)
CFG_HASH=$(hash config/my_app.conf)
sed -i -e "s#REPLACE_WITH_SHA256_OF_app/my_app#$APP_HASH#" -e "s#REPLACE_WITH_SHA256_OF_config/my_app.conf#$CFG_HASH#" sw-description
openssl dgst -sha256 -sign "../$PRIV" -out sw-description.sig sw-description
(echo sw-description; echo sw-description.sig; find app config lua -type f | sort) | cpio -ov -H crc > my_update.swu
openssl dgst -sha256 -verify "../$PUB" -signature sw-description.sig sw-description || { echo "Signature verify FAILED"; exit 1; }
echo "Built my_update.swu successfully."
openssl genrsa -out priv.pem 2048
openssl rsa -in priv.pem -pubout -out pub.pem
openssl req -new -x509 -key priv.pem -out cert.pem -days 3650 -subj "/CN=swupdate-dev" -addext "extendedKeyUsage=codeSigning"
openssl dgst -sha256 -sign priv.pem -out sw-description.sig sw-description
openssl dgst -sha256 -verify pub.pem -signature sw-description.sig sw-description
# 0) Layout
update/
├── sw-description
├── app/my_app
├── config/my_app.conf
└── lua/app_update.lua # (optional)
# 1) Hash each file and put sha256=<hash> in sw-description (see full example below)
# 2) Sign sw-description
openssl genrsa -out priv.pem 2048
openssl rsa -in priv.pem -pubout -out pub.pem
openssl dgst -sha256 -sign priv.pem -out sw-description.sig sw-description
# 3) Pack (sw-description must be first!)
cd update
(echo sw-description; echo sw-description.sig; find app config lua -type f | sort) | cpio -ov -H crc > my_update.swu
# 4) (Optional) verify locally
openssl dgst -sha256 -verify ../pub.pem -signature sw-description.sig sw-description
# 5) Install
../swupdate/swupdate -i my_update.swu -k ../pub.pem
If you see “feature ‘file’ / ‘raw’ / ‘config’ … is absent”, rebuild SWUpdate with the right handler, or use only type="raw"
.
# Path setup
UPDATE_DIR = update
PRIV_KEY = priv.pem
PUB_KEY = pub.pem
SWDESC = $(UPDATE_DIR)/sw-description
SWU = $(UPDATE_DIR)/my_update.swu
SIG = $(UPDATE_DIR)/sw-description.sig
all: $(SWU)
$(PRIV_KEY):
openssl genrsa -out $(PRIV_KEY) 2048
$(PUB_KEY): $(PRIV_KEY)
openssl rsa -in $(PRIV_KEY) -pubout -out $(PUB_KEY)
$(SIG): $(SWDESC) $(PRIV_KEY)
openssl dgst -sha256 -sign $(PRIV_KEY) -out $(SIG) $(SWDESC)
$(SWU): $(SWDESC) $(SIG)
cd $(UPDATE_DIR) && \
echo sw-description > filelist && \
echo sw-description.sig >> filelist && \
find app config lua -type f | sort >> filelist && \
cpio -ov -H crc < filelist > my_update.swu && \
rm filelist
verify:
openssl dgst -sha256 -verify $(PUB_KEY) -signature $(SIG) $(SWDESC)
clean:
rm -f $(SWU) $(SIG)
sw-description
has hashes for every file.sw-description
after the last edit.sw-description
and .sig
are first in .swu
.filename
paths in sw-description
match exactly what’s inside the archive.-k
argument matches what your build expects..swu
if you need confidentiality.