Talking to a TPM 2.0: Startup, Provisioning, and Reading Raw Commands

By Davis Muro7 minutes read


TPM 2.0 is often treated as a magic switch you “turn on in BIOS.” In reality, it’s just a device on a bus that speaks a very concrete binary protocol.

If you’ve ever wondered:

this post walks through those pieces using real command dumps.

1. Startup: _TPM_Init() vs TPM2_Startup()

Before you can send “normal” commands (like GetRandom or CreatePrimary), the TPM has to go through a startup sequence.

There are two distinct layers:

On typical PCs, firmware (UEFI/BIOS) issues TPM2_Startup for you. On dev boards, embedded systems, or some test setups, you may be responsible for sending it yourself. If you skip it, the TPM won’t behave like a normal device.

1.1 What Happens If You Skip Startup

If you try to send a command before TPM2_Startup, you’ll run into error codes like:

Here’s an example using TPM2_GetRandom before startup. The request is:

==============================
            REQUEST
==============================

TPM Command Tag (hex): 0x8001
- TPM_ST_NO_SESSIONS: command has no session area.

TPM Command Length (hex): 0x0000000c
- 0x0c = 12 bytes total command size.

TPM Command Code (hex): 0x0000017b
- TPM_CC_GetRandom.

TPM Command (hex), annotated:

Offset  Bytes          Meaning
------  -----          -------
00      80 01          tag = 0x8001 (command, no sessions)
02      00 00 00 0c    length = 0x0000000c (12 bytes)
06      00 00 01 7b    commandCode = 0x0000017b (GetRandom)
0a      00 10          bytesRequested = 0x0010 (16 bytes)

The TPM’s response:

==============================
            RESPONSE
==============================

TPM Response Tag (hex): 0x8001
- Response without sessions.

TPM Response Size (hex): 0x0000000a
- 0x0a = 10 bytes total response size (minimal error response).

TPM Error Response core: 00 00 01 00
- responseCode = 0x00000100 = TPM_RC_INITIALIZE.

Offset  Bytes          Meaning
------  -----          -------
00      80 01          tag = 0x8001 (response, no sessions)
02      00 00 00 0a    length = 0x0000000a (10 bytes)
06      00 00 01 00    responseCode = 0x00000100 (TPM_RC_INITIALIZE)

The takeaway: until _TPM_Init() has occurred and a successful TPM2_Startup(SU_CLEAR or SU_STATE) has been processed, the TPM refuses to act like a fully initialized device.


2. “Is This TPM Manufactured?” — Provisioning and Hierarchies

With “normal” PCs, the TPM you see is usually already provisioned by the platform vendor. With raw TPM modules or engineering samples, that’s not always true.

When people say a TPM is “manufactured” or “provisioned,” they usually mean:

2.1 Commands Involved in Provisioning

Depending on the vendor, provisioning flows may involve:

On many motherboards or laptops, UEFI/BIOS runs all of this once, then exposes a “Ready to use” TPM to the OS.

2.2 How to Tell If a TPM Is Provisioned

You can probe TPM state with capability and property queries:

A very practical test is: try to create a primary in the storage or endorsement hierarchy. If the TPM is correctly manufactured, a simple TPM2_CreatePrimary under the storage hierarchy (0x40000001) should succeed.

3. Handles and Object Creation: CreatePrimary vs Create

Handles are where a lot of people get confused. They look like arbitrary 32‑bit numbers, but the ranges are meaningful.

3.1 Handle Types (Quick Reference)

For this post, the main ones are:

In typical flows you:

  1. Use a hierarchy handle as the parent when calling TPM2_CreatePrimary.
  2. Get back a transient object handle in the 0x8000_0000 range.
  3. Use that transient handle as parent in TPM2_Create, TPM2_Load, etc.
  4. Optionally turn that transient handle into a persistent one with TPM2_EvictControl.

3.2 TPM2_CreatePrimary: Getting a First Handle

TPM2_CreatePrimary creates a primary object directly under a hierarchy (storage, endorsement, etc.):

a real CreatePrimary command dump (simplified and annotated).

3.2.1 Example: Raw CreatePrimary Command, Success, and Error

Command:

==============================
            REQUEST
==============================

TPM Command Tag (hex): 0x8002
- TPM_ST_SESSIONS: command includes a session area (for auth).

TPM Command Length (hex): 0x0000003f
- 0x3f = 63 bytes total command size.

TPM Command Code (hex): 0x00000131
- TPM_CC_CreatePrimary.

TPM Command (hex):
80 02 00 00 00 3f 00 00 01 31 40 00 00 01 00 00 00 09
40 00 00 09 00 00 00 00 00 00 00 00 1a 00 01 00 0b 00
03 00 72 00 00 00 06 00 80 00 43 00 10 08 00 00 00 00
00 00 00 00 00 00 00 00 00 00 00 00 00

Breaking down just the key fields:

Offset  Bytes              Meaning
------  -----              -------
00      80 02              tag = 0x8002 (command with sessions)
02      00 00 00 3f        length = 0x0000003f (63 bytes)
06      00 00 01 31        commandCode = 0x00000131 (CreatePrimary)

0A      40 00 00 01        parent = 0x40000001 (storage hierarchy)

0E      00 00 00 09        authSize = 9 bytes in auth area

12      40 00 00 09        sessionHandle = 0x40000009
                          (simple auth session handle here)

16      00 00              nonceSize = 0 (no nonce)
18      00                 sessionAttributes = 0
19      00 00              hmacSize = 0 (empty auth)

1B      00 1a              inPublic.size = 0x001a (26 bytes)

1D      00 01              type = TPM_ALG_RSA (0x0001)
1F      00 0b              nameAlg = TPM_ALG_SHA256 (0x000B)

21      00 03 00 72        objectAttributes (bitfield)
25      00 00              authPolicy.size = 0 (no policy)

27      00 06              parameter size / start of RSA params
29      00 80 00 43 00 10 08  ...
                          (RSA keyBits, exponent, scheme, symmetric info)

...     (rest of template / padding)

Successful CreatePrimary Response (Annotated)

==============================
            RESPONSE          
==============================

TPM Response Tag (hex): 0x8002
- TPM_ST_SESSIONS: response includes a session area.

TPM Response Size (hex): 0x000001ea
- 0x1ea = 490 bytes total response size.

TPM Response Code (hex): 0x00000000
- TPM_RC_SUCCESS.

TPM Response (hex):
80 02 00 00 01 ea 00 00 00 00 80 00 00 00 00 00 
01 d3 01 1a 00 01 00 0b 00 03 04 72 00 00 00 06 
00 80 00 43 00 10 08 00 00 00 00 00 01 00 cc f4 
c7 e3 16 f1 63 41 80 93 d2 19 7d 52 5a 90 9e 54 
82 ea 4a 5e 47 52 f7 12 7a 59 18 bf a1 d5 62 1e 
44 ca 2d 40 de 0b 3d 64 e4 51 e6 f0 f5 f6 a4 e1 
a3 6f 22 cf 9e 63 56 4d e2 78 57 21 68 51 d7 aa 
cf 28 59 48 61 6c 63 c1 46 b6 cb a8 14 25 af d8 
23 1b c2 03 62 1c c5 7e 1a ab c3 b4 76 6c 71 fa 
4c 61 54 c6 f6 18 0e 33 65 fa 1c 0c ab fa 20 07 
f8 37 2a e5 15 00 7e 87 95 cf ca f7 75 87 5c c6 
f9 f2 76 27 58 28 2f c7 d7 e7 df 68 93 14 e7 4d 
fb 8e 4f a2 d9 da c8 d3 90 69 f3 84 8c c2 ff 85 
c1 23 cb c6 61 60 a5 7e 82 9c 83 5b 1e 05 1f 77 
68 c8 32 6d 91 e5 da 02 52 de 43 e3 2a d7 52 d6 
da f4 c6 5d 87 c1 ba 2f 74 c8 87 fe 09 ca ba 40 
e9 f8 df 96 17 d0 a7 7a dc 13 4f 08 f1 a7 30 c8 
3f d0 04 0d be 9a a9 be ad 4d 3f 6e 33 2b 98 b4 
e9 3d 08 1a ea 20 ca d0 7e d3 66 7c e2 ef 00 37 
00 00 00 00 00 20 e3 b0 c4 42 98 fc 1c 14 9a fb 
f4 c8 99 6f b9 24 27 ae 41 e4 64 9b 93 4c a4 95 
99 1b 78 52 b8 55 01 00 10 00 04 40 00 00 01 00 
04 40 00 00 01 00 00 00 20 5d a0 41 ba c0 ee 31 
35 ae bb 0c ad fb a4 97 c6 a1 87 7f ae 83 2d d3 
d1 f8 f7 a8 71 b8 25 e8 54 80 21 40 00 00 01 00 
30 f3 79 37 38 d5 69 6c 1c 36 59 9e ec 09 bf 22 
9e 92 aa 3a 9d 76 59 69 1e 1f ca 41 5d d0 db a3 
6b 6b ed 5e 38 98 6b 19 76 d7 64 6b 47 e9 4c e0 
9f 00 22 00 0b 4a 08 3c 3b 94 77 e9 82 76 05 11 
87 bc f5 3d ef ef af 5d 97 56 bd fa df 6f a9 32 
14 ce 8b 00 85 00 00 01 00 00 

Breaking down the key parts of the successful response:

Offset  Bytes              Meaning
------  -----              -------
00      80 02              tag = 0x8002 (response with sessions)
02      00 00 01 ea        length = 0x000001ea (490 bytes)
06      00 00 00 00        responseCode = 0x00000000 (TPM_RC_SUCCESS)

The next fields are the normal CreatePrimary return parameters:

0A      80 00 00 00        handle = 0x80000000
                          - Transient handle for the new primary object.

0E      00 00              outPublic.size MSB
10      00 01 d3 01 ...    outPublic (TPM2B_PUBLIC)
                          - Public area for the created key:
                            * type = TPM_ALG_RSA (0x0001)
                            * nameAlg = TPM_ALG_SHA256 (0x000B)
                            * objectAttributes = 0x00030472
                            * RSA parameters (key size, exponent, scheme)
                            * public modulus (unique.rsa)

...                        outCreationData (TPM2B_CREATION_DATA)
                          - Creation-time state and locality info.

...                        creationHash (TPM2B_DIGEST)
                          - Hash of the creation data.

...                        creationTicket (TPMT_TK_CREATION)
                          - Ticket proving this object was created by the TPM.

...                        outName (TPM2B_NAME)
                          - The computed name of the new object.

...                        sessionArea
                          - Session response (auth, nonce, attributes, HMAC if used).

The exact byte offsets for outPublic, outCreationData, creationHash, creationTicket, and outName depend on their internal size fields, but the important pattern is:

This is the “happy path” layout you want to see when debugging: header → handle → public template → creation info → sessions.

Error Response for Contrast

==============================
            RESPONSE
==============================

TPM Response Tag (hex): 0x8002
- Response without sessions (minimal error response).

TPM Response Size (hex): 0x0000000a
- 0x0a = 10 bytes total.

TPM Response (hex):
80 01 00 00 00 0a 00 00 01 d5

Offset  Bytes              Meaning
------  -----              -------
00      80 01              tag = 0x8001 (no session area)
02      00 00 00 0a        length = 0x0000000a
06      00 00 01 d5        responseCode = 0x000001d5 (error)

0x000001D5 is a non‑success TPM_RC_* code (the exact symbolic name depends on the spec revision and context). The important contrast is:

3.3 TPM2_Create: Child Objects Under a Parent

Once you have a primary key handle (e.g., 0x80000000), you can call TPM2_Create to create child objects under that parent.

The flow:

  1. TPM2_CreatePrimary under 0x40000001 → returns 0x80000000.
  2. TPM2_Create(parent = 0x80000000, template, inSensitive) → returns outPrivate and outPublic.
  3. TPM2_Load(parent = 0x80000000, outPrivate, outPublic) → returns a new transient handle (e.g., 0x80000001).

High level:

TPM2_Create – Example Request

High‑level description:

==============================
          REQUEST
==============================
TPM Command Tag (hex):    0x8002      ; TPM_ST_SESSIONS
TPM Command Length (hex): 0x0000004A  ; 74 bytes total (example)
TPM Command Code (hex):   0x00000153  ; TPM_CC_Create

TPM Command (hex):
80 02                         ; tag = TPM_ST_SESSIONS
00 00 00 4A                   ; commandSize = 0x4A (74 bytes)
00 00 01 53                   ; commandCode = TPM_CC_Create

80 00 00 00 00 01             ; parentHandle = 0x80000001 (example)

00 09                         ; authAreaSize = 9 bytes
40 00 00 09                   ; sessionHandle = TPM_RS_PW (password session)
00                            ; nonceSize = 0
00                            ; sessionAttributes = 0
00                            ; hmacSize = 0 (no password)

; inSensitive (TPM2B_SENSITIVE_CREATE)
00 00                         ; size of inSensitive (0, no auth/user data)

; inPublic (TPM2B_PUBLIC)
00 1E                         ; size = 30 bytes (example, truncated)
00 01                         ; type = TPM_ALG_RSA
00 10                         ; nameAlg = TPM_ALG_SHA256
00 00                         ; objectAttributes (part1, truncated)
00 72                         ; objectAttributes (part2, truncated, e.g. sign, userWithAuth)
00 00                         ; authPolicy size = 0
00 01 00 01                   ; parameters: keyBits = 2048, exponent = 0x00010001
00 00                         ; unique (RSA modulus) size = 0 (TPM will generate)

; outsideInfo (TPM2B_DATA)
00 00                         ; size = 0

; creationPCR (TPML_PCR_SELECTION)
00 00 00 00                   ; count = 0 (no PCR selection)

TPM Command (binary – truncated, corresponding to hex above):
10000000 00000010 ...
...                           ; omitted: direct binary equivalent of the hex above

TPM2_Create – Example Successful Response

==============================
          RESPONSE
==============================
TPM Response Tag (hex):  0x8002        ; TPM_ST_SESSIONS
TPM Response Size (hex): 0x00000150    ; 336 bytes total (example)
TPM Response Code (hex):0x00000000     ; TPM_RC_SUCCESS

TPM Response (hex):
80 02                         ; tag
00 00 01 50                   ; responseSize
00 00 00 00                   ; responseCode = success

; outPrivate (TPM2B_PRIVATE) – truncated
00 60                         ; size = 0x60 (96 bytes, example)
AA AA AA ... AA               ; private buffer (sensitive, encrypted by TPM)

; outPublic (TPM2B_PUBLIC) – truncated
00 40                         ; size = 0x40 (64 bytes, example)
00 01                         ; type = TPM_ALG_RSA
00 10                         ; nameAlg = TPM_ALG_SHA256
00 00 00 72                   ; objectAttributes (truncated)
00 00                         ; authPolicy size = 0
00 01 00 01                   ; parameters: 2048‑bit, exponent 65537
00 20                         ; unique size = 32 bytes (truncated example)
BB BB BB ... BB               ; beginning of public modulus (truncated)

; creationData (TPM2B_CREATION_DATA) – truncated
00 30                         ; size = 0x30 (48 bytes, example)
CC CC CC ... CC               ; creation data

; creationHash (TPM2B_DIGEST) – truncated
00 20                         ; size = 0x20 (32 bytes, SHA‑256)
DD DD DD ... DD               ; hash of creation data

; creationTicket (TPMT_TK_CREATION) – truncated
80 01                         ; tag = TPM_ST_CREATION
00 00 00 00                   ; hierarchy = TPM_RH_OWNER (example)
00 20                         ; digest size
EE EE EE ... EE               ; ticket digest

TPM Response (binary – truncated, corresponding to hex above):
10000000 00000010 ...
...                           ; omitted: direct binary equivalent of the hex above

4. Putting It All Together

From the TPM’s perspective, a “normal” provisioning flow looks roughly like:

  1. Reset / power on
    • _TPM_Init() is asserted.
  2. Startup
    • Firmware or your code sends TPM2_Startup(SU_CLEAR).
  3. (Optional) Manufacturing / provisioning
    • Vendor or platform runs TPM2_Clear, TPM2_HierarchyChangeAuth, creates EK/SRK, etc.
  4. Your application starts using the TPM
    • Query properties (TPM2_GetCapability).
    • Create a primary key:
      • TPM2_CreatePrimary(parent = 0x40000001).
    • Use that key as a parent for children:
      • TPM2_Create, TPM2_Load, etc.

5. A Concrete Example: Signing Data with a TPM

Everything above can feel abstract until you actually use a TPM for something real.

To make this concrete, I’ve put together a small sample project that walks through a complete, minimal TPM signing flow using TSS:

👉 TPMSign

What the Project Demonstrates

The project shows how to:

  1. Initialize and verify TPM state
    • Assumes the TPM has completed _TPM_Init() and TPM2_Startup.
    • Verifies that a usable storage hierarchy exists.
  2. Create (or reuse) a primary key
    • Uses TPM2_CreatePrimary under the storage hierarchy (0x40000001).
    • Produces a transient handle in the 0x8000_0000 range.
  3. Create a signing key under that primary
    • Uses TPM2_Create + TPM2_Load.
    • The private portion of the key never leaves the TPM.
  4. Sign user-supplied data
    • Hashes the input message
    • Uses TPM2_Sign to generate a signature inside the TPM.
    • Returns a standard RSA signature that can be verified outside the TPM.

At no point does the signing key’s private material leave the TPM — all cryptographic operations are bound to TPM handles, exactly as described earlier in this post.

If you understood the annotated command dumps in this post, the project should feel familiar — it’s the same flow, just applied to a practical task