TPM2_NV_DefineSpace from UEFI
Tags: uefi, tpm2, edk2, tutorial
Published: 2022-05-24 00:24:49

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/wnurvaxwlz8szq8dz0e6.png)

### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2oshfl3d4f6qfm47vdjc.png)
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/esmcbnl5l2txzdt2jm6g.png)
These structures are defined as TPMS_AUTH_COMMAND and TPMS_AUTH_RESPONSE. 
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7lcb7k7bb1i22kft7h2g.png)  
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.
```c
#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;
}
```

### 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.
```c
typedef struct {
  UINT16    size;
  BYTE      buffer[sizeof (TPMT_HA)];
} TPM2B_DATA;
```
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
```

#### 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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/lela2detizavoqq7qwn2.jpg)
 


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