DEV Community

MachineHunter
MachineHunter

Posted on

TPM2_NV_DefineSpace from UEFI

We can say that the most complicated part of TPM2.0 is authentication/session. My previous post's TPM2_GetRandom and TPM2_GetCapability didn't require session (as we specified TPM_ST_NO_SESSIONS in the Header tag) so it was relatively easy to implement. However, commands which requires session are much more difficult than those. In this article, we will define 128bit (16byte) of space in NVRAM of TPM by using TPM2_NV_DefineSpace.

Parameters of TPM2_NV_DefineSpace

Image description

Sessions

If command requires session, we have to specify TPM_ST_SESSIONS in the tag of Command Header. Session in TPM is quite a complex concept. For details, I want you to look at TCG spec Part1 19.5 or other materials. In this case, session is used for authorization of the use of authHandle because it has @ mark.

Now, one of the trickiest pitfall here is that, where to write the actual content of the session. It is not explicitly written in the above table, but looking at TCG spec Part1 18.9 and 18.10, it says that we have to insert some authorization-related structure like below.
Image description
Image description
These structures are defined as TPMS_AUTH_COMMAND and TPMS_AUTH_RESPONSE.
Image description

When sending command, we have to put TPMS_AUTH_COMMAND (the actual content of the session) above the thick line which is between handle and command-specific parameters. For the response, there will be only parameterSize above the thick line and other information (TPMS_AUTH_RESPONSE) is appended at last. These authorization-related structures will exists for any command which requires sessions.

Implementation

The whole code will be as follows.

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

#pragma pack(1)
    typedef struct {
        TPMI_SH_AUTH_SESSION sessionHandle;
        UINT16 nonceSizeZero;
        TPMA_SESSION sessionAttributes;
        UINT16 hmacSizeZero;
    } ORIG_AUTH_AREA;

    typedef struct {
        UINT16 size;
        TPMI_RH_NV_INDEX nvIndex;
        TPMI_ALG_HASH nameAlg;
        /*TPMA_NV attributes;*/
        UINT32 attributes;
        UINT16 authPolicySizeZero;
        UINT16 dataSize;
    } ORIG_NV_PUBLIC;

    typedef struct {
        TPM2_COMMAND_HEADER Header;
        TPMI_RH_PROVISION authHandle;
        UINT32 authSize;
        ORIG_AUTH_AREA authArea;
        /*TPM2B_AUTH auth;*/
        UINT16 authSizeZero;
        /*TPM2B_NV_PUBLIC publicInfo;*/
        ORIG_NV_PUBLIC publicInfo;
    } TPM2_NV_DEFINE_SPACE_COMMAND;

    typedef struct {
        TPM2_RESPONSE_HEADER Header;
    } TPM2_NV_DEFINE_SPACE_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);


    // Auth Area
    UINT32 authSize;
    ORIG_AUTH_AREA authArea;
    authArea.sessionHandle = SwapBytes32(TPM_RS_PW);
    authArea.nonceSizeZero = SwapBytes16(0);
    authArea.sessionAttributes.continueSession = 0;
    authArea.sessionAttributes.auditExclusive  = 0;
    authArea.sessionAttributes.auditReset      = 0;
    authArea.sessionAttributes.reserved3_4     = 0;
    authArea.sessionAttributes.decrypt         = 0;
    authArea.sessionAttributes.encrypt         = 0;
    authArea.sessionAttributes.audit           = 0;
    authArea.hmacSizeZero = SwapBytes16(0);
    authSize = sizeof(authArea);


    // publicInfo area
    ORIG_NV_PUBLIC publicInfo;
    publicInfo.nvIndex = SwapBytes32(NV_INDEX_FIRST);
    publicInfo.nameAlg = SwapBytes16(TPM_ALG_SHA1);
    /*
     *TPMA_NV attributes;
     *attributes.TPMA_NV_PPWRITE        = 1;
     *attributes.TPMA_NV_OWNERWRITE     = 1;
   *attributes.TPMA_NV_AUTHWRITE      = 1;
   *attributes.TPMA_NV_POLICYWRITE    = 1;
   *attributes.TPMA_NV_COUNTER        = 0;
   *attributes.TPMA_NV_BITS           = 0;
   *attributes.TPMA_NV_EXTEND         = 0;
   *attributes.reserved7_9            = 000;
   *attributes.TPMA_NV_POLICY_DELETE  = 0;
   *attributes.TPMA_NV_WRITELOCKED    = 0;
   *attributes.TPMA_NV_WRITEALL       = 1;
   *attributes.TPMA_NV_WRITEDEFINE    = 0;
   *attributes.TPMA_NV_WRITE_STCLEAR  = 1;
   *attributes.TPMA_NV_GLOBALLOCK     = 0;
   *attributes.TPMA_NV_PPREAD         = 1;
   *attributes.TPMA_NV_OWNERREAD      = 1;
   *attributes.TPMA_NV_AUTHREAD       = 1;
   *attributes.TPMA_NV_POLICYREAD     = 1;
   *attributes.reserved20_24          = 00000;
   *attributes.TPMA_NV_NO_DA          = 1;
   *attributes.TPMA_NV_ORDERLY        = 0;
   *attributes.TPMA_NV_CLEAR_STCLEAR  = 0;
   *attributes.TPMA_NV_READLOCKED     = 0;
   *attributes.TPMA_NV_WRITTEN        = 0;
   *attributes.TPMA_NV_PLATFORMCREATE = 0;
   *attributes.TPMA_NV_READ_STCLEAR   = 0;
   * => 00000010000011110101000000001111
     * => 0x20f500f
     */
    publicInfo.attributes = SwapBytes32(0x20f500f);
    publicInfo.authPolicySizeZero = SwapBytes16(0);
    publicInfo.dataSize = SwapBytes16(16);
    publicInfo.size = SwapBytes16(sizeof(publicInfo) - sizeof(publicInfo.size));


    TPM2_NV_DEFINE_SPACE_COMMAND CmdBuffer;
    UINT32 CmdBufferSize;
    TPM2_NV_DEFINE_SPACE_RESPONSE RecvBuffer;
    UINT32 RecvBufferSize;

    // set parameters
    CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_SESSIONS);
    CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_NV_DefineSpace);
    CmdBuffer.authHandle         = SwapBytes32(TPM_RH_OWNER);
    CmdBuffer.authSize           = SwapBytes32(authSize);
    CmdBuffer.authArea           = authArea;
    CmdBuffer.authSizeZero       = SwapBytes16(0);
    CmdBuffer.publicInfo         = publicInfo;
    CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.authHandle) + sizeof(CmdBuffer.authSize) + sizeof(CmdBuffer.authArea) + sizeof(CmdBuffer.authSizeZero) + sizeof(CmdBuffer.publicInfo);
    CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);


    // send TPM command
    Print(L"sending TPM command...\r\n");
    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);


    UINT32 res = SwapBytes32(RecvBuffer.Header.responseCode);
    Print(L"responseCode is %d\r\n", res);


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

TPMS_AUTH_COMMAND

This is the session content mentioned above. There are 3 ways of authentication in TPM.

  • Password
  • HMAC
  • Policy

In this example, I'm using password (TPM_RS_PW) which is the easiest authentication method.

When using tpm2-tool and you didn't specify any of these authentication, it defaults to use password authentication with blank password.

You may notice I'm not using TPMS_AUTH_COMMAND even though it's defined in Tpm20.h. Most of the attributes are same as TPMS_AUTH_COMMAND, but nounceSizeZero and hmacSizeZero are redefined. This is actually one of the pitfalls: TPM's canonicalization. For example, nonce has a type of TPM2B_NONCE which is same as TPM2B_DIGEST, and if you lookup this definition in Tpm20.h, it's defined like this.

typedef struct {
  UINT16    size;
  BYTE      buffer[sizeof (TPMT_HA)];
} TPM2B_DATA;
Enter fullscreen mode Exit fullscreen mode

Normally, if you set size=0 and buffer=NULL, total size of data actually sent will be sizeof(UINT16)+sizeof(TPMT_HA). However, in TPM, size of data sent will only be sizeof(UINT16). If size=2 and buffer="A\0" then the data size sent will be sizeof(UINT16)+2.

Therefore, if you have structures with TPM2B_ prefix, you have to redefine that structure and adjust the size of buffer field to the actual data size you're trying to put. This is called canonicalization.

auth parameter

If you read authorization-related section of TCG spec or Practical Guide to TPM2.0, you will see a lots of word authValue. This is roughly an input for authorization, so in password authentication, this value is the actual password. Also, authValue is specified by different parameter in different commands. In TPM2_NV_DefineSpace, the parameter for authValue is auth parameter.

I wanted to use blank password for this authentication so that is why I redefined TPM2B_AUTH auth to UINT16 authSizeZero.

publicInfo Area

This parameter is the configuration of NVRAM space we're trying to define. To expand TPM2B_NV_PUBLIC structure, it will be like follows.

* TPM2B_NV_PUBLIC
  * UINT16 size
  * TPMS_NV_PUBLIC nvPublic
    * [UINT32] [TPM_HANDLE] TPMI_RH_NV_INDEX nvIndex
    * [UINT16] [TPM_ALG_ID] TPMI_ALG_HASH nameAlg
    * [UINT32 bitfield] TPMA_NV attributes
    * TPM2B_DIGEST authPolicy
      * UINT16 size
      * BYTE buffer[]
    * UINT16 dataSize
Enter fullscreen mode Exit fullscreen mode

NV Index and Name

NV Index is something like a handle for the specific NVRAM space. There are constants NV_INDEX_FIRST and NV_INDEX_LAST so you can specify any value in this range for nvIndex. TPM uses "Name" as an unique identifier for TPM entities. We must specify by what algorithm we generate Name so there is nameAlg parameter.

TPMA_NV

We have to take roundabout method to send TPMA_ structure. Since TPM require us to send data in big endian, we have to SwapBytes TPMA_, but SwapBytes32 doesn't allow UINT32 bitfield. You can use union to cast it into UINT32 or just do it manually like above code.

TPMA_NV determines the core configuration of the NV Index we're trying to create. For detailed meanings of each bit, you can look at TCG spec Part2, and here, I will just explain the points.

  • There are mainly 4 types of NV Index: Ordinary, Bit Field, Extend, Hybrid NV Indexes.
  • I'm specifying NV Ordinary Index which is a type we can put unstructured data with any size
  • We can specify separate authorization for the read and write to this NV Index
  • NV Index can be locked to prevent read or write

Running the code

You will get this kind of output if it succeeded. My response code is 332 which means the NV Index I specified is already in use and actually it is, but if it isn't, you should get response code 0. Also, after defining this, you can run this program again and see if you get 332.
Image description

Summary

This article was mainly about how to implement TPM commands which requires authorization and session.

  • If command requires session, we have to specify TPM_ST_SESSIONS in the tag of Command Header
  • session is required to authorize the use of parameter which has @ mark
  • TPMS_AUTH_COMMAND and TPMS_AUTH_RESPONSE should be placed as written in TCG spec Part1 18.9 and 18.10
  • TPMS_AUTH_COMMAND and TPMS_AUTH_RESPONSE is required for every command that requires session
  • There are 3 ways of authentication in TPM: Password, HMAC, and Policy
  • Canonicalization: TPM sends only sizeof(UINT16) if you specify 0 for size, even though you had buffer[MAX_...] in the structure
  • We have to redefine structure if we have struct with TPM2B_ prefix due to canonicalization
  • authValue is roughly an input required for authorization
  • NV Index is something like a handle for the specific NVRAM space
  • TPM uses "Name" as an unique identifier for TPM entities
  • How to swap bytes of TPMA_ object and broad explanation of TPMA_NV

Top comments (0)