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.
-
An agent runs on each monitored host. It is narrowly privileged — a
non-root systemd service with exactly the capabilities it needs and nothing more. It
measures the host and produces evidence. It never decides whether that evidence is
acceptable.
-
A verifier runs on a separate, independently administered device. It pulls
from each enrolled agent on a schedule, checks the evidence, compares it against an
operator-approved baseline, and is the only place an alarm can fire.
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:
- checks the quote's signature against the host's enrolled attestation key (AK);
- confirms the nonce matches — this quote is fresh, not a replay of a clean one from before the compromise;
- replays the IMA log and recomputes PCR 10, and checks that the result equals the value the TPM just signed.
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:
-
Etminan defends against remote and software-level root compromise — an
attacker who gains privileged code execution on the host over the network.
-
It does not defend against physical attacks: cold-boot,
evil-maid, desoldering or interposing the TPM. If the adversary is standing at the machine
with tools, this is not your control.
-
It does not cover below-TPM firmware implants, nor a
compromised hypervisor presenting a virtual TPM — if the layer beneath the
TPM is lying, the TPM's signature is trustworthy over a lie.
-
PCRs 0–7 give a cheap partial boot-chain measurement, not full measured
boot. We include them because they are nearly free, not because they are a complete boot
attestation.
-
Denial of service is detectable, not preventable. A host can refuse to
answer — but a host that goes silent is a host raising its own alarm.
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.