DEV Community

MachineHunter
MachineHunter

Posted on • Updated on

TPM2_GetRandom from UEFI

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). 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. 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 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 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
Enter fullscreen mode Exit fullscreen mode

Then, you can start using Tcg2Protocol like this (I will put the whole source code at the end).

#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;
}
Enter fullscreen mode Exit fullscreen mode

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

Writing the code

I recommend you to start with defining the structure just like the table above.

#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()
Enter fullscreen mode Exit fullscreen mode

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.

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);
Enter fullscreen mode Exit fullscreen mode

"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.

// Table 11 - TPM_CC Constants (Numeric Order)
typedef UINT32 TPM_CC;
Enter fullscreen mode Exit fullscreen mode

Finally, send command and parse response like this.

// 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");
Enter fullscreen mode Exit fullscreen mode

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

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'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.

$ tpm2_rc_decode 725
tpm:parameter(2):structure is the wrong size
Enter fullscreen mode Exit fullscreen mode

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

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
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

#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;
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)