etminan

Offline-verifiable TPM attestation for Linux — no cloud, no third-party registrar

On-box integrity checkers share a structural weakness: the checksum database and the alarm both live on the very host being watched. Etminan splits the job in two — and the device that makes the decision needs no TPM of its own.

01The alarm that lives inside the building it guards

AIDE, Tripwire, Samhain and their relatives all work the same way: a program on the host hashes the files you care about, compares them against a database of known-good hashes it keeps locally, and shouts if something changed. This catches the honest failure — a botched deploy, a config someone edited by hand, a package that drifted. It does not catch the dishonest one.

The problem is structural, not a bug in any particular tool: the checksum database and the code that raises the alarm both live on the machine being watched. A root-level compromise sits above both. An attacker who owns the box can recompute the database to include their backdoored binary, disable the periodic check, or simply arrange for the alarm to never leave the host. By the time anyone looks, the watchdog is reporting “all clear” — truthfully, from its own corrupted point of view.

Concretely: an attacker replaces /usr/sbin/sshd with a version that logs credentials, then runs aide --update to bless the new hash and edits a cron job so the next comparison never fires. Nothing is left for the next scan to find. The tool did exactly what it was designed to do; the design just assumed the host telling the truth about itself. Once root is gone, that assumption is gone with it.

02Two trust domains, one relay

Etminan starts from the opposite assumption: the monitored host may already be lying. So it never asks the host to judge itself. The job is split across two separate trust domains.

The measurement itself comes from two pieces of hardware and kernel machinery the host cannot talk its way around. Linux's IMA (Integrity Measurement Architecture) subsystem hashes executables and files as they are opened and extends each hash into a TPM Platform Configuration Register — PCR 10. A PCR cannot be set to an arbitrary value; it can only be “extended” (new value = hash of old value concatenated with the new measurement). Its contents are a tamper-evident running summary of everything IMA has seen since boot. You cannot walk it backwards to hide an entry.

Each cycle, the verifier sends a fresh random nonce and asks the agent for a quote: a TPM-signed statement of the current PCR values (PCR 10 for IMA, plus PCRs 0–7 for the boot chain) with that nonce folded in. The agent returns the quote together with the human-readable IMA measurement log. Then the verifier does the work:

That last step is the load-bearing one. It binds the human-readable log — the list of exactly which files were measured and what their hashes were — to the cryptographic quote. The host cannot hand over a tidy log that disagrees with the PCR the TPM signed, because the verifier rebuilds the PCR from the log and compares. And the host cannot forge a freshly-nonced, validly-signed quote showing clean PCRs, because the AK private key never leaves the TPM. The host is a relay. It can refuse to answer — but silence is itself an alarm.

03The engineering punchline: the verifier needs no TPM

Here is the part that tends to surprise people who have worked with TPMs before. The verifier needs no TPM of its own. Verifying a quote is pure signature math over data the agent already sent — a public-key check, a nonce comparison, and a hash replay. None of it touches a TPM device. We confirmed this early, in Phase 0, by verifying real quotes with tpm2_checkquote using no TCTI, no /dev/tpm* device flag, nothing but the public key and the quote bytes.

In practice, the only reason a naive verifier build would still link libtss2 is the marshalling: TPM structures like TPMS_ATTEST and TPMT_SIGNATURE arrive in the TPM's own wire encoding, and the easy way to parse them is to call the vendor library. We took the harder way. The verifier hand-rolls exactly those parsers in a small tpm_wire.rs, differentially tested against the real tss-esapi to confirm byte-for-byte agreement (the real library stayed on as a dev-dependency only, purely to keep that test honest).

The result: the verifier is a self-contained static Linux binary (musl) with no libtss2 dependency at all. It carries no TPM device requirement, so the machine that makes every trust decision doesn't need to be — and shouldn't be — the same kind of machine as the ones it watches.

One honesty note, because it matters to engineers reading the repo: the verifier also compiles on macOS, which is convenient for development. That is not a deployment claim. What ships and what you run in production is the Linux x86_64 binary. The agent, by contrast, does need a real TPM and IMA, so it is Linux-only by nature.

04Why “offline” is the whole point

Cloud attestation services — AWS Nitro, Azure Attestation, GCP — are real and they work. They are also disqualified outright for a specific and growing set of deployments: EU critical infrastructure under NIS2, financial systems under DORA, defense and government estates, and OT/ICS networks that are air-gapped on purpose. In all of these, putting the attestation decision — the thing that says “this host is trustworthy” — inside a US cloud provider's trust path is a non-starter before the first technical question is even asked.

Etminan's core attestation loop and all of its automatic, background operation contact no cloud, phone no telemetry home, and put no third party in the trust path. Everything runs on hardware the operator owns and administers.

Honest carve-out: there is exactly one operator-initiated network call that reaches etminan.dev — the optional plugins list/install catalog fetch, which you run by hand when you choose to install a signed plugin. It is not part of the attestation loop and never runs on its own. Nothing else phones home. (A fully air-gapped verifier mode — no outbound at all — is an Enterprise feature in development, not something we claim today.)

05Operator control instead of auto-magic

An integrity system is only as good as what it does when something changes — and on a real host, something changes constantly. The lazy answer is to auto-accept anything that looks routine, which quietly trains the system to normalize drift until the one change that matters slips through with the rest.

Etminan does the opposite. Drift is proposed, never auto-accepted. A new or changed measurement lands in a pending queue. It becomes part of the trusted baseline only when an operator explicitly approves it, and that approval is signed with an Ed25519 operator key and appended to a self-contained, hash-chained audit log — as is every other trust-changing action (enrollment, rejection, exclusion rules, key rotation). There is no auto-approve path to disable, because there was never one to begin with. The Enterprise edition adds two-person / dual-control approval and native SIEM output (CEF/LEEF over syslog) on top of this, for shops where separation of duties is a written requirement rather than a preference.

06What it does not do

Stating the threat model honestly is not a disclaimer at the bottom of the page; in security circles it is the part that makes the rest believable. So, plainly:

07Where this sits next to Keylime

The obvious comparison is Keylime, the CNCF remote-attestation project, and it deserves a fair one rather than a straw man. Keylime is a full attestation platform: a registrar, a verifier, a policy engine, a revocation notifier, its own runtime measurement policy language. If you want that platform and want to automate policy at scale, it is a strong, mature choice.

Keylime is the right choice if you want a full attestation platform with a policy engine. Etminan is the right choice if you want a minimal, auditable, operator-controlled integrity proof that a human fully understands — and whose verifier runs offline without a TPM of its own.

Two lean Rust binaries and a SQLite baseline, every trust change signed and logged, no auto-approve, a verifier with no TPM and no libtss2. Different shape, different operating model. The comparison page lays out where Etminan is concretely ahead of the classic file-integrity tools and where it is honestly behind.


Try it

The free, open Standard edition is a package install and a quickstart that runs in a few minutes — verifier identity, operator key, agent enrollment, first baseline approval. Pull it, run it against a host you control, and tell us where it's wrong.

All posts