DEV Community

MachineHunter
MachineHunter

Posted on

TPM2_GetCapability from UEFI

In my previous post, I introduced how to submit very easy command TPM2_GetRandom to TPM from UEFI. TPM2_GetCapability is also an easy command, but we need to specify more parameters and those meanings are bit more complex than TPM2_GetRandom. TPM2_GetCapability is a command frequently used to check the current status of your TPM, so it is helpful to have one program already implemented.

parameters of TPM2_GetCapability

Image description
Put simply, we specify what kind of information we want in capability. and request the result in the range of [property:property+propertyCount].

The types of capability we can specify is listed in the command desription.
Image description
In this post, we will use TPM2_GetCapability to list out supported commands by specifying TPM_CAP_COMMANDS as a capability parameter.

Implementation

The whole code looks like this.

#include <Uefi.h>
#include <Library/UefiLib.h>
#include <Protocol/Tcg2Protocol.h>
#include <IndustryStandard/Tpm20.h>

#pragma pack(1)
    typedef struct {
        TPM2_COMMAND_HEADER Header;
        TPM_CAP capability;
        UINT32 property;
        UINT32 propertyCount;
    } TPM2_GET_CAPABILITY_COMMAND;

    typedef struct {
        TPM2_RESPONSE_HEADER Header;
        TPMI_YES_NO moreData;
        TPMS_CAPABILITY_DATA capabilityData;
    } TPM2_GET_CAPABILITY_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_CAPABILITY_COMMAND CmdBuffer;
    UINT32 CmdBufferSize;
    TPM2_GET_CAPABILITY_RESPONSE RecvBuffer;
    UINT32 RecvBufferSize;

    // set command parameters
    CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_NO_SESSIONS);
    CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_GetCapability);
    CmdBuffer.capability         = SwapBytes32(TPM_CAP_COMMANDS);
    CmdBuffer.property           = SwapBytes32(TPM_CAP_FIRST);
    CmdBuffer.propertyCount      = SwapBytes32(MAX_CAP_CC);
    CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.capability) + sizeof(CmdBuffer.property) + sizeof(CmdBuffer.propertyCount);
    CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);


    // send TPM command
    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);


    // parse response
    UINT16 res = SwapBytes32(RecvBuffer.Header.responseCode);
    Print(L"ResponseCode is %d\r\n", res);
    Print(L"moreData: %d\r\n", RecvBuffer.moreData);

    UINT32 i;
    UINT32 count = SwapBytes32(RecvBuffer.capabilityData.data.command.count);
    Print(L"--- %d commands supported ---\r\n", count);

    TPMA_CC* tpmacc = (TPMA_CC*)RecvBuffer.capabilityData.data.command.commandAttributes;
    for(i=0; i<count; i++) {
        union {
            UINT32 tpma_cc_uint32;
            TPMA_CC buf;
        } castbuf;
        castbuf.buf = tpmacc[i];
        UINT32 tpmacc32 = SwapBytes32(castbuf.tpma_cc_uint32);

        if((UINT16)tpmacc32 == TPM_CC_NV_DefineSpace) {
            Print(L"TPM2_NV_DefineSpace is supported: %X\r\n", tpmacc32);
        }
    }

    while(1);
    return 0;
}
Enter fullscreen mode Exit fullscreen mode

command parameter description

Again, we have to send data in BigEndian to TPM so there are lots of SwapBytes. Macros like TPM_CAP_COMMANDS and TPM_CAP_FIRST are searched in the Tpm20.h which is located in MdePkg/Include/IndustryStandard/Tpm20.h. Notice we're requesting MAX_CAP_CC as propertyCount parameter, which we can guess moreData parameter in the response will be 0.

response description

Parsing response is the main part of this article.
We can find out typedef BYTE TPMI_YES_NO; in Tpm20.h and since it says "YES_NO", we can easily tell moreData takes 1 or 0 as a value. However, capabilityData parameter which has a type TPMS_CAPABILITY_DATA is more complex. By looking it up on Tpm20.h, we can find out TPMS_CAPABILITY_DATA structure is as follows.

* [struct] TPMS_CAPABILITY_DATA
  * [UINT32] TPM_CAP capability
  * [union] TPMU_CAPABILITIES
    * [struct] TPML_ALG_PROPERTY algorithms;
      * [UINT32] UINT32 count
      * [struct] TPMS_ALG_PROPERTY algProperties[MAX_CAP_ALGS];
        * [UINT16] TPM_ALG_ID alg
        * [UINT32(bitfield)] TPMA_ALGORITHM algProperties
    * ...
Enter fullscreen mode Exit fullscreen mode

Name Prefix Convention

Looking at TPM macros above, we notice there are some convention in the prefix of its name. Name Prefix Convention is defined in TCG spec Part2 4.16. Here are the part of it.
Image description
The prefix you must be aware of is TPM2B_ prefix. Structures with this prefix vary in size depending on the data they hold. There aren't any in TPMS_CAPABILITY_DATA, but if this structure is included, you can't just define response structure and access via that structure just like what I'm doing in the above code. This will be described afterwards when this actually appears. For now, it is enough to know that, you can know what type of data it is from the prefix.

bitfield to UINT32

This is not TPM specific thing, but we often see bitfield (the one with TPMA_ prefix) in TPM structure. Since TPM treats data in big endians, we have to swap it, but SwapBytes32 is for UINT32 and not for UINT32 bitfields. Therefore, we need to first convert it to UINT32. In order to convert bitfield to UINT32, you can define union as follows.

union {
  UINT32 tpma_cc_uint32;
  TPMA_CC buf;
} castbuf;
castbuf.buf = /*TPMA_CC object*/;
Print(L"%d", SwapBytes32(castbuf.tpma_cc_uint32));
Enter fullscreen mode Exit fullscreen mode

Running the code

You will get this kind of output if it succeeded. If you get errors, you can check my previous posts on how to evaluate them.
Image description
0x240012A is 00 0 0 001 0 0 1 000000 0000000100101010 so TPMA_CC values for TPM2_NV_DefineSpace are as follows.

  • commandIndex: 0x12A
  • nv: 1
  • extensive: 0
  • flushed: 0
  • cHandles: 1
  • rHandle: 0
  • V: 0
  • Res: 0

Summary

  • TPM2_GetCapability is a command frequently used to check the current status of TPM
  • How to set parameters of TPM2_GetCapability
  • Name Prefix Convention of TPM macros
  • How to convert bitfields to integer value in order to swap bytes

Top comments (0)