# How a TPM works, from PCRs to remote attestation

What discrete and firmware TPMs are, how they keep keys, record the boot in PCRs, and prove a machine's state to a remote verifier through credential activation and quotes.

Author: Berkan YILDIZ
Published: 2026-10-29
Tags: TPM 2.0, Attestation, Measured boot, Hardware security, Firmware security
Canonical: <https://blog.berkan.fr/how-a-tpm-works>


A Trusted Platform Module (TPM) is a small, separate computer attached to the main one. It keeps keys it never hands out, records what happened while the machine started, and signs that record so someone else can check it. That's the whole job. The details get complicated because the TPM has to do all of this without trusting the operating system that sends it commands.

The two earlier articles on this blog show the TPM from the inside of a particular boot: [how EDK II firmware brings it up and fills its PCRs](/uefi-tpm-initialization), and [how BitLocker seals a disk key to them](/bitlocker-tpm-boot-chain). This one steps back. It explains what a TPM is, what is inside it, and how its pieces fit together, and then walks through remote attestation, the protocol that lets a server decide whether to trust a machine it cannot see.

Everything here is TPM 2.0, the version every current PC, server and cloud VM ships with. Command codes, structures and constants come from the [TPM 2.0 Library specification][library-spec], and the commands at the end can be run with [tpm2-tools][tpm2-tools] on Linux.

## The short version

Five ideas carry the rest of the article:

- **A TPM is passive.** It cannot read the computer's memory, pause the CPU or stop anything from running. It answers commands, and everything it knows about the machine is what software told it.
- **Its keys are generated and used inside it.** Software asks the TPM to sign or decrypt; the private key never comes out. Keys kept on disk are encrypted with a key only that TPM holds.
- **PCRs only grow.** A Platform Configuration Register can't be written, only *extended*: its new value is a hash of its old value and the new measurement. No sequence of commands brings it to a chosen value.
- **The TPM holds the summary; the log holds the story.** A PCR is a single hash. The list of what was measured lives in ordinary memory, and the PCR value proves that list was not edited.
- **Attestation is a signed PCR report.** The TPM signs its PCR values and a fresh challenge with a key that a manufacturer's certificate ties to genuine TPM hardware. The verifier checks the signature, replays the log, and decides whether it likes what booted.

## Where a TPM lives

"TPM" names a specification, not a chip. Anything that implements the TCG command set and its security requirements is a TPM, and implementations differ mostly in *where* the code runs and *what* protects its secrets.

| Kind | Where it runs | What protects its secrets | Examples |
| --- | --- | --- | --- |
| Discrete | A dedicated chip on the motherboard, connected over LPC, SPI or I²C | The chip itself: tamper-resistant silicon, often certified to Common Criteria and FIPS 140 | Infineon OPTIGA, Nuvoton NPCT, STMicroelectronics ST33 |
| Integrated | A separate security block inside another chip, such as the CPU package | Hardware isolation inside that chip | Microsoft Pluton, when configured as the TPM |
| Firmware (fTPM) | An application on the platform's security processor, isolated from the main CPU | The security processor's isolation and keys fused into the SoC; state is stored encrypted in SPI flash | Intel PTT (in the CSME), AMD fTPM (on the AMD Secure Processor) |
| Virtual (vTPM) | Software on a hypervisor, one instance per VM | The host and hypervisor | Hyper-V and Azure vTPM, QEMU with `swtpm`, Google Shielded VM, AWS NitroTPM |

From software's point of view all four look the same. Windows talks to them through TPM Base Services, Linux through `/dev/tpmrm0`, and the bytes sent are identical. The differences matter when you ask what an attacker has to break:

- A **discrete TPM** is a separate chip, so its commands travel over a motherboard bus that someone with a screwdriver can probe. Unless software encrypts them, secrets returned by the TPM can be read off that bus, and BitLocker volume keys have been [sniffed this way][bitlocker-sniffing]. The keys *inside* the chip are well protected.
- A **firmware TPM** has no exposed bus: commands cross an internal mailbox. Its secrets, however, are only as safe as the security processor running it. [faulTPM][faultpm] extracted an AMD fTPM's entire internal state by glitching the AMD Secure Processor's supply voltage.
- A **virtual TPM** is a program. It is as trustworthy as the host that runs it, which is why cloud providers protect its state with their own hardware roots.

None of this changes how a TPM is used. It changes how much physical access, and how much effort, a given attack costs.

## What is inside

![The inside of a TPM: an I/O interface that tags each command with a locality, a command dispatcher that checks authorization, cryptographic engines, a random number generator, volatile memory with PCRs, loaded objects and sessions, and non-volatile memory with seeds, NV indices, counters and persistent keys. Wrapped key blobs are stored outside, on the host's disk.](/images/blog/tpm-anatomy.svg)

Every TPM has the same functional parts, whatever it is built from:

- **An I/O interface** receives command bytes and returns response bytes. On a PC this is a block of memory-mapped registers (TIS/FIFO or CRB, defined by the [PC Client Platform TPM Profile][ptp-spec]).
- **A command dispatcher** parses each command, checks that the caller is authorized to use the objects it names, and runs it.
- **Cryptographic engines**: RSA and elliptic-curve keys for signing and encryption, SHA-1, SHA-256 and often SHA-384 for hashing, HMAC, and AES for symmetric encryption.
- **A random number generator** seeds every key the TPM creates. `TPM2_GetRandom` hands its output to software, too.
- **Volatile memory** holds the PCRs, the keys currently loaded, and authorization sessions. It is small: room for a handful of loaded keys and sessions at once. Operating systems run a *resource manager* that swaps objects in and out with `TPM2_ContextSave` and `TPM2_ContextLoad`, so applications can pretend there is more room.
- **Non-volatile memory** keeps what must survive power loss: the hierarchy seeds, a few persistent keys, the manufacturer's certificates, counters, and small *NV indices* that software can define for its own data. A few kilobytes to a few tens of kilobytes in all.
- **A clock and counters** let the TPM put a time on what it signs and notice when the platform has rebooted.

### A TPM only knows what it is told

This is the most important property to understand, and the one most often gotten wrong. The TPM does not watch the system. It never reads RAM, it can't inspect a bootloader, and it doesn't decide what runs. When firmware "measures" a bootloader, *firmware* hashes the bootloader and sends that hash to the TPM. The TPM records the number it was given.

So measurement only means something if the code doing it is trustworthy. The first code to run, which measures the next, is the *root of trust for measurement*. Nothing measured it, so it has to be protected some other way: in read-only boot ROM, or verified by the CPU before it runs (Intel Boot Guard, AMD Platform Secure Boot). If that root is compromised, it can send the TPM the hashes of good code while running bad code, and every PCR will look perfect.

### Localities say who is asking

A PC exposes the TPM's registers at several address windows, one per *locality*, starting at `0xFED40000` and spaced `0x1000` apart. The chipset decides which code may write to which window, and the TPM learns a command's locality from the window it arrived through.

Ordinary software, the OS included, uses locality 0. Higher localities are reserved for code the hardware itself vouches for: locality 4 is generated only by the CPU when it starts a *dynamic* launch (Intel TXT, AMD SKINIT), and localities 3 to 1 for the code that launch hands control to. Some PCRs can only be reset or extended from a high locality, so a value in them proves that the CPU itself started the measurement. That is how D-RTM, the dynamic root of trust, builds a clean measurement chain after the machine is already running.

## Talking to a TPM

A TPM command is a short big-endian byte string: a 10-byte header followed by parameters. Here is `TPM2_GetRandom` asking for eight random bytes, and the reply:

```text Command and response for TPM2_GetRandom
command:   80 01  00 00 00 0c  00 00 01 7b  00 08
           tag    size = 12    command code bytesRequested = 8
                               (GetRandom)

response:  80 01  00 00 00 14  00 00 00 00  00 08  3f a1 07 c2 9e 51 d4 0b
           tag    size = 20    response code size   eight random bytes
                               (success)
```

The tag `0x8001` (`TPM_ST_NO_SESSIONS`) says no authorization follows; `0x8002` (`TPM_ST_SESSIONS`) says it does. A response code of zero is success; anything else names an error, such as a wrong password or an object that is not loaded.

Commands refer to keys, sessions and storage through 32-bit *handles*, whose first byte gives their type:

| Handle | Type | Example |
| --- | --- | --- |
| `0x80xxxxxx` | Transient object: a key loaded until it is flushed or the TPM resets | Whatever `TPM2_Load` returns |
| `0x81xxxxxx` | Persistent object: a key kept in NV memory | `0x81000001`, the usual Storage Root Key; `0x81010001`, the usual RSA EK |
| `0x01xxxxxx` | NV index | `0x01C00002`, the RSA EK certificate |
| `0x02xxxxxx`, `0x03xxxxxx` | HMAC and policy sessions | Returned by `TPM2_StartAuthSession` |
| `0x40xxxxxx` | Permanent entities | `0x40000001` owner, `0x4000000B` endorsement, `0x4000000C` platform, `0x40000007` null |

Most of the rest of this article is about what a few of these commands do: `TPM2_CreatePrimary`, `TPM2_Create`, `TPM2_Load`, `TPM2_PCR_Extend`, `TPM2_Unseal`, `TPM2_ActivateCredential` and `TPM2_Quote`.

## Keys that never leave the TPM

A TPM can create practically unlimited keys while storing almost none of them. It manages this with three ideas: seeds, deterministic primary keys, and wrapping.

### Seeds and hierarchies

A TPM has three *hierarchies*, each with its own owner and its own secret *seed*: a large random value generated inside the TPM and kept in its NV memory.

| Hierarchy | Controlled by | Seed | What it is for |
| --- | --- | --- | --- |
| Platform | Firmware | Platform Primary Seed | The platform manufacturer's own keys. Firmware locks it, with a random password or by disabling it, before booting the OS. |
| Storage (owner) | The machine's owner | Storage Primary Seed | Everyday keys: BitLocker, Windows Hello, SSH keys and so on. |
| Endorsement | The privacy administrator | Endorsement Primary Seed | The TPM's identity, used in attestation. |

A fourth, the *null* hierarchy, has a seed that changes on every reboot, for keys that should never outlive the current boot.

Clearing the TPM (`TPM2_Clear`, or "Clear TPM" in a firmware menu) replaces the Storage Primary Seed. Every key in the storage hierarchy becomes unusable at once, because none of them can be recomputed. The Endorsement Primary Seed is not touched by a clear, which is why a TPM keeps its identity for life.

### Primary keys are recomputed, not stored

A primary key sits at the top of a hierarchy. `TPM2_CreatePrimary` doesn't generate it at random: it derives it from the hierarchy's seed and a *template* (the key type, size, attributes and policy) with a key-derivation function. Same seed and same template, same key, every time.

That means a TPM doesn't need to store its primary keys. Software asks for "the RSA 2048 storage key with the standard template" after each boot and gets back the same key. Keys used constantly, such as the Storage Root Key, are often made persistent with `TPM2_EvictControl` purely to save the time it takes to derive an RSA key.

### Child keys live on your disk

Every other key is created *under* a parent. `TPM2_Create` generates a new key inside the TPM and returns two parts:

- the **public area**: the key type, the public key, the attributes, the policy;
- the **private area**, encrypted with a symmetric key derived from the parent's secrets and protected by an HMAC.

The application stores both on disk, wherever it likes. To use the key later, it sends both back with `TPM2_Load`; the parent decrypts and checks the private area inside the TPM, and the key is usable until it is flushed. A stolen copy of the blob is worthless on any other TPM, and even on this one after a clear.

The result is a tree. The primary key is the root, each blob is encrypted to its parent, and the TPM holds only the seed at the top. Storage is unlimited; the TPM's memory never fills up.

### Attributes decide what a key may do

Each key carries attributes, fixed when it is created and covered by its integrity check, so they can't be changed afterwards. The important ones:

| Attribute | Meaning |
| --- | --- |
| `fixedTPM` | The key can never be copied to another TPM. |
| `fixedParent` | The key can't be moved to another parent, even in the same TPM. |
| `sensitiveDataOrigin` | The TPM generated the private key itself; no one supplied it. |
| `userWithAuth` | The key may be used with its password or HMAC; without it, only a policy authorizes it. |
| `sign`, `decrypt` | What operations the key performs. |
| `restricted` | The key only operates on data the TPM itself produced or checked. |

`restricted` is the attribute attestation rests on. A restricted *signing* key refuses to sign a message that starts with `0xFF544347`, the constant `TPM_GENERATED_VALUE` (`\xFF` then "TCG"). Every structure the TPM produces about itself, such as a PCR report, starts with exactly those four bytes. So when a verifier sees a signature from a restricted key over a message beginning with `0xFF544347`, it knows the TPM wrote that message: software using the same key could never have gotten it signed.

A restricted *decryption* key is the mirror image. It will decrypt only TPM-formatted data, such as child key blobs or credential blobs, never arbitrary ciphertext. Storage keys are restricted decryption keys, and so is the endorsement key, which we'll come back to.

A key's *Name* is its name algorithm followed by a hash of its whole public area, attributes included. Two keys with the same Name are, for every practical purpose, the same key with the same rules. Attestation relies on this too.

## Proving you're allowed: authorization

Every key, NV index and hierarchy has an *authorization value*, a password in effect, and may also have an *authorization policy*. A command that uses one must prove the caller satisfies it, in one of three ways.

### Passwords

The password is sent in the command, in the clear (the handle for this is `TPM_RS_PW`, `0x40000009`). Simple, and adequate when the path to the TPM is trusted.

### HMAC sessions

The caller and the TPM each contribute a fresh random *nonce*. The caller then proves it knows the password by sending an HMAC of the command, keyed with a session key derived from the password and those nonces. The TPM computes the same HMAC and compares. The password itself never crosses the bus, and since the nonces change on every command, a recorded HMAC can't be replayed.

The same session can encrypt the first parameter of a command and of its response, so a secret sent to or returned by the TPM isn't readable on the wire. Starting the session *salted*, with a random value encrypted to a TPM key the caller trusts, keeps the session key secret even from someone who saw every byte exchanged. This is exactly the protection whose absence makes discrete-TPM bus sniffing work.

### Policies

A policy is a set of conditions the TPM checks itself. The caller starts a *policy session* and runs policy commands, each of which checks one condition and, if it holds, folds that condition into the session's *policy digest*, just as a PCR is extended:

```text
policyDigest = H(policyDigest || TPM_CC_PolicyXxx || parameters)
```

At the end, the TPM compares the session's digest with the digest stored in the key. They match only if exactly the same conditions were satisfied, in the same order. Conditions include:

- `TPM2_PolicyPCR`: the selected PCRs currently hold specific values.
- `TPM2_PolicySecret`: the caller knows the password of *another* object, such as a hierarchy.
- `TPM2_PolicySigned`: someone holding a given signing key approved this use.
- `TPM2_PolicyCounterTimer`, `TPM2_PolicyNV`: the TPM's clock or an NV value meets a condition.
- `TPM2_PolicyOR`: any one of several branches is satisfied.
- `TPM2_PolicyAuthorize`: the current policy was signed by a given key. This lets a policy be updated later without recreating the key, which matters when the PCRs it names change with every firmware update.

The well-known endorsement key policy is a single `PolicySecret` naming the endorsement hierarchy. Starting from a zero digest, it works out to `837197674484b3f81a90cc8d46a5d724fd52d76e06520b64f2a1da1b331469aa`, and that exact value appears in the EK of nearly every TPM in existence.

### Guessing is rate-limited

A four-digit PIN has 10,000 possibilities, so what stops an attacker from simply trying them all? The TPM does. It counts authorization failures, and after a configured number of them it enters *lockout* and refuses all password-protected operations, forgetting one failure per recovery interval. Because the counter is inside the TPM, rebooting or copying the disk doesn't reset it. This dictionary-attack protection is what makes short PINs reasonable.

## PCRs: a record that can only grow

A PC Client TPM has 24 PCRs in each *bank*, one bank per hash algorithm (SHA-1, SHA-256 and so on). They start at zero when the platform resets. After that, the only way to change one is to extend it:

```text
PCR[new] = H( PCR[old] || measurement )
```

where `measurement` is itself a hash, of a bootloader for example, and `||` means concatenation. The TPM stores the result and forgets the input.

### A worked example

Say firmware measures a bootloader, then the bootloader measures a kernel. In SHA-256, with stand-in strings for the two programs:

```text Extending one SHA-256 PCR twice
PCR before boot  0000000000000000000000000000000000000000000000000000000000000000

measure  "bootloader v1.0"  -> 168629ebe13fbcd455ea0dcb8f8b021be974ca941a4b86649f2d1fcc33e47e88
PCR = H(PCR || that)        -> 53e68fad85531e1a7ba05e914644e965f4ec2665db9c6df8f200607e11d50f60

measure  "kernel 6.12"      -> 1c0742ea173c9e325ef09df58acf16b22fe5f1c190291a7b7113435ce7ef15a1
PCR = H(PCR || that)        -> b81f03368ec5fe9c17ee1244159b492f8b0d92659827959ea4ae1176833854c8
```

Three properties fall out of this:

- **The final value depends on every measurement.** Change the bootloader to `"bootloader v1.1"` and the PCR ends at `51a777f7…`, not `b81f0336…`.
- **It depends on their order.** Measure the kernel first and the bootloader second, and the PCR ends at `676ddaf9…`.
- **It can't be steered.** Producing `b81f0336…` from any other starting point would mean finding an input to SHA-256 that hashes to a chosen value, which nobody can do. Malware that runs after the bootloader can extend the PCR, but only away from the good value, never back to it.

That last property is the reason PCRs are trustworthy. A program can't erase the fact that it was measured, and it can't hide what came before it.

### Who can reset what

On a PC, PCRs 0 to 15 are reset only when the whole platform resets, and the boot measures into them from the start. PCRs 16 and 23 are for debugging and applications and can be reset by software. PCRs 17 to 22 belong to the dynamic launch: they read as all ones after a normal boot, and only a dynamic launch, from a high locality, resets them to zero. PCR 17 can only be reset from locality 4, so a value in it that starts from zero proves the CPU itself performed a dynamic launch.

Sleep needs care. Before an S3 suspend the OS sends `TPM2_Shutdown(TPM_SU_STATE)` so the TPM saves its PCRs, and on resume firmware sends `TPM2_Startup(TPM_SU_STATE)` to restore them. Get that wrong and the PCRs come back empty, ready for anyone to extend with a fake history: that is the attack in [A Bad Dream][bad-dream].

### Measured boot is not Secure Boot

The two are often confused, and they do opposite things.

- **Secure Boot enforces.** Firmware checks each boot image's signature against a database of trusted keys and refuses to run anything that fails. It stops untrusted code but leaves no record.
- **Measured boot records.** Each stage hashes the next and extends a PCR *before* running it. It stops nothing; it leaves an unforgeable record of what ran.

They complement each other. Secure Boot keeps unsigned code off the machine, and measured boot lets someone else check, after the fact, that it really did, including whether Secure Boot was even turned on (that state is measured into PCR 7).

### What each PCR holds

The [PC Client Platform Firmware Profile][pc-firmware-profile] assigns each PCR a role, so that a change in one component shows up in a predictable place:

| PCR | Holds |
| --- | --- |
| 0 | Firmware code: the root of trust and the firmware itself |
| 1 | Firmware configuration |
| 2 | Code of option ROMs and UEFI drivers from add-in devices |
| 3 | Their configuration |
| 4 | Boot manager and boot applications: the code that loads the OS |
| 5 | Boot configuration, such as the GPT partition table |
| 6 | Manufacturer-specific events, such as resume from sleep |
| 7 | Secure Boot policy: its state and the certificates that authorized each image |
| 8–15 | The operating system's own measurements |
| 16, 23 | Debug and application use |
| 17–22 | Dynamic launch |

[The TPM initialization article](/uefi-tpm-initialization#what-the-pcr-indices-typically-measure) traces what EDK II firmware puts in each one.

## The event log tells the story

A PCR is one hash, and on its own it says nothing a person can read. `b81f0336…` doesn't tell you a bootloader and a kernel ran. So every time firmware extends a PCR, it also appends an entry to an *event log* in ordinary memory: which PCR, what kind of event, the digest that was extended, and a description, such as a file path or a variable name.

![PCR accumulation and the external event log: firmware hashes selected bytes, extends the digest into a TPM PCR, and separately logs the digest with a description. A consumer replays the recorded digests and compares the result with the TPM state.](/images/blog/tpm-pcr-event-log.svg)

The log is not protected: anything with access to memory could edit it. It doesn't need to be. Anyone who reads it can *replay* it, starting from zero and extending each logged digest in order, and compare the result with the real PCR. If a single entry was changed, added, removed or reordered, the replayed value won't match. The TPM protects one hash; that hash protects the whole log.

Replay proves the log is *complete and unedited*. It doesn't prove that what the log describes is *good*. That is a separate decision, made by comparing each entry with known-good values. Keep the two apart: they are the two halves of every attestation.

## Sealing: secrets that wait for the right state

PCRs also let the TPM keep a secret until the machine is in a known state. *Sealing* creates a small data object under a storage key, containing the secret, with a policy that names PCR values:

1. Software computes the policy digest for "PCRs 7 and 11 hold these values" (a `TPM2_PolicyPCR` digest) and creates the object with `TPM2_Create`, passing the secret and that digest.
2. On a later boot, software starts a policy session and calls `TPM2_PolicyPCR`. The TPM reads its *current* PCRs and folds them into the session.
3. `TPM2_Unseal` returns the secret only if the session's digest matches the object's. If anything measured into those PCRs changed, the TPM refuses.

This is how BitLocker unlocks a disk without a password: the volume key is sealed to the boot chain, and a modified bootloader can't get it back. [The BitLocker article](/bitlocker-tpm-boot-chain) traces that path in detail. Sealing is local. Attestation answers the same question, "is this machine in a good state?", for someone on the other end of a network.

## Attestation: proving it to someone else

Suppose a company's VPN server wants to admit only laptops that booted their genuine firmware, with Secure Boot on and the expected bootloader. It can't look at the laptop. It can only send messages to software on it, and that software could be compromised and lie. Attestation gives the server evidence a compromised OS can't forge.

To trust that evidence, the server (the *verifier*) needs four answers:

1. **Is this a real TPM?** Software could pretend to be a TPM and sign anything.
2. **Was this report produced by the TPM?** And not by software holding a key it shouldn't.
3. **Is it fresh?** A report from yesterday's clean boot proves nothing about today.
4. **Is what it reports acceptable?** The PCRs and log describe what ran; is that what should have run?

The IETF's [RATS architecture][rats] gives the parties names. The laptop is the *attester*. The *verifier* checks the evidence and produces a result. The *relying party*, here the VPN server, acts on that result. The TPM manufacturer is an *endorser*, vouching for its hardware, and whoever publishes known-good hashes, such as the OS vendor, is a *reference value provider*. In small setups, one server does all of it.

![Remote attestation in two phases. Enrollment, once: the attester sends its EK certificate and the public area of a new AK; the verifier checks the certificate and AK attributes, encrypts a secret with MakeCredential, and the TPM decrypts it with ActivateCredential only if that AK is loaded in it. Attestation, every time: the verifier sends a nonce; the TPM signs a quote over the PCRs and nonce with the AK; the attester returns the quote with the event log; the verifier checks the signature, the nonce, the PCR digest, replays the log and appraises it.](/images/blog/tpm-attestation-flow.svg)

### The endorsement key: a TPM's identity

Every TPM can produce an *endorsement key* (EK): a primary key in the endorsement hierarchy, derived from the Endorsement Primary Seed with a standard template from the [TCG EK Credential Profile][ek-profile]. Since that seed never changes, the EK is the same for the life of the TPM.

At the factory, the manufacturer reads the EK's public key and signs a certificate for it, the *EK certificate*, stored in the TPM's NV memory: the RSA 2048 one at index `0x01C00002`, the ECC P-256 one at `0x01C0000A`. The certificate chains to the manufacturer's certificate authority. Some firmware TPMs don't store one; Windows fetches theirs from the vendor's online service instead.

A valid EK certificate answers question 1: this public key belongs to a TPM that Infineon, Nuvoton, Intel or AMD made. That much is straightforward. The obvious next step, having the EK sign the PCRs, is deliberately impossible.

### Why the EK can't sign

The EK template sets `restricted` and `decrypt`, and *not* `sign`. It can't sign anything at all. The reason is privacy. The EK is unique to one TPM and never changes, so if it signed reports, every service that ever received one could recognize the same machine, and link its activity, forever. Instead, a TPM signs with separate keys, and the EK is used only to prove, once, that such a key lives in a real TPM.

### The attestation key

The *attestation key* (AK, called AIK in TPM 1.2) does the signing. It's an ordinary key the TPM creates, with these attributes:

```text
fixedTPM | fixedParent | sensitiveDataOrigin | userWithAuth | restricted | sign
```

Each one matters to the verifier. `fixedTPM` and `fixedParent` mean the private key can never leave this TPM. `sensitiveDataOrigin` means the TPM generated it, so nobody else knows it. `restricted` means it signs only what the TPM itself produced, so it can't be used to sign a fake report.

A machine can have any number of AKs, one per service if it likes, so they don't share the EK's privacy problem. But that raises question 1 again, for the AK: the verifier has the AK's public area, which *claims* those attributes, but how does it know this key is really inside the TPM whose EK certificate it just checked? The EK can't sign a statement saying so.

### Credential activation: linking the AK to the EK

The answer is a challenge that only the right TPM can answer: the verifier encrypts a secret so that it can be decrypted only by the TPM holding the EK, and only if that TPM also holds the AK.

1. **The attester sends** its EK certificate and the AK's public area.
2. **The verifier checks** the EK certificate's chain to a manufacturer. It reads the AK's attributes from its public area and checks that they are the ones above. Then it computes the AK's Name, the hash of that public area.
3. **The verifier runs `MakeCredential`**, which is software and needs no TPM:
   - pick a random secret, and a random *seed*;
   - encrypt the seed to the EK's public key (RSA-OAEP with the label `IDENTITY`, or an ECDH exchange for an ECC EK);
   - derive an AES key from the seed *and the AK's Name*, and an HMAC key from the seed;
   - encrypt the secret with the AES key, and HMAC the result together with the AK's Name.
4. **The attester calls `TPM2_ActivateCredential`**, naming the loaded AK and the EK. The TPM decrypts the seed with the EK's private key, then derives the keys again using the Name *of the AK it actually holds*. If that Name differs from the one the verifier used, by even one attribute bit, the HMAC check fails and the TPM returns nothing.
5. **The attester sends the secret back**, and the verifier compares.

Getting the secret back proves that the TPM holding the EK's private key also holds a key with exactly that Name, so exactly those attributes, at the same time. Since the AK is `fixedTPM`, it can never have been anywhere else. The verifier records the AK's public key as belonging to this TPM, and from then on trusts its signatures. In a classic *Privacy CA* setup, the secret instead decrypts a certificate for the AK, which the attester can show other services without revealing its EK.

This is done once per AK, at enrollment. Everything after it is cheap.

### The quote

To attest, the verifier sends a fresh random *nonce*. The attester's software asks the TPM for a *quote*:

```c
TPM2_Quote(signHandle     = AK,
           qualifyingData = nonce,
           inScheme       = { RSASSA, SHA-256 },
           PCRselect      = { sha256: 0, 1, 2, 3, 4, 5, 6, 7 });
```

The TPM reads the selected PCRs itself, builds a `TPMS_ATTEST` structure and signs it with the AK:

| Field | Size | Contents |
| --- | --- | --- |
| `magic` | 4 bytes | `0xFF544347`: proof the TPM generated the structure |
| `type` | 2 bytes | `0x8018`, `TPM_ST_ATTEST_QUOTE` |
| `qualifiedSigner` | variable | The AK's qualified name |
| `extraData` | variable | The verifier's nonce, copied as given |
| `clockInfo` | 17 bytes | TPM clock (8), reset count (4), restart count (4), a "clock never went backwards" flag (1) |
| `firmwareVersion` | 8 bytes | The TPM's own firmware version |
| `pcrSelect` | variable | Which banks and PCRs were read |
| `pcrDigest` | variable | Hash of the selected PCR values, concatenated in order |

Note what is *not* in it: the PCR values themselves, and the log. The quote carries only their hash. The attester sends the PCR values and the event log alongside, and the signed `pcrDigest` proves they're the ones the TPM read.

Because the AK is restricted, only the TPM can produce a signed message that starts with `0xFF544347`. Software holding the AK can make the TPM sign *some* quote, but only over the PCRs as they actually are.

### Checking a quote

The verifier's checks map directly onto the four questions. In outline:

```python Verifying a quote, in outline
import hashlib
import struct

TPM_GENERATED_VALUE = 0xFF544347
TPM_ST_ATTEST_QUOTE = 0x8018


def verify(ak_public, quote, signature, pcrs, event_log, nonce, policy):
    # 2. The TPM produced this: the enrolled AK signed it, and it is a quote.
    ak_public.verify(signature, quote)
    magic, kind = struct.unpack(">IH", quote[:6])
    assert magic == TPM_GENERATED_VALUE and kind == TPM_ST_ATTEST_QUOTE

    attest = parse_tpms_attest(quote)

    # 3. It answers this challenge, not an earlier one.
    assert attest.extra_data == nonce

    # The PCR values sent alongside are the ones the TPM read.
    selected = b"".join(pcrs[index] for index in attest.pcr_selection)
    assert hashlib.sha256(selected).digest() == attest.pcr_digest

    # The log is complete and unedited: replaying it gives those PCRs.
    # (PCR 0 can start at 00…03 rather than zero, from a locality-3 startup.)
    replayed = {index: bytes(32) for index in attest.pcr_selection}
    for event in event_log:
        if event.pcr in replayed and event.extends_pcr:
            replayed[event.pcr] = hashlib.sha256(replayed[event.pcr] + event.sha256).digest()
    assert all(replayed[index] == pcrs[index] for index in attest.pcr_selection)

    # 4. What the log describes is acceptable.
    return policy.appraise(event_log)
```

Question 1 was answered at enrollment, when the AK was tied to a certified EK. The last step is where the real policy lives, and it's the hardest part to get right. A verifier might require Secure Boot enabled in PCR 7, signed by the expected certificates; a boot manager whose hash is on an approved list in PCR 4; no debug mode; a revocation list recent enough. A verifier that compares whole PCR values against one "golden" value breaks with every firmware update. Appraising the individual log events is more work but survives updates.

Two more fields are worth watching. `resetCount` goes up on every reboot and `restartCount` on every resume, so a verifier that quotes the same machine repeatedly can tell whether it restarted in between. `firmwareVersion` lets it reject TPMs with known flaws. Because those three values can link reports from the same TPM, the TPM obfuscates them when the signing key is in neither the endorsement nor the platform hierarchy. An AK created under the EK, as below, is in the endorsement hierarchy.

### End to end with tpm2-tools

On Linux, the whole exchange fits in a few commands. The verifier's side needs no TPM at all:

```bash Enrollment and one attestation with tpm2-tools
# Attester: derive the EK, and create an AK under it.
tpm2_createek -c ek.ctx -G rsa -u ek.pub
tpm2_readpublic -c ek.ctx -o ek.pem -f pem
tpm2_createak -C ek.ctx -c ak.ctx -G rsa -g sha256 -s rsassa -u ak.pub -n ak.name

# Verifier: encrypt a secret to that EK and that AK's Name. No TPM needed.
openssl rand 32 > secret.bin
tpm2_makecredential -T none -u ek.pem -G rsa -s secret.bin \
    -n "$(xxd -p -c 256 ak.name)" -o credential.out

# Attester: the EK's policy is PolicySecret(endorsement), so satisfy it, then activate.
tpm2_startauthsession --policy-session -S session.ctx
tpm2_policysecret -S session.ctx -c e
tpm2_activatecredential -c ak.ctx -C ek.ctx -i credential.out -o recovered.bin \
    -P "session:session.ctx"
tpm2_flushcontext session.ctx
cmp secret.bin recovered.bin && echo "AK enrolled"

# Verifier: a fresh challenge.
NONCE=$(openssl rand -hex 20)

# Attester: quote PCRs 0 to 7, and collect the firmware's event log.
tpm2_quote -c ak.ctx -l sha256:0,1,2,3,4,5,6,7 -q "$NONCE" \
    -m quote.msg -s quote.sig -o quote.pcrs -g sha256
cp /sys/kernel/security/tpm0/binary_bios_measurements eventlog.bin

# Verifier: check the signature, the nonce and the PCR digest.
tpm2_checkquote -u ak.pub -m quote.msg -s quote.sig -f quote.pcrs -g sha256 -q "$NONCE"
tpm2_eventlog eventlog.bin
```

`tpm2_checkquote` covers the signature, magic, type, nonce and PCR digest. `tpm2_eventlog` decodes the log and prints the PCR values it replays to, which should match `quote.pcrs`. The appraisal is up to you. A production deployment would also check the EK certificate, which `tpm2_getekcertificate` and `tpm2_nvread` retrieve, against the manufacturer's CA, and keep the AK's public key after enrollment rather than trusting whatever the attester sends next time.

## What attestation proves, and what it doesn't

A verified quote proves something precise: *a genuine TPM, holding this enrolled AK, had these PCR values when it answered this nonce, and this log is a faithful account of how they got there.* It's worth knowing the edges of that statement.

**It shows what was measured at load time, not what is running now.** A PCR records a program's hash before it ran. If that program is later exploited in memory, nothing is extended and the PCRs still look clean. Runtime measurement such as Linux's IMA, which extends PCR 10 with every file it is configured to measure, narrows the gap without closing it.

**It's only as good as the root of trust.** The TPM records what firmware tells it. Firmware that lies about its own measurements, or a root of trust that isn't verified by the hardware, produces perfect quotes for a compromised machine.

**A good measurement isn't a safe program.** Attestation can prove a machine booted bootloader version X. If version X has a known vulnerability, the proof is accurate and the machine is still exposed. Policy must reject vulnerable versions, which is why revocation lists matter.

**It proves *a* TPM, not *your* TPM.** An attacker who controls a laptop could forward the verifier's nonce to a different, clean machine with a genuine TPM and relay its quote back: the *cuckoo attack* described by [Parno][cuckoo]. Enrollment is the defence. If the verifier ties the EK or AK to a specific device when it's issued, through inventory or a certificate provisioned at that time, a quote from any other TPM is rejected.

**The PCRs can be reset behind the platform's back.** On a discrete TPM, [driving the chip's reset line][oslo] resets its PCRs without resetting the CPU, after which software can replay a clean boot's measurements. Firmware that mishandles sleep, as in A Bad Dream, does the same. Dynamic launch and correctly handled S3 resume narrow these windows.

**The TPM can have bugs too.** [TPM-Fail][tpm-fail] recovered ECDSA keys from Intel's fTPM and an STMicroelectronics chip through signing-time differences. [ROCA][roca] found Infineon TPMs generating RSA keys whose private keys could be computed from the public ones. Both were fixed by firmware updates, and both are reasons a verifier should check `firmwareVersion`.

**Privacy needs care.** Every AK is tied to the EK at enrollment, so whoever enrolls it can link the two. A Privacy CA keeps that knowledge in one place. *Direct Anonymous Attestation* (ECDAA in TPM 2.0) goes further: the TPM proves it holds a credential from a trusted issuer without revealing which TPM it is.

None of these is a reason not to use attestation. They're the reason an attestation policy says more than "the signature verified".

## Attestation in practice

The same protocol runs underneath most of the systems that use the word:

- **Windows** keeps each boot's TCG event log in `C:\Windows\Logs\MeasuredBoot`. Device Health Attestation, which Intune compliance policies use, appraises those logs with a TPM quote, [Microsoft Azure Attestation][azure-attestation] offers the same appraisal as a service, and Windows Hello for Business can attest that a user's key was generated inside a TPM.
- **Linux** has [Keylime][keylime], an open-source attestation framework: an agent on each machine quotes its TPM, a verifier checks measured-boot and IMA logs against a policy, and a registrar handles enrollment. systemd measures Unified Kernel Images into PCR 11, giving the OS's own boot stages predictable values.
- **Clouds** give each VM a vTPM. Google Shielded VM, Azure Trusted Launch and AWS NitroTPM measure the VM's boot into it, and the provider's monitoring or the customer's own verifier checks the quotes: the same quote-and-replay flow, with a hypervisor as the root of trust.

The details differ: who runs the verifier, which PCRs it checks, what the policy accepts. The skeleton is always the one above: an EK certificate proves the hardware, credential activation ties an AK to it, a quote over a fresh nonce reports the PCRs, and the event log explains them.

## References

- [Trusted Platform Module 2.0 Library specification][library-spec], particularly [Part 1: Architecture][library-part1] and [Part 3: Commands][tpm-commands]
- [TCG EK Credential Profile for TPM Family 2.0][ek-profile]
- [TCG PC Client Platform TPM Profile][ptp-spec] and [PC Client Platform Firmware Profile][pc-firmware-profile]
- [RFC 9334: Remote ATtestation procedureS (RATS) Architecture][rats]
- [tpm2-tools][tpm2-tools]
- [Linux kernel: TPM security][linux-tpm-security]
- [Microsoft: TPM fundamentals][windows-tpm-fundamentals]

[library-spec]: https://trustedcomputinggroup.org/resource/tpm-library-specification/
[library-part1]: https://medias.cdn.trustedcomputinggroup.org/production/Trusted_Platform_Module_2_0_Library_Part_1_Architecture_Version_185_pub_2a3392dc49.pdf
[tpm-commands]: https://medias.cdn.trustedcomputinggroup.org/production/Trusted_Platform_Module_2_0_Library_Part_3_Commands_Version_185_pub_87a5c10230.pdf
[ek-profile]: https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile/
[ptp-spec]: https://trustedcomputinggroup.org/resource/pc-client-platform-tpm-profile-ptp-specification/
[pc-firmware-profile]: https://medias.cdn.trustedcomputinggroup.org/production/TCG_PC_Client_Platform_Firmware_Profile_Version_1_06_Revision_52_pub_3_1184065dfc.pdf
[rats]: https://www.rfc-editor.org/rfc/rfc9334
[tpm2-tools]: https://github.com/tpm2-software/tpm2-tools
[linux-tpm-security]: https://docs.kernel.org/security/tpm/tpm-security.html
[windows-tpm-fundamentals]: https://learn.microsoft.com/en-us/windows/security/hardware-security/tpm/tpm-fundamentals
[bitlocker-sniffing]: https://pulsesecurity.co.nz/articles/TPM-sniffing
[faultpm]: https://arxiv.org/abs/2304.14717
[bad-dream]: https://www.usenix.org/conference/usenixsecurity18/presentation/han
[cuckoo]: https://www.usenix.org/legacy/event/hotsec08/tech/full_papers/parno/parno.pdf
[oslo]: https://www.usenix.org/conference/16th-usenix-security-symposium/oslo-improving-security-trusted-computing
[tpm-fail]: https://tpm.fail/
[roca]: https://crocs.fi.muni.cz/public/papers/rsa_ccs17
[azure-attestation]: https://learn.microsoft.com/en-us/azure/attestation/tpm-attestation-concepts
[keylime]: https://keylime.dev/

