Chain of trust

┌────────────────────────────────────────────┐ │ eFuse │ │ Hardware root of trust. Never burned by │ │ ThistleOS (unless you explicitly opt in). │ └────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ Recovery OS (ota_0) │ │ Trusted base. Verifies ota_1 before booting. │ │ SHA-256 + (optional) secure boot signature. │ └────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ ThistleOS firmware (ota_1) │ │ Kernel verifies SHA-256 of each .app.elf and │ │ .drv.elf before loading. Signature check for │ │ permission grants. │ └────────────────────────────────────────────┘ │ ▼ ┌────────────────────────────────────────────┐ │ Apps and Drivers (SD card) │ │ Unsigned: zero permissions, isolated. │ │ Signed: declared permissions granted. │ └────────────────────────────────────────────┘

Signing scheme

ThistleOS v0.1.0 uses HMAC-SHA256 with a per-developer shared key stored in the kernel's NVS partition. The developer generates a key pair, registers the public key with the kernel (via a one-time NVS write), and signs binaries with the private key.

A planned upgrade to Ed25519 asymmetric signatures is tracked in the roadmap. Ed25519 will allow the kernel to verify signatures using only the public key embedded in firmware, with no shared secret on the device.

v0.1.0 (current)v0.2.0 (planned)
AlgorithmHMAC-SHA256Ed25519
Key typeShared secret (32 bytes)Public/private key pair
Key storage on deviceNVS (encrypted partition)Public key only, in firmware
Developer workflowPre-shared key exchangePublish public key; sign with private key

Signing a binary (v0.1.0)

# Generate a developer key (keep this secret)
thistle-keygen --output dev_key.bin

# Sign an app
thistle-sign --key dev_key.bin my_app.app.elf
# Produces: my_app.app.elf.sig

# Sign a driver
thistle-sign --key dev_key.bin my_driver.drv.elf
# Produces: my_driver.drv.elf.sig

# Sign a firmware update
thistle-sign --key dev_key.bin thistle_os.bin
# Produces: thistle_os.bin.sig

Registering a developer key on the device

# One-time setup: flash developer key into NVS
thistle-register-key --port /dev/ttyACM0 --key dev_key.bin
Keep your private key secure
Anyone who obtains your private key can sign apps that run with full permissions on any device that trusts your key. Store it in a password manager or hardware security key, not in your source repository.

Permission system

Permissions are a bitmask of permission_t values checked on every privileged syscall. The kernel maintains one bitmask per loaded app ID.

How permissions are granted

  1. App ELF contains a thistle_app_t struct with fields declaring required capabilities
  2. The catalog entry for the app also lists the same permissions in its permissions array
  3. During install, the kernel verifies the signature against the app's content
  4. If valid, permissions_grant(app_id, declared_perms) is called
  5. At runtime, every privileged syscall calls permissions_check(app_id, required_perm)

How unsigned apps are sandboxed

An app without a valid signature is loaded with permissions_grant(app_id, 0) — zero permissions. It can still call display and input syscalls (those are unpermissioned), but any attempt to access radio, GPS, storage, network, audio, or IPC returns ESP_ERR_NOT_ALLOWED.

The app itself is not prevented from running — it just cannot do anything sensitive. This is intentional: it allows unsigned apps to be useful (e.g., a calculator or a clock) while preventing them from silently transmitting location data or sending radio packets.

OTA update verification

The OTA flow has two levels of verification:

  1. SHA-256 — verified immediately after download, before writing to the SD card. A corrupt download is detected and discarded.
  2. Signature — verified before flashing to ota_1. A firmware image without a valid signature from the ThistleOS signing key is rejected outright.

After a new firmware image boots successfully, it must call ota_mark_valid() within 30 seconds. If it does not, the ESP-IDF bootloader automatically rolls back to the previous partition on the next reset.

Password vault encryption

The built-in settings app includes a password vault. Vault entries are encrypted at rest on the SD card using:

The master password is never stored — only the derived key is used for encryption/decryption and is held in DRAM only while the vault app is open. Closing the vault app zeroes the in-memory key.

Key management recommendations

eFuse policy

ThistleOS never burns eFuses by default. eFuse burning is a one-way operation — once a bit is set, it cannot be cleared. Burning the wrong value can permanently brick a device.

eFuse features available (but not enabled by default):

FeatureeFuseEffect
Secure Boot v2ABS_DONE_0Bootloader only boots signed firmware. Requires burning the RSA-3072 or ECDSA public key into eFuse key block.
Flash EncryptionFLASH_CRYPT_CNTFlash contents encrypted at rest. JTAG debugging disabled automatically.
JTAG DisableJTAG_DISABLEPermanently disables JTAG debugging. Useful for production units.
Read-Protect NVS KeyKey block protection bitsNVS encryption key cannot be read back via JTAG or software.
eFuse burning is permanent
Do not burn eFuses on development hardware. Once burned, features like Secure Boot cannot be disabled. Only burn eFuses on production devices using a controlled, well-tested process.