Чтение значения PCR из UEFI

PCR — одна из наиболее часто используемых функций TPM в системе безопасности UEFI. Измеренная загрузка, которая обеспечивает целостность прошивки UEFI, является хорошим примером использования PCR. В этой статье я расскажу, как прочитать значение PCR из модуля UEFI.
Если вы не знакомы с программированием TPM из UEFI, возможно, вам лучше начать с моей серии статей «Доступ к TPM2.0 из модуля UEFI».

Объяснение PCR

PCR (Platform Configuration Registers) в TPM хранят измерения состояний программного обеспечения. UEFI использует это значение, чтобы убедиться, что ни один из кодов во время фазы загрузки не был изменен. Значение в PCR фактически является хэшем, и оно может быть обновлено только операцией, называемой расширением (или сбросом системы). Перед выполнением следующей программы (например, DxeCore, загрузчика), вызывающая программа расширяет значение PCR, вычисляя PCR[i] = H(PCR[i]||m). PCR[i] — значение i-го PCR, H() — хэш-функция, а m — новое измерение следующей выполняемой программы.

TPM позволяет контролировать доступ к сущностям в зависимости от значения PCR. Например, если вы хотите иметь ключ, доступ к которому возможен только во время определенной части фазы загрузки, вы можете аутентифицировать ключ с помощью политики, которая разрешает операцию чтения только тогда, когда PCR имеет определенное значение.

На любой платформе существует как минимум 24 PCR. Каждый PCR предназначен для хранения измерений различных программ, типичный пример приведен ниже.

(Изображение из Практического руководства по TPM 2.0)

Фактическое распределение зависит от клиента ПК, но в этом посте я буду читать значение PCR[0].

Банк PCR — это группа PCR с одинаковым алгоритмом хэширования. Простой пример приведен ниже.

PCR bank 0 (sha1)
  * PCR[0]: sha1 value
  * ...
  * PCR[23]: sha1 value

PCR bank 1 (sha256)
  * PCR[0]: sha256 value
  * ...
  * PCR[23]: sha256 value
Вход в полноэкранный режим Выход из полноэкранного режима

Это делается для того, чтобы решить такие проблемы, как совместимость для старых приложений, использующих только старый хэш, или для новых приложений, ограничивающих использование только сильного хэша. При расширении значения PCR[i], TPM должен расширить PCR[i] каждого банка, если этот PCR присутствует в банке. Бывают случаи, когда PCR[i] реализован в банке 0, но не реализован в банке 1. Я буду читать из банка PCR с хэшем sha256.

Для дальнейшего описания PCR вы можете обратиться к TCG spec part1.

Реализация

Для создания модуля UEFI я буду использовать EDK2. На самом деле существует два способа доступа к PCR из UEFI.

  1. Используя Tcg2Protocol.GetActivePcrBanks
  2. Используя Tcg2Prtocol.SubmitCommand для отправки команды TPM2_PCR_Read В этом посте я буду использовать два способа.

Формат TPM2_PCR_Read

Изучив спецификацию TCG, часть 3, вы можете проверить формат команды следующим образом.

TPML_PCR_SELECTION

Определение структуры можно найти в TCG spec part2, а фактическое определение структуры в EDK2 можно найти в MdePkg/Include/IndustryStandard/Tpm20.h. Если кратко описать структуру TPML_PCR_SELECTION, то она выглядит следующим образом.

* [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)
Вход в полноэкранный режим Выход из полноэкранного режима

Ниже приведены соответствующие константы.

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 может быть фактическим номером PCR в вашем PC-клиенте, и поскольку EDK2 его не знает, вы должны изменить его на соответствующее значение. Но поскольку мы обращаемся только к PCR[0], не имеет значения, чтобы оставить его таким.

Каждый pcrSelections указывает банк PCR, а каждый бит в pcrSelect указывает PCR. Вы можете удивиться, почему PCR_SELECT_MIN и PCR_SELECT_MAX равны 3, но BYTE является 8-битным и 3*8=24, что означает, что каждый бит определяет индекс PCR, который вы хотите прочитать. Так, например, если вам нужны PCR[0] и PCR[10], вы установите pcrSelect для pcrSelectIn следующим образом (опуская SwapBytes для простоты).

pcrSelect[0] = 00000001 = 1
pcrSelect[1] = 00000100 = 4
pcrSelect[2] = 00000000 = 0
Вход в полноэкранный режим Выход из полноэкранного режима

TPML_DIGEST

Здесь вы получаете фактическое значение PCR, указанное в pcrSelectionIn. Эта структура намного проще, чем TPML_PCR_SELECTION, потому что это просто список digest. Но это может запутать, если вы запросили несколько значений ПЦР: какое значение находится в каком индексе дайджеста. Этот порядок действительно отмечен в описании TPM2_PCR_Read в спецификации.

  • TPM будет обрабатывать список TPMS_PCR_SELECTION в pcrSelectionIn в следующем порядке
  • Внутри каждого TPMS_PCR_SELECTION TPM будет обрабатывать биты в массиве pcrSelect в порядке возрастания PCR.

Исходный код

Вот весь исходный код, получающий значение PCR[0] из банка PCR sha256.

#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...rn");

    RecvBufferSize = sizeof(RecvBuffer);
    EFI_STATUS stats = Tcg2Protocol->SubmitCommand(Tcg2Protocol, CmdBufferSize, (UINT8*)&CmdBuffer, RecvBufferSize, (UINT8*)&RecvBuffer);
    if(stats==EFI_SUCCESS)
        Print(L"SubmitCommand Success!rn");
    else
        Print(L"stats: 0x%x (EFI_DEVICE_ERROR:0x%x, EFI_INVALID_PARAMETER:0x%x, EFI_BUFFER_TOO_SMALL:0x%x)rn", 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)rn", res, res);

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

    UINT32 cntSelectionOut = SwapBytes32(RecvBuffer.pcrSelectionOut.count);
    Print(L"pcrSelectionOut.count (should be 1): %drn", 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): %drn", i, sizeofSelect);
        for(j=0; j<sizeofSelect; j++) {
            Print(L"pcrSelections[%d].pcrSelect[%d]: %1xrn", i, j, pcrSelections[i].pcrSelect[j]);
        }
    }

    UINT32 cntDigest = SwapBytes32(RecvBuffer.pcrValues.count);
    Print(L"Number of digests: %drn", 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):rn", i, size);
        for(j=0; j<size; j++) {
            Print(L"%2X", buf[j]);
        }
        Print(L"rn");
        digests = (TPM2B_DIGEST*)(((void*)digests)+sizeof(UINT16)+size);
    }

    while(1);
    return 0;
}
Вход в полноэкранный режим Выход из полноэкранного режима

Оцените статью
devanswers.ru
Добавить комментарий