SWUpdate: build, sign & install a minimal .swu that updates only an app + config

SWUpdate: build, sign & install a minimal .swu that updates only an app + config

Goal

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.


Directory structure

Below is the recommended structure of your update package.

  • sw-description – The manifest file describing all update components.
  • sw-description.sig – The signature of the manifest, generated after signing.
  • app/ – Contains application binaries to be installed.
  • config/ – Holds configuration files that need to be updated.
  • lua/ – Optional directory for Lua hooks (preinst, postinst, etc.) for custom logic.
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

Concepts (very short)

  • .swu – A cpio CRC archive. Not encrypted by default.
  • sw-description – The manifest: which files to install, where, how to verify them (hashes), and what scripts to run.
  • Signature – Only sw-description is signed. It in turn contains hashes of every payload file. This gives authenticity + integrity.
  • Encryption – Not provided by default. Use OpenSSL (or SWUpdate’s encrypted images feature) if you need confidentiality.
  • Handlers – Code inside SWUpdate that knows how to install each “type” (raw, file, ubivol, …). If a handler is missing, you’ll get “feature … absent”.
  • State markers / bootloader markers – Keep the system sane if a power cut happens mid‑update (A/B, rollback, etc.).

sw-description – minimal, signed, hashed, raw-only

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";
    };
}

Generate the hashes

sha256sum app/my_app config/my_app.conf
# Put these values into sw-description

Example Lua hook (lua/app_update.lua) – optional

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".


Signing & packaging

1) Keys (one time)

openssl genrsa -out priv.pem 2048
openssl rsa  -in priv.pem -pubout -out pub.pem

2) Sign sw-description

openssl dgst -sha256 -sign priv.pem -out sw-description.sig sw-description

3) Pack (ensure sw-description is 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) Verify locally

openssl dgst -sha256 -verify ../pub.pem -signature sw-description.sig sw-description

5) Run SWUpdate

../swupdate/swupdate -i my_update.swu -k ../pub.pem

Optional: Encrypt the .swu

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

Common errors & how to fix them

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

One-shot helper script

#!/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."

Appendix – quick OpenSSL cheat sheet

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

TL;DR – The shortest working recipe

# 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".


Makefile for Automated Build

# 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)

Final checklist

  • sw-description has hashes for every file.
  • You re-signed 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.
  • SWUpdate built with SIGNED_IMAGES and the raw handler.
  • -k argument matches what your build expects.
  • (Optional) Lua hooks or postinstall scripts do chmod / service restart / rollback.
  • (Optional) Encrypt .swu if you need confidentiality.
Comments
comments powered by Disqus