TPM2_GetRandom from UEFI
Tags: uefi, tpm2, edk2, tutorial
Published: 2022-05-18 18:39:30

Using TPM2.0 from UEFI module is a painstaking task.
TPM offers APIs called TSS (TCG Software Stack) for software to easily communicate with TPM. Also there are tools to interact with TPM via command line (i.e. [tpm2-tools](https://tpm2-tools.readthedocs.io/en/latest/)). However, both runs above kernel so we can't use them from UEFI module.

UEFI access to various devices via "protocol". TPM is also a device, so UEFI provides **Tcg2Protocol** for communicating TPM. To let TPM do something, we need to send "TPM command" to the TPM. Tcg2Protocol has a function called "SubmitCommand" to do this.
> To be more precise, fTPM is not a physical device but you can use Tcg2Protocol to communicate with them just like discrete TPM.

The format of TPM Command is defined in [TCG spec](https://trustedcomputinggroup.org/resource/tpm-library-specification/). TCG spec is consisted of 4 part so make sure to download all of these (Part 3 & 4 has two specs but using only the one which involves code is fine).

Learning about TPM is also hard, since there are only few resources out there. Most popular resource you can use to understand about TPM is maybe [Practical Guide to TPM2.0](https://www.amazon.co.jp/Practical-Guide-TPM-2-0-2015-01-24/dp/B011DAFMK8) which you can download kindle version from amazon for free, but both TCG spec and this book doesn't provides us **sample code**. Therefore, it often takes you long time to write the first working code.

It took me really long time to be able to write code accessing TPM from UEFI module, so I hope this series of article helps someone who is also having trouble.



## Starting with very easy command: TPM2_GetRandom
**I strongly recommend implementing easy TPM command first**.　Implementation difficulty varies greatly between TPM commands, but even with easy TPM command, there are several *pitfalls* that you must be aware of.

I will be using [EDK2](https://github.com/tianocore/edk2) to build UEFI module. Also, make sure the machine you're running this module has TPM2.0 (either fTPM or discrete TPM) and the bios supports TCG2 protocol.


### EDK2 settings to use Tcg2Protocol
Just add this in `.inf` file.
```
[Protocols]
  gEfiTcg2ProtocolGuid
```

Then, you can start using Tcg2Protocol like this (I will put the whole source code at the end).
```c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/Tpm20.h>

EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
	EFI_TCG2_PROTOCOL *Tcg2Protocol;
	SystemTable->BootServices->LocateProtocol(&gEfiTcg2ProtocolGuid, NULL, (VOID**)&Tcg2Protocol);

	EFI_STATUS stats = Tcg2Protocol->SubmitCommand(Tcg2Protocol, CmdBufferSize, (UINT8*)&CmdBuffer, RecvBufferSize, (UINT8*)&RecvBuffer);

	while(1);
	return 0;
}
```


### Looking for TPM2_GetRandom format in TCG spec
You can check the format in Part3 spec. Just Ctrl-F with command name, in this case, TPM2_GetRandom. You will see table like this.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lvuvripunmg4ycdxdh1a.png)
 

### Writing the code
I recommend you to start with defining the structure just like the table above.
```c
#pragma pack(1)                                     
  typedef struct {
    TPM2_COMMAND_HEADER Header;
    UINT16 bytesRequested;
  } TPM2_GET_RANDOM_COMMAND;

  typedef struct {
    TPM2_RESPONSE_HEADER Header;
    TPM2B_DIGEST randomBytes;
  } TPM2_GET_RANDOM_RESPONSE;                       
#pragma pack()
```
Parameters in the header are common to all TPM command so specific struct is defined in Tpm20.h. Tpm20.h is located in `MdePkg/Include/IndustryStandard/Tpm20.h` and I recommend looking this while writing.
Also, **using `#pragma pack(1)` to disable alignment is one of the pitfalls** so don't forget this.

Next, assign the Command parameters as follows.
```c
TPM2_GET_RANDOM_COMMAND CmdBuffer;
UINT32 CmdBufferSize;
TPM2_GET_RANDOM_RESPONSE RecvBuffer;
UINT32 RecvBufferSize;

CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_NO_SESSIONS);
CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_GetRandom);
CmdBuffer.bytesRequested     = SwapBytes16(16);
CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.bytesRequested);
CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);
```
"Session" is some authorization related thing, but we don't need this in this command. We request 16 bytes of random value in this command.
**You can see `SwapBytes` in the code and this is another one of the pitfalls.** We have to send data to TPM in **BIG ENDIAN**.

`SwapBytes` is a EDK2 specific function and we need to specify bits at the end. In the spec's table it says, `TPC_CC_GetRandom` has a type of `TPM_CC` and I found out this is 32bit by searching `TPM_CC` in Tpm20.h. It should be defined like this.
```c
// Table 11 - TPM_CC Constants (Numeric Order)
typedef UINT32 TPM_CC;
```

Finally, send command and parse response like this.
```c
// send
RecvBufferSize = sizeof(RecvBuffer);
EFI_STATUS stats = Tcg2Protocol->SubmitCommand(Tcg2Protocol, CmdBufferSize, (UINT8*)&CmdBuffer, RecvBufferSize, (UINT8*)&RecvBuffer);
if(stats==EFI_SUCCESS)
  Print(L"SubmitCommand Success!\r\n");
else
  Print(L"stats: 0x%x (EFI_DEVICE_ERROR:0x%x, EFI_INVALID_PARAMETER:0x%x, EFI_BUFFER_TOO_SMALL:0x%x)\r\n", stats, EFI_DEVICE_ERROR, EFI_INVALID_PARAMETER, EFI_BUFFER_TOO_SMALL);


// show result
UINT16 res = SwapBytes32(RecvBuffer.Header.responseCode);
Print(L"ResponseCode is %d\r\n", res);

UINT16 i;
UINT16 size = SwapBytes16(RecvBuffer.randomBytes.size);
Print(L"generated value: ");
for(i=0; i<size; i++)
  Print(L"%X", RecvBuffer.randomBytes.buffer[size-i-1]);
Print(L"\r\n");
```
If you don't get `EFI_SUCCESS`, then you might not have TPM2.0 or something is wrong with the TPM (I also had a problem that TPM2_CreatePrimary returns EFI_DEVICE_ERROR on fTPM). Other than that, **all the error information is in the response code**.


### Running the code
If you get screen like this, then it's a success! (response code should be 0 if any command succeeded).
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/tu7ghie50qzip1fv57l0.jpg) 


## Evaluating errors
Mostly, you'll get errors and response code will not be 0. It's hard to identify what is wrong since the only information given is this response code, but you can follow these steps.
1. Decode response code by tpm2_rc_decode to know the abstract information of error
2. Read each command's "Detailed Actions" section in TCG spec Part3 to know exactly where in the process you're stuck at
3. Read the index of subsections in the TCG spec Part3 Chapter5 Command Processing to know what process you have passed and what you haven't

### Decode response code by tpm2_rc_decode to know the abstract information of error
Firstly, you have to parse this response code. The most easy way is to use **[tpm2-tools](https://tpm2-tools.readthedocs.io/en/latest/)'s `tpm2_rc_decode` command**. You can install tpm2-tools by `apt install tpm2-tools`. For example, if the response code was `725` in decimal, you can use this command like this.
```shell
$ tpm2_rc_decode 725
tpm:parameter(2):structure is the wrong size
```
We can know that the second parameter's size is wrong from this message.

### Read each command's "Detailed Actions" section in TCG spec Part3 to know exactly where in the process you're stuck at
If this information is not enough, you can see each command's "Detailed Actions". Taking TPM2_NV_DefineSpace as an example, if you got an error `tpm:handle(1):inconsistent attributes`, you can look at the "Detailed Actions" and find out what condition you didn't pass.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9cn4gb7mk7r3550db6dm.png)

### Read the index of subsections in the TCG spec Part3 Chapter5 Command Processing to know what process you have passed and what you haven't
You might also wonder, what part did you passed and what you didn't. Command Processing order can be found out by looking at index of Chapter5 of TCG spec Part3.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ibwmst0k7d4focp9u9gb.png)
So, if you had parameter related error, it means you have passed all of the above part such as header validation or authorization checks.


## Summary
That's it for this post. Below is the summary.
* You can't use tpm2-tools or TSS from UEFI
* How to setup EDK2 to use Tcg2Protocol
* You can use `Tcg2Protocol->SubmitCommand` to send command to TPM from UEFI
* Looking at TCG spec and Tpm20.h while writing the code is recommended
* You have to send data in Big Endian to TPM
* You have to disable alignment by `#pragma pack(1)`
* How to evaluate error

## Full source code
```c
#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/Tpm20.h>

#pragma pack(1)
	typedef struct {
		TPM2_COMMAND_HEADER Header;
		UINT16 bytesRequested;
	} TPM2_GET_RANDOM_COMMAND;

	typedef struct {
		TPM2_RESPONSE_HEADER Header;
		TPM2B_DIGEST randomBytes;
	} TPM2_GET_RANDOM_RESPONSE;
#pragma pack()

EFI_STATUS EFIAPI UefiMain(IN EFI_HANDLE ImageHandle, IN EFI_SYSTEM_TABLE *SystemTable) {
	EFI_TCG2_PROTOCOL *Tcg2Protocol;
	SystemTable->BootServices->LocateProtocol(&gEfiTcg2ProtocolGuid, NULL, (VOID**)&Tcg2Protocol);

	TPM2_GET_RANDOM_COMMAND CmdBuffer;
	UINT32 CmdBufferSize;
	TPM2_GET_RANDOM_RESPONSE RecvBuffer;
	UINT32 RecvBufferSize;

	CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_NO_SESSIONS);
	CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_GetRandom);
	CmdBuffer.bytesRequested     = SwapBytes16(16);
	CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.bytesRequested);
	CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);


	// send
	RecvBufferSize = sizeof(RecvBuffer);
	EFI_STATUS stats = Tcg2Protocol->SubmitCommand(Tcg2Protocol, CmdBufferSize, (UINT8*)&CmdBuffer, RecvBufferSize, (UINT8*)&RecvBuffer);
	if(stats==EFI_SUCCESS)
		Print(L"SubmitCommand Success!\r\n");
	else
		Print(L"stats: 0x%x (EFI_DEVICE_ERROR:0x%x, EFI_INVALID_PARAMETER:0x%x, EFI_BUFFER_TOO_SMALL:0x%x)\r\n", stats, EFI_DEVICE_ERROR, EFI_INVALID_PARAMETER, EFI_BUFFER_TOO_SMALL);


	// show result
	UINT16 res = SwapBytes32(RecvBuffer.Header.responseCode);
	Print(L"ResponseCode is %d\r\n", res);

	UINT16 i;
	UINT16 size = SwapBytes16(RecvBuffer.randomBytes.size);
	Print(L"generated value: ");
	for(i=0; i<size; i++)
		Print(L"%X", RecvBuffer.randomBytes.buffer[size-i-1]);
	Print(L"\r\n");

	while(1);
	return 0;
}
```