Build1 publisher2 min readPublished
Getting 32 random bytes out of a TPM takes a 12-byte hand-marshalled command
A Rust walkthrough drives TPM 2.0 straight through Windows TPM Base Services, leaving the caller to build the big-endian header, size the reply buffer before the chip answers, and close the context by hand.
The Engineer · Build desk

What happened
- A dev.to walkthrough drives TPM 2.0 from Rust through Windows TPM Base Services, covering the whole path from creating a TBS context to parsing the bytes that come back.
- The author picked TPM2_GetRandom for the demonstration because it does not require a session and has only a small number of parameters.
- The declared dependency is a single crate: windows-sys 0.61, with the Win32_System_TpmBaseServices feature turned on.
- TBS_COMMAND_LOCALITY_ZERO is the only Locality value Tbsip_Submit_Command currently supports, so the code passes it directly as a fixed argument.
- The author notes that TPM2_GetRandom can return fewer bytes than asked for, because the TPM returns only what it can generate in a single operation.
Compiled by The EngineerSomething wrong?How this is made
Why it matters
- cost Skipping the vendor SDK means writing a marshaller and a parser for every command you need, so the engineering bill grows with the command set and not with the transport code you wrote once.
- decision A caller has to decide up front whether a short entropy reply is retried or treated as a failure, because a reply shorter than the request is a correct TPM response.
- exposure Anything long-lived built from this shape leaks TBS handles on the error path, since a panic in create, submit or parse skips the close at the bottom of main.
- constraint Setting the TPM 2.0 flag as a raw bitfield literal and casting the v2 params struct through the older pointer type ties the code to the struct layout that this windows-sys release exposes.
Twelve bytes go out on the wire. The header is always 10: tag, then commandSize, then commandCode [4]. TPM2_GetRandom carries one UINT16 parameter, so the parameter block is 2 bytes [5]. commandSize counts the header itself, so both the field and the buffer say 12 [6][7].
Each field is appended with `to_be_bytes()`, because integers in TPM commands and responses are big-endian [8]. The tag used here is TPM_ST_NO_SESSIONS, 0x8001, and the constant for the sessioned path is 0x8002 [9]. The command code is 0x0000_017B [10].
Creating the context is the part that reads like C, because it is C. The sample fills TBS_CONTEXT_PARAMS2_0_0 with `_bitfield: 4` and a comment noting that bit 2 is includeTpm20, and sets version to TPM_VERSION_20 [13][12]. It then casts a pointer to the v2 struct into `*const TBS_CONTEXT_PARAMS` and hands that to `Tbsi_Context_Create` inside an unsafe block [14]. Failure arrives as a u32 status compared against TBS_SUCCESS [15].
The response buffer is sized before the chip has answered anything. `main` computes max_response_param_size as TPM2B_SIZE plus bytes_requested, so 2 plus 32 [19]. The submit function allocates the header on top of that, which is 44 bytes for a 32-byte request [20][18]. That figure is a function of the request, not a constant, since the parameter budget tracks bytes_requested [19]. response_len goes in as the length of the buffer and the actual response size is written back into the same variable [18]. Priority is TBS_COMMAND_PRIORITY_NORMAL, chosen because only one command is submitted [17].
What transfers from this walkthrough is the transport: create a context, marshal, submit, parse, close [2][1]. The marshalling does not transfer. GetRandom was picked because it needs no session and has few parameters [3], and a command that uses a session starts with the other tag [9] and adds body this code never builds. If you need one or two sessionless commands and your codebase already carries unsafe FFI, hand-rolling against windows-sys is a defensible trade. If you need an attestation quote, the sample gives you the pipe and stops at GetRandom [1].
What to watch
- Whether the author extends the same pattern to a command that uses a session, which is the path the 0x8002 tag constant in the sample points at.
- Whether windows-sys keeps the TBS_CONTEXT_PARAMS2 layout stable past 0.61, since the includeTpm20 flag is written as a bare bitfield value.
- Whether TBS ever supports a Locality other than zero, which would turn a fixed argument into a parameter.