Reading PCR value from UEFI
Tags: uefi, tpm2, edk2, tutorial
Published: 2022-07-25 12:15:28

PCR is one of the most-used TPM functionality in UEFI security. Measured boot which ensures integrity of UEFI firmware is a good example of the use of PCR. In this post, I will explain how to read PCR value from UEFI module.
If you are not familiar with TPM programming from UEFI, it might be better to start with my [Accessing TPM2.0 from UEFI module Series](https://dev.to/machinehunter/series/18231).


## PCR explanaition
**PCRs (Platform Configuration Registers) in TPM holds measurements of software states**. UEFI uses this value to ensure if none of the code during the bootphase are modified. Value in PCR is actually a hash and this can only be updated by an operation called *extend* (or system reset). Before executing the next program (e.g. DxeCore, bootloader), the caller *extend* the PCR value by calculating `PCR[i] = H(PCR[i]||m)`. `PCR[i]` is the *i* th PCR's value, `H()` is the hash function and `m` is the new measurement of the next program going to be executed.

TPM allows you to **control access towards entities depending on PCR value**. For example, if you want to have a key which is only accessible during specific part in the bootphase, you can authenticate the key with a policy which only allows reading operation when PCR has specific value.

There are at least **24 PCRs** on any platform. Each PCRs are meant to store different software's measurements, and typical example is like below.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kvug3dztteg0w9c69ihg.png)
(Image from [A Practical Guide to TPM 2.0](https://ebrary.net/24779/computer_science/platform_configuration_registers))

The actual allocation depends on PC client, but in this post, I'll read the value of **PCR[0]**.

**PCR bank** is a group of PCR with same hash algorithm. Simple example is like below.
```
PCR bank 0 (sha1)
  * PCR[0]: sha1 value
  * ...
  * PCR[23]: sha1 value

PCR bank 1 (sha256)
  * PCR[0]: sha256 value
  * ...
  * PCR[23]: sha256 value
```  
This is to handle issues like compatibility for old application only using old hash, or new application restricting to use only strong hash. When extending PCR[i] value, TPM should extend each bank's PCR[i] if that PCR is present in bank. There are cases when PCR[i] is implemented in bank0 but not in bank1. I will be reading from PCR bank with **sha256** hash.

For further description of PCR, you can refer to [TCG spec](https://trustedcomputinggroup.org/resource/tpm-library-specification/) part1.

## Implementation
I will be using [EDK2](https://github.com/tianocore/edk2) to build the UEFI module. There are actually two ways to access PCR from UEFI.
1. Using `Tcg2Protocol.GetActivePcrBanks`
2. Using `Tcg2Protocol.SubmitCommand` to send `TPM2_PCR_Read` command
I will be using 2 in this post.

### TPM2_PCR_Read format
Looking at [TCG spec](https://trustedcomputinggroup.org/resource/tpm-library-specification/) part3, you can check the command's format as follows.
![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hal3qu9hzcyciaz329v.png)

#### TPML_PCR_SELECTION
Structure's definition can be found at TCG spec part2, and the actual definition of the structure in EDK2 can be found in `MdePkg/Include/IndustryStandard/Tpm20.h`. To summarize the `TPML_PCR_SELECTION` structure, it is like follows.
```
* [TPML_PCR_SELECTION] pcrSelectionIn/pcrSelectionOut
   * [UINT32]             count
   * [TPMS_PCR_SELECTION] pcrSelections[count] (max: HASH_COUNT)
      * [TPMI_ALG_HASH][TPM_ALG_ID][UINT16] hash
      * [UINT8]                             sizeofSelect (min: PCR_SELECT_MIN)
      * [BYTE]                              pcrSelect[sizeofSelect] (max: PCR_SELECT_MAX)
```
Below are the related constants.
```c
HASH_COUNT         = 5;
PCR_SELECT_MIN     = ((PLATFORM_PCR + 7) / 8) = 3;
PLATFORM_PCR       = 24;
PCR_SELECT_MAX     = ((IMPLEMENTATION_PCR + 7) / 8) = 3;
IMPLEMENTATION_PCR = 24;
```
`IMPLEMENTATION_PCR` maybe the actual number of PCR in your PC client, and since EDK2 doesn't know it, you have to change it to the appropriate value. But since we're only accessing only PCR[0], it doesn't matter to keep it this way.

**Each `pcrSelections` specify PCR bank, and each bit in `pcrSelect` specify PCR**. You might wonder why `PCR_SELECT_MIN` and `PCR_SELECT_MAX` are 3, but `BYTE` is 8bit and `3*8=24`, which means, each bit specifies the PCR index you want to read. So for example, if you want PCR[0] and PCR[10], you will set `pcrSelect` for `pcrSelectIn` like the following (Omitting SwapBytes for simplicity).
```
pcrSelect[0] = 00000001 = 1
pcrSelect[1] = 00000100 = 4
pcrSelect[2] = 00000000 = 0
```

#### TPML_DIGEST
This is where you get the actual value of PCR specified in the `pcrSelectionIn`. This structure is way simpler than `TPML_PCR_SELECTION` because it is just a list of `digest`. But, it might be confusing if you requested multiple PCR values: what value is in what index of digest. This order is actually noted in the description of `TPM2_PCR_Read` in the spec.
> * TPM will process the list of TPMS_PCR_SELECTION in pcrSelectionIn in order
> * Within each
TPMS_PCR_SELECTION, the TPM will process the bits in the pcrSelect array in ascending PCR order



### Source code
Here is the whole source code getting PCR[0] value from sha256 PCR bank.
```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 {
		UINT32 count;
		TPMS_PCR_SELECTION pcrSelections[1];
	} ORIG_TPML_PCR_SELECTION;

	typedef struct {
		TPM2_COMMAND_HEADER Header;
		ORIG_TPML_PCR_SELECTION pcrSelectionIn;
	} TPM2_PCR_READ_COMMAND;

	typedef struct {
		TPM2_RESPONSE_HEADER Header;
		UINT32 pcrUpdateCounter;
		ORIG_TPML_PCR_SELECTION pcrSelectionOut;
		TPML_DIGEST pcrValues;
	} TPM2_PCR_READ_RESPONSE;
#pragma pack()


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

	ORIG_TPML_PCR_SELECTION pcrSelectionIn;
	pcrSelectionIn.count                         = SwapBytes32(1);
	pcrSelectionIn.pcrSelections[0].hash         = SwapBytes16(TPM_ALG_SHA256);
	pcrSelectionIn.pcrSelections[0].sizeofSelect = PCR_SELECT_MIN;

	for(i=0; i<PCR_SELECT_MIN; i++)
		pcrSelectionIn.pcrSelections[0].pcrSelect[i] = 0;

	UINT32 pcrId;
	for(pcrId=0; pcrId<PLATFORM_PCR; pcrId++) {
		if(pcrId==0)
			pcrSelectionIn.pcrSelections[0].pcrSelect[pcrId/8] |= (1<<(pcrId%8));
	}


	TPM2_PCR_READ_COMMAND CmdBuffer;
	UINT32 CmdBufferSize;
	TPM2_PCR_READ_RESPONSE RecvBuffer;
	UINT32 RecvBufferSize;

	CmdBuffer.Header.tag         = SwapBytes16(TPM_ST_NO_SESSIONS);
	CmdBuffer.Header.commandCode = SwapBytes32(TPM_CC_PCR_Read);
	CmdBuffer.pcrSelectionIn     = pcrSelectionIn;
	CmdBufferSize = sizeof(CmdBuffer.Header) + sizeof(CmdBuffer.pcrSelectionIn);
	CmdBuffer.Header.paramSize = SwapBytes32(CmdBufferSize);

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

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

	UINT32 pcrUpdateCounter = SwapBytes32(RecvBuffer.pcrUpdateCounter);
	Print(L"Pcr Update Counter: %d (0x%X)\r\n", pcrUpdateCounter, pcrUpdateCounter);

	UINT32 cntSelectionOut = SwapBytes32(RecvBuffer.pcrSelectionOut.count);
	Print(L"pcrSelectionOut.count (should be 1): %d\r\n", cntSelectionOut);

	TPMS_PCR_SELECTION* pcrSelections = (TPMS_PCR_SELECTION*)RecvBuffer.pcrSelectionOut.pcrSelections;
	for(i=0; i<cntSelectionOut; i++) {
		UINT8 sizeofSelect = pcrSelections[i].sizeofSelect;
		Print(L"pcrSelections[%d].sizeofSelect (should be 3): %d\r\n", i, sizeofSelect);
		for(j=0; j<sizeofSelect; j++) {
			Print(L"pcrSelections[%d].pcrSelect[%d]: %1x\r\n", i, j, pcrSelections[i].pcrSelect[j]);
		}
	}

	UINT32 cntDigest = SwapBytes32(RecvBuffer.pcrValues.count);
	Print(L"Number of digests: %d\r\n", cntDigest);

	TPM2B_DIGEST* digests = (TPM2B_DIGEST*)RecvBuffer.pcrValues.digests;
	for(i=0; i<cntDigest; i++) {
		UINT16 size = SwapBytes16(digests[i].size);
		BYTE buf[size];
		for(j=0; j<size; j++)
			buf[size-j-1] = digests[i].buffer[j];

		Print(L"pcrValues.digest[%d] (size %d):\r\n", i, size);
		for(j=0; j<size; j++) {
			Print(L"%2X", buf[j]);
		}
		Print(L"\r\n");
		digests = (TPM2B_DIGEST*)(((void*)digests)+sizeof(UINT16)+size);
	}

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