Translate

Archives

UEFI Utility to Read TPM 1.2 PCRs

A Trusted Platform Module (TPM) supports many security functions including a number of special registers called Platform Configuration Registers (PCRs) which can hold data in a shielded location in a manner that prevents tampering or spoofing.

A PCR is a 20-byte register. which incidentally is the length of a SHA-1 (Secure Hash Algorithm) hash. Most modern TPMs have 24 or even more PCRs; older ones have 16 PCRs. The TPM 1.2 specification, developed by the Trusted Computing Group (TCG) only requires 16 PCRs. Typically PCRs are used to store measurements. Measurements can be of code, data structures, configuration, information, or anything that can be loaded into memory with code measurement being the most common use case. The idea is that no code is executed until it has been measured.

Measurements consist of a cryptographic hash using SHA-1. To further protect the integrity of the measurements, hashes are not written directly to PCRs; instead a PCR is extended with a measurement. This means that the TPM takes the current value of the PCR and the measurement to be extended, hashes them together, and replaces the content of the PCR with that hash result. Note that a TPM is not responsible for actually measuring anything; only for securely storing the measurement.

Extend works like this:

     New PCR value = SHA-1 hash (Current PCR value || new SHA-I hash)

The TPM retrieves the current value of a PCR, concatenates the new SHA-1 hash to the end of the retrieved value in order to obtain a 40-byte value, calculates the SHA-1 of this 40-byte value to obtain a 20-byte hash and sets the PCR value to this SHA-1.

The result is that the only way to arrive at a particular measurement in a PCR is to extend exactly the same measurements in exactly the same order. Therefore, if any module being measured has been modified, the resulting PCR measurement will be different and thus it is easy to detect if any code, configuration, data, etc. that has been measured was been modified or corrupted as it is computationally infeasible to forge the resulting PCR value.

PCRs can never be arbitrarily overwritten but many can be reset while the platform is powered on to a known value, either all zero bits or all one bits, using the TPM PCR_Reset command. Whether a specific PCR can be reset or not is defined by various platform specifications and requires appropriate permissions. All PCRs can be reset at power on.

Before I go any further, here is the source code for my ShowPCR12 utility:

//
//  Copyright (c) 2017  Finnbarr P. Murphy.   All rights reserved.
//
//  Retrieve and print TPMi 1.2 PCR digests 
//
//  License: BSD License
//


#include <uefi .h>
#include <library /UefiLib.h>
#include </library><library /ShellCEntryLib.h>
#include </library><library /ShellLib.h>
#include </library><library /BaseMemoryLib.h>
#include </library><library /UefiBootServicesTableLib.h>

#include <protocol /EfiShell.h>
#include </protocol><protocol /LoadedImage.h>
#include </protocol><protocol /TcgService.h>
#include </protocol><protocol /Tcg2Protocol.h>
#include <industrystandard /UefiTcgPlatform.h>

#define UTILITY_VERSION L"0.8"

#define EFI_TCG2_PROTOCOL_GUID \
  {0x607f766c, 0x7455, 0x42be, { 0x93, 0x0b, 0xe4, 0xd7, 0x6d, 0xb2, 0x72, 0x0f }}


VOID 
Print_PcrDigest(TPM_PCRINDEX PcrIndex, TPM_PCRVALUE *PcrValue)
{
    int size = sizeof(TPM_PCRVALUE);
    UINT8 *buf = (UINT8 *)PcrValue;

    Print(L"[%02d]  ", PcrIndex);
    for (int i = 0; i < size; i++) {
        Print(L"%02x ", 0xff & buf[i]);
    }
    Print(L"\n");
}


BOOLEAN
CheckForTpm20()
{
     EFI_STATUS Status = EFI_SUCCESS;
     EFI_TCG2_PROTOCOL *Tcg2Protocol;
     EFI_GUID gEfiTcg2ProtocolGuid = EFI_TCG2_PROTOCOL_GUID;

     Status = gBS->LocateProtocol( &gEfiTcg2ProtocolGuid,
                                   NULL,
                                   (VOID **) &Tcg2Protocol);
     if (EFI_ERROR (Status)) {
         return FALSE;
     } 

     return TRUE;
}


VOID
Usage(CHAR16 *Str)
{
    Print(L"Usage: %s [--version]\n", Str);
}


INTN
EFIAPI
ShellAppMain(UINTN Argc, CHAR16 **Argv)
{
    EFI_STATUS Status = EFI_SUCCESS;
    EFI_TCG_PROTOCOL *TcgProtocol;
    EFI_GUID gEfiTcgProtocolGuid = EFI_TCG_PROTOCOL_GUID;

    TPM_RSP_COMMAND_HDR *TpmRsp;
    UINT32              TpmSendSize;
    UINT8               CmdBuf[64];
    TPM_PCRINDEX        PcrIndex;
    TPM_PCRVALUE        *PcrValue;


    if (Argc >= 2) {
        if (!StrCmp(Argv[1], L"--version")) {
            Print(L"Version: %s\n", UTILITY_VERSION);
        } else {
            Usage(Argv[0]);
        }
        return Status;
    }

    Status = gBS->LocateProtocol( &gEfiTcgProtocolGuid, 
                                  NULL, 
                                  (VOID **) &TcgProtocol);
    if (EFI_ERROR (Status)) {
        if (CheckForTpm20()) {
            Print(L"ERROR: Platform configured for TPM 2.0, not TPM 1.2\n");
        } else {
            Print(L"ERROR: Failed to locate EFI_TCG_PROTOCOL [%d]\n", Status);
        }
        return Status;
    }  


    // Loop through all the PCRs and print each digest 
    for (PcrIndex = 1; PcrIndex < = TPM_NUM_PCR; PcrIndex++) {
        TpmSendSize           = sizeof (TPM_RQU_COMMAND_HDR) + sizeof (UINT32);
        *(UINT16*)&CmdBuf[0]  = SwapBytes16 (TPM_TAG_RQU_COMMAND);
        *(UINT32*)&CmdBuf[2]  = SwapBytes32 (TpmSendSize);
        *(UINT32*)&CmdBuf[6]  = SwapBytes32 (TPM_ORD_PcrRead);
        *(UINT32*)&CmdBuf[10] = SwapBytes32 (PcrIndex);

        Status = TcgProtocol->PassThroughToTpm( TcgProtocol,
                                                TpmSendSize,
                                                CmdBuf,
                                                sizeof (CmdBuf),
                                                CmdBuf);
        if (EFI_ERROR (Status)) {
            if (CheckForTpm20()) {
                Print(L"ERROR: Platform configured for TPM 2.0, not TPM 1.2\n");
            } else {
                Print(L"ERROR: PassThroughToTpm failed [%d]\n", Status);
            }
            return Status;
        }

        TpmRsp = (TPM_RSP_COMMAND_HDR *) &CmdBuf[0];
        if ((TpmRsp->tag != SwapBytes16(TPM_TAG_RSP_COMMAND)) || (TpmRsp->returnCode != 0)) {
            Print(L"ERROR: TPM command result [%d]\n", SwapBytes16(TpmRsp->returnCode));
            return EFI_DEVICE_ERROR;
        }

        PcrValue = (TPM_PCRVALUE *) &CmdBuf[sizeof (TPM_RSP_COMMAND_HDR)];
        Print_PcrDigest(PcrIndex, PcrValue);
    }

    return Status;
}

I developed it using the Tianocore UDK2015 (UEFI Development Kit, 2015 release). UDK2017 is not yet released; it is scheduled for early 2017Q1.

Here is my build configuration file:

[Defines]
  INF_VERSION                    = 0x00010006
  BASE_NAME                      = ShowPCR12 
  FILE_GUID                      = 4ea87c51-7395-4ccd-0355-747011f3ce51
  MODULE_TYPE                    = UEFI_APPLICATION
  VERSION_STRING                 = 0.1
  ENTRY_POINT                    = ShellCEntryLib
  VALID_ARCHITECTURES            = X64

[Sources]
  ShowPCR12.c

[Packages]
  MdePkg/MdePkg.dec
  ShellPkg/ShellPkg.dec

[LibraryClasses]
  ShellCEntryLib
  ShellLib
  BaseLib
  BaseMemoryLib
  UefiLib

[Protocols]

[BuildOptions]

[Pcd]


To assist you in understanding the code I have included a copy of the TPM_PCRRead specification from Part 3 of the TPM 1.2 specification. In UDK2015, the assigned ordinal for TPM_PCRRead is TPM_ORD_PcrRead and that is what you will see in the above code.

I also include a copy of the relevant section of the TPM 1.2 EFI specification detailing the PassThroughToTpm API which is exposed via the EFI_TCG_PROTOCOL_GUID guid.

Note this utility interacts with a TPM at a very low level, i.e. what Will Arthur et al in their excellent book A Practical Guide to TPM 2.0 in Figure 7.1 call the System API (SAPI) layer. Could I have used one of the two other higher levels API layers? Yes, but frankly SAPI is easier for me to use when coding a UEFI shell utility as I am quite familiar with it.

Here is the output of this utility when run against the hardware TPM on my Lenovo T450:

fs1> ShowPCR12.EFI
[01]  11 EE 11 E5 04 52 D7 88 EC 99 BE D1 61 75 D1 37 68 10 56 D2 
[02]  B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36 
[03]  B2 A8 3B 0E BF 2F 83 74 29 9A 5B 2B DF C3 1E A9 55 AD 72 36 
[04]  A4 32 BC 6C 7F 61 51 27 CC 7F 73 65 22 63 79 CB AD 41 0A 6E 
[05]  45 A3 23 38 2B D9 33 F0 8E 7F 0E 25 6B C8 24 9E 40 95 B1 EC 
[06]  A9 CD BE 97 0A A5 DD AA A8 C2 07 28 A0 EB 16 44 DC 4D 50 FE 
[07]  DD 38 9D 19 91 FA 9A D1 D9 D9 CF 41 B4 94 8E 39 39 DD 7D BC 
[08]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[09]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[10]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[11]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[12]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[13]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[14]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[15]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 
[16]  00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
fs1> 

As you can see the first eight PCRs have non-zero values. The Trusted Computing Group only defined a Static Chain of Trust for TPM 1.2 starting from a Static Core Root of Trust for Measurements (S-CRTM) and assigns the following meanings to measurements in the first 8 PCRs:

  • PCR0 – CRTM, BIOS code, and Platform Extensions
  • PCR1 – Platform Configuration
  • PCR2 – Option ROM Code
  • PCR3 – Option ROM Configuration and Data
  • PCR4 – IPL (Initial Program Loader) Code
  • PCR5 – IPL Code Configuration and Data
  • PCR6 – State Transition and Wake Events
  • PCR7 – Platform Manufacturer Control

PCRs 8 to 15 are assigned for use by the operating system. For example, a trusted boot loader typically uses PCR8 and PCR9.

The following diagram shows the difference between a Static Chain of Trust which I have discussed and a Dynamic Chain of Trust which I am about to briefly introduce.

TPM 1.2 only supports a Static Chain of Trust. Here, the first thing measured at boot is called the Core Root of Trust for Measurements (CRTM) whereby the firmware (AKA BIOS) boot block will measure the firmware and store the resultant value in PCR0 before executing it. On Intel platforms, it does this by invoking an Intel-supplied chunk of code called Authenticated Code Module (BIOS ACM) which is embedded in the firmware and can authenticate and validate the firmware. Then the firmware will measure the next thing in the boot chain before executing it and store the value in PCR1. This process continues for each component in the boot sequence.

On the other hand, Dynamic Root of Trust for Measurements DRTM) is quite different as you can see from the above diagram. The goal of DRTM is to create a trusted environment from an initially untrusted state. Intel calls their implementation Trusted Execution Technology (TXT) and AMD uses the name Secure Virtual Machine (SVM). A detailed discussion of the differences between SRTM and DRTM is outside the scope of this post. Do a search, SRTM versus DRTM, on StackExchange for more information.

By the way, if you have access to the Intel TXT (Trusted Execution Technology) EFI compliance testing toolkit, the included utility, pcrdump.efi, provides similar functionality to the utility described in this post.

4 comments to UEFI Utility to Read TPM 1.2 PCRs

  • Mario

    Hi!

    Your work is really interesting! Are you also able to rewrite the svp area on newer T440/T450? Maybe we can share some information. Feel free to contact me ;-)

  • Mario

    Hi!

    No, (S)uper(V)isor(P)assword. I have an eeprom reader/writer so Iam able to read the eeprom full. Easy way is just to remove the dxe for the password stuff. But that will not clear the svp at all, only the query.

    On the older ones (pre t440) it is a 24RF08. I have built an Arduino to reset those old ones, but this will not work with newer ones. For what i know, the svp is stored in the tpm chip on newer models.

    Maybe i`am wrong, but i know this is possible. I have seen a bios for the t440 with a manually added dxe driver which clears the svp out.

    rg

  • Ben

    Hi,

    I have recently begun some additions to our bootloader code that require the use of TPM2.0 and I am having a really hard time. I am getting very lost in a specification that seems to have been designed to do everything and more!

    Basically I need to implement the Load, EncryptDecrypt and DA functions. This is probably beyond the scope of the comments system here and maybe beyond the scope of unsolicited advice but any examples, help, or tips would be most appreciated.

    B