In the Umbra post I made a claim that deserves its own article: that when the CLI opens a tunnel to a confidential VM, "you are not just talking over an encrypted channel, you are talking over an encrypted channel to the code you expected." Vanilla TLS cannot make that promise. This post is about the protocol that can — aTLS, attested TLS — and about atlas, the open-source library that implements it (atlas is the library, aTLS is the protocol).
What TLS Actually Proves
TLS gives you an encrypted, integrity-protected channel to whoever holds a private key, and the web PKI adds a name: some certificate authority vouches that this key belongs to this domain. That is the whole story. Nothing in the handshake says anything about the software on the other end.
For browsing the web this is the right trade-off. For infrastructure that handles your code and credentials, it is thin comfort: a valid certificate means roughly "whoever controlled this DNS name at issuance time, and holds the key now". If the server is compromised, same certificate, same padlock. If the operator swaps the backend for something that logs your traffic, same certificate, same padlock.
What we actually want to authenticate is what is running. That requires help from hardware.
Remote Attestation in Five Minutes
Umbra's CVMs run on Intel TDX, which executes a VM as a trust domain: its memory is encrypted and the host, hypervisor included, stays outside. For this post, the part that matters is that the hardware also keeps measurements — hashes of everything that booted:
- MRTD — the initial memory image of the VM;
- RTMR0–2 — the boot chain: virtual firmware, kernel, OS stack, each stage hash-chained into the next;
- RTMR3 — the runtime register. The guest stack extends it event by event and keeps an event log: which container compose file is running, instance configuration, and — this detail becomes important later — a hash of the TLS key the workload serves.
On request, the CPU signs a quote over these registers plus 64 bytes of caller-chosen report_data, with a key hierarchy that chains up to Intel. A verifier checks the signature against Intel's collateral (the DCAP flow), which also yields a TCB status: UpToDate for a fully patched platform, degrading through OutOfDate to Revoked. Policies pin the statuses they accept, with an optional grace period so a fleet doesn't fall over the day Intel ships a microcode update.
So a quote proves: genuine, acceptably patched hardware booted exactly this stack. The expected "golden" values have to come from somewhere trustworthy — reproducible image builds, which is its own rabbit hole (atlas documents how to compute them).
A Valid Quote Is Not Enough
Here is the trap in the naive design. Fetch a quote from the server, verify the measurements, then open TLS and start talking. Two attacks survive it:
- Replay — an old quote, captured once, presented forever.
- Relay — the one that actually kills the naive design. An attacker sits between you and a genuine CVM. You open TLS to the attacker. The attacker fetches a fresh, perfectly valid quote from the real CVM and forwards it. Every check passes. You are talking to the attacker, who reads everything and proxies the rest.
The root cause: nothing ties the quote to this TLS session. The fix is channel binding.
One Round Trip
This is the aTLS protocol as atlas implements it:
Step 1 — a promiscuous handshake. The client completes a normal TLS 1.3 handshake but accepts the certificate provisionally, recording it instead of trusting it.
Step 2 — EKM and nonce. Both endpoints derive 32 bytes of Exported Keying Material from the session (RFC 5705 / RFC 8446 §7.5, used as RFC 9266 channel binding). EKM is derived from the session secrets, so it has the property we need: an attacker bridging two TLS sessions ends up with two different EKM values, and cannot make them agree. The client then sends a fresh 32-byte nonce over the established channel:
POST /tdx_quote HTTP/1.1
Content-Type: application/json
{ "nonce_hex": "<hex_nonce>" }
Step 3 — the bound quote. The server computes report_data = SHA512(nonce ‖ EKM) from its side of the session and asks the CPU to sign a fresh quote carrying that report_data, returning it with the RTMR event log.
Step 4 — verification. The client now checks, in order: the quote's signature chain and TCB status against Intel's collateral; that report_data equals SHA512(nonce ‖ EKM) computed from its own side of the session — this is the session binding and the freshness in one stroke; that replaying the event log entry by entry reproduces the RTMR3 value the hardware signed; that one of those replayed events binds the certificate key of this very session, proving the attested workload — not a middlebox — owns the negotiated TLS key; and finally that MRTD, RTMR0–2, the OS image hash, and the application compose match the expected values.
Walk the attacks against it. A leaked quote is useless: its report_data was computed over a different session's EKM. An old quote fails the nonce. A stolen certificate key doesn't help: the attacker's session has its own EKM, and the event log binds the key to the attested workload. Software that isn't the expected software fails the measurements. Each check closes a specific door, and the union leaves the attacker needing to break TDX itself.
From Measurements to a Decision
Verification is only as good as your expectations, and atlas makes those explicit in a policy — bootchain measurements, OS image hash, expected compose, acceptable TCB statuses:
use atlas_rs::{atls_connect, Policy, DstackTdxPolicy, ExpectedBootchain};
let policy = Policy::DstackTdx(DstackTdxPolicy {
expected_bootchain: Some(ExpectedBootchain {
mrtd: "b24d3b24e9e3...".into(),
rtmr0: "24c15e08c07a...".into(),
rtmr1: "6e1afb7464ed...".into(),
rtmr2: "89e73cedf48f...".into(),
}),
os_image_hash: Some("86b181377635...".into()),
app_compose: Some(expected_compose),
allowed_tcb_status: vec!["UpToDate".into()],
..Default::default()
});
let tcp = tokio::net::TcpStream::connect((host, 443)).await?;
let (tls_stream, report) = atls_connect(tcp, host, policy, None).await?;
// tls_stream is a normal TLS stream — but now you know what's behind it.
Policies also load from JSON, which is exactly how Umbra uses them: at umbra cvm launch, the Console returns a policy bundle — golden measurements from the image release pipeline plus material bound to this CVM at launch, like the digest of your authorized SSH keys — and the CLI writes it to a per-CVM policy file. Opening a session later is: load the policy, connect, atls_connect, then run a WebSocket-relayed SSH session through the verified stream (the CLI slots in as an OpenSSH ProxyCommand, so plain ssh, VS Code, and Cursor all ride the attested tunnel without knowing it exists).
A nice consequence of per-CVM policies: the Console resolves which FQDN to dial, but it cannot lie usefully. Point the CLI at the wrong CVM — even a genuine one — and verification fails, because that CVM's launch-bound material doesn't match your policy file. Name resolution is untrusted; measurements are the identity. This is also why SSH host keys stop being the trust anchor: attestation replaces trust-on-first-use.
The Same Handshake on Three Edges
In Umbra, this exact verification runs wherever a connection crosses into a TEE, always locally on the side taking the risk:
- Developer machine → Dev CVM. The CLI verifies before any session byte flows, as above.
- Dev CVM → Security CVM. The egress forwarder inside each Dev CVM verifies the Security CVM before shipping sandbox traffic to it. Updated policies and a rotated proxy CA can be distributed through the Console at runtime, but the forwarder verifies them inside the CVM before use — distribution is not trust.
- Console → Security CVM. The control plane attests the Security CVM before issuing it any service credentials, and re-runs the same verification on every reconciliation probe, so infrastructure drift trips an alarm instead of passing silently.
Nobody in this graph accepts a third party's word that verification happened somewhere else. That is the design rule that makes the rest of the system defensible.
What You Still Have to Trust
Attestation moves trust; it does not eliminate it. The honest residue:
- Intel. The silicon, the key hierarchy, and the patch treadmill — TCB statuses exist precisely because this layer moves under you.
- The measured code. A quote proves which software is running, not that the software is free of bugs. A vulnerable image attests beautifully. Small, reviewable, reproducible images are what make measurements meaningful.
- The pipeline that produced the golden values. If the build system is compromised, attackers don't need to defeat attestation — they ship you measurements of their own backdoor. This is why supply-chain work (reproducible builds, SLSA provenance) is not a compliance checkbox here; it is load-bearing.
- The verifier's own host. The policy file sits on your machine; protecting your laptop is still your job.
That first bullet — what it means to put a hardware vendor inside your trust base, and how that compares to protecting data with mathematics instead — is the subject of the next post: TEEs in the broader landscape of privacy-enhancing technologies, next to FHE, MPC, and zero-knowledge proofs.
Try It
atlas is a workspace of bindings around one Rust core, so the same verification runs everywhere your clients do: atlas-rs natively, atlas-python via PyO3, @concrete-security/atlas-node, and a WASM build for browsers (with a small WebSocket proxy for the raw TCP hop, since browsers can't open sockets). There is a relaxed development mode for local iteration; production policies pin UpToDate and the measurements you built yourself.
The atlas repository has the protocol details and API docs, and Umbra is a full working deployment of it — three different trust edges, one handshake.