This post walks through the server-side architecture of a licensing system we built for ARM64 embedded Linux devices that uses TPM 2.0 chips as hardware trust anchors. The system issues licenses that are cryptographically bound to a specific physical device — no network connection between the device and the server required, ever.
The Problem
Our client ships embedded Linux devices into environments where:
- Devices operate fully offline — no cloud callbacks possible
- License enforcement must survive firmware updates and reboots
- Cloning a disk image to another board must not clone the license
- Different customers need different feature sets on identical hardware
- An admin (not the device) manages the entire license lifecycle
Traditional approaches fall short here. Online license checks are impossible. MAC-address binding is trivially spoofed. File-based licenses without hardware binding can be copied between boards.
Three-Phase Architecture
The system is split into three completely independent phases with no network link between them:
Phase 1 — Enrollment (on device): The device collects its TPM identity — the Endorsement Key public certificate, a freshly generated Attestation Key, manufacturer info — and packages it into an enrollment.json file. This file leaves the device via sneakernet: USB stick, email, support ticket attachment.
Phase 2 — Issuance (on server): An admin uploads the enrollment file through a web UI. The server validates the TPM certificate chain against pinned manufacturer root CAs, confirms the device identity is genuine, and (after admin approval) generates a signed license bound to that specific TPM. The admin downloads license.json and delivers it back to the device the same way.
Phase 3 — Activation (on device): The device verifies the license signature, confirms the embedded TPM identity matches its own hardware, performs a TPM-based proof-of-possession challenge, and enforces the licensed features. Covered in Part 2.
The deliberate air gap between phases is a feature: the license server is a purely internal admin tool. It never needs to be exposed to the internet or to the devices themselves. The attack surface is limited to whoever has physical or email access to the enrollment files.
Server-Side Design Decisions
Two-Tier PKI
The server uses a two-tier certificate hierarchy:
- Root CA — kept completely offline, used only to sign intermediate certificates
- Intermediate CA (Signing CA) — lives on the server, signs every issued license
The device firmware embeds only the Root CA public certificate (baked into the ELF binary at build time). This means if the Intermediate CA key is ever compromised, we generate a new keypair, sign it with the offline Root CA, update the server config, and move on. No firmware update needed on any device in the field. Existing licenses remain verifiable because the chain still terminates at the same Root CA.
Background Validation with Live Progress
When an admin uploads an enrollment file, the server responds immediately (HTTP 202) and runs EK certificate chain validation in the background. This validation involves fetching intermediate certificates from TPM manufacturer PKI servers (via the AIA extension in the EK certificate), which can take seconds depending on the vendor's infrastructure.
Rather than showing a spinner and hoping, the server writes human-readable progress messages to the database as it works ("Fetching intermediate CA from the manufacturer PKI...", "Building certificate chain..."). The web UI polls these via HTMX and renders live status — so the admin knows the system is working, not stuck.
Fetched intermediate certificates are cached locally so subsequent enrollments from devices with the same TPM manufacturer skip the network round-trip entirely.
Trust Store: Pinned Manufacturer Root CAs
The server doesn't trust arbitrary certificate chains. TPM manufacturer Root CA certificates are explicitly pinned in a read-only directory bind-mounted into the container. Adding a new vendor means obtaining their root certificate, verifying the fingerprint, dropping it in the directory, and restarting.
Modular Binding Providers
The core architecture doesn't hardcode TPM 2.0. Enrollment validation and activation proof generation are dispatched based on an identity_schema field in the enrollment data. The current implementation handles tpm2_ek_ak_binding, but the system is designed so adding a new binding type (CAAM secure elements, hardware UIDs with challenge-response, etc.) means implementing a validator and a proof provider, registering them under a new schema string — no database schema changes, no Web UI changes.
One Device, Many Enrollments; One Enrollment, Many Licenses
A physical device is identified by a stable device ID derived from its Endorsement Key. But devices can rotate their Attestation Keys (AK) — for example, after a TPM reset or a key compromise. Each AK rotation produces a new enrollment that goes through admin review.
Multiple licenses can be issued against a single enrollment over time: feature upgrades, renewals, or reconfigurations. When an AK rotation is approved, all licenses from the old enrollment are automatically revoked since they're bound to the old AK and can no longer be activated.
This model cleanly separates device identity (stable) from key identity (rotatable) from license identity (versioned).
Offline Proof-of-Possession
Each license embeds an activation_proof generated using a software implementation of TPM2_MakeCredential — no physical TPM needed on the server. The proof is encrypted to the device's Endorsement Key and bound to its Attestation Key.
On the device side, only the genuine TPM that owns those keys can decrypt the proof via TPM2_ActivateCredential. This is the cryptographic link that makes disk cloning useless: a cloned image has the license file, but a different board's TPM cannot complete the activation challenge.
Plaintext Features, Signed Envelope
License features (which capabilities are enabled, quantities, validity dates) are stored as plaintext JSON. Anyone can read them. But the entire license — features, binding data, activation proof, and the intermediate certificate — is signed. Tampering with any field invalidates the signature, and the device rejects it.
We chose this over encrypted features because debugging is significantly easier when you can inspect a license file with jq, and because the value isn't in hiding feature names — it's in preventing unauthorized activation.
Tech Stack
This is a low-traffic internal admin tool, not a high-throughput API gateway — so we optimized for developer velocity and ecosystem fit over raw performance.
-
FastAPI + Python — async support, strong cryptography ecosystem (
cryptography,tpm2-pytss), Pydantic validation out of the box - HTMX + Tailwind CSS — server-rendered UI with no SPA complexity; admin tools don't need React
- PostgreSQL — JSONB for flexible enrollment/license storage, robust partitioning for audit logs
- Docker Compose — self-contained deployment behind an existing reverse proxy
What's Next
Part 2 will cover the device side: how the embedded C++ firmware validates the license, performs the TPM2_ActivateCredential challenge, and enforces features at runtime — all without any network access.
Part 2 coming soon: Device-side TPM enforcement
Building something similar or have questions? Reach out at veytrontech.com
Top comments (0)