/* SPDX-License-Identifier: LGPL-2.1+ */
/*
 * This program is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation; either version 2.1 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful, but
 * WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 */

#if ENABLE_TPM

#include <efi.h>
#include <efilib.h>
#include "measure.h"

#define EFI_TCG_PROTOCOL_GUID { 0xf541796d, 0xa62e, 0x4954, {0xa7, 0x75, 0x95, 0x84, 0xf6, 0x1b, 0x9c, 0xdd} }

typedef struct _TCG_VERSION {
        UINT8 Major;
        UINT8 Minor;
        UINT8 RevMajor;
        UINT8 RevMinor;
} TCG_VERSION;

typedef struct tdEFI_TCG2_VERSION {
        UINT8 Major;
        UINT8 Minor;
} EFI_TCG2_VERSION;

typedef struct _TCG_BOOT_SERVICE_CAPABILITY {
        UINT8 Size;
        struct _TCG_VERSION StructureVersion;
        struct _TCG_VERSION ProtocolSpecVersion;
        UINT8 HashAlgorithmBitmap;
        BOOLEAN TPMPresentFlag;
        BOOLEAN TPMDeactivatedFlag;
} TCG_BOOT_SERVICE_CAPABILITY;

typedef struct tdTREE_BOOT_SERVICE_CAPABILITY {
        UINT8 Size;
        EFI_TCG2_VERSION StructureVersion;
        EFI_TCG2_VERSION ProtocolVersion;
        UINT32 HashAlgorithmBitmap;
        UINT32 SupportedEventLogs;
        BOOLEAN TrEEPresentFlag;
        UINT16 MaxCommandSize;
        UINT16 MaxResponseSize;
        UINT32 ManufacturerID;
} TREE_BOOT_SERVICE_CAPABILITY;

typedef UINT32 TCG_ALGORITHM_ID;
#define TCG_ALG_SHA 0x00000004  // The SHA1 algorithm

#define SHA1_DIGEST_SIZE 20

typedef struct _TCG_DIGEST {
        UINT8 Digest[SHA1_DIGEST_SIZE];
} TCG_DIGEST;

#define EV_IPL 13

typedef struct _TCG_PCR_EVENT {
        UINT32 PCRIndex;
        UINT32 EventType;
        struct _TCG_DIGEST digest;
        UINT32 EventSize;
        UINT8 Event[1];
} TCG_PCR_EVENT;

INTERFACE_DECL(_EFI_TCG);

typedef EFI_STATUS(EFIAPI * EFI_TCG_STATUS_CHECK) (IN struct _EFI_TCG * This,
                                                   OUT struct _TCG_BOOT_SERVICE_CAPABILITY * ProtocolCapability,
                                                   OUT UINT32 * TCGFeatureFlags,
                                                   OUT EFI_PHYSICAL_ADDRESS * EventLogLocation,
                                                   OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry);

typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_ALL) (IN struct _EFI_TCG * This,
                                               IN UINT8 * HashData,
                                               IN UINT64 HashDataLen,
                                               IN TCG_ALGORITHM_ID AlgorithmId,
                                               IN OUT UINT64 * HashedDataLen, IN OUT UINT8 ** HashedDataResult);

typedef EFI_STATUS(EFIAPI * EFI_TCG_LOG_EVENT) (IN struct _EFI_TCG * This,
                                                IN struct _TCG_PCR_EVENT * TCGLogData,
                                                IN OUT UINT32 * EventNumber, IN UINT32 Flags);

typedef EFI_STATUS(EFIAPI * EFI_TCG_PASS_THROUGH_TO_TPM) (IN struct _EFI_TCG * This,
                                                          IN UINT32 TpmInputParameterBlockSize,
                                                          IN UINT8 * TpmInputParameterBlock,
                                                          IN UINT32 TpmOutputParameterBlockSize,
                                                          IN UINT8 * TpmOutputParameterBlock);

typedef EFI_STATUS(EFIAPI * EFI_TCG_HASH_LOG_EXTEND_EVENT) (IN struct _EFI_TCG * This,
                                                            IN EFI_PHYSICAL_ADDRESS HashData,
                                                            IN UINT64 HashDataLen,
                                                            IN TCG_ALGORITHM_ID AlgorithmId,
                                                            IN struct _TCG_PCR_EVENT * TCGLogData,
                                                            IN OUT UINT32 * EventNumber,
                                                            OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry);

typedef struct _EFI_TCG {
        EFI_TCG_STATUS_CHECK StatusCheck;
        EFI_TCG_HASH_ALL HashAll;
        EFI_TCG_LOG_EVENT LogEvent;
        EFI_TCG_PASS_THROUGH_TO_TPM PassThroughToTPM;
        EFI_TCG_HASH_LOG_EXTEND_EVENT HashLogExtendEvent;
} EFI_TCG;

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

typedef struct tdEFI_TCG2_PROTOCOL EFI_TCG2_PROTOCOL;

typedef UINT32 EFI_TCG2_EVENT_LOG_BITMAP;
typedef UINT32 EFI_TCG2_EVENT_LOG_FORMAT;
typedef UINT32 EFI_TCG2_EVENT_ALGORITHM_BITMAP;

#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2       0x00000001
#define EFI_TCG2_EVENT_LOG_FORMAT_TCG_2         0x00000002

typedef struct tdEFI_TCG2_BOOT_SERVICE_CAPABILITY {
        UINT8 Size;
        EFI_TCG2_VERSION StructureVersion;
        EFI_TCG2_VERSION ProtocolVersion;
        EFI_TCG2_EVENT_ALGORITHM_BITMAP HashAlgorithmBitmap;
        EFI_TCG2_EVENT_LOG_BITMAP SupportedEventLogs;
        BOOLEAN TPMPresentFlag;
        UINT16 MaxCommandSize;
        UINT16 MaxResponseSize;
        UINT32 ManufacturerID;
        UINT32 NumberOfPCRBanks;
        EFI_TCG2_EVENT_ALGORITHM_BITMAP ActivePcrBanks;
} EFI_TCG2_BOOT_SERVICE_CAPABILITY;

#define EFI_TCG2_EVENT_HEADER_VERSION  1

typedef struct {
        UINT32 HeaderSize;
        UINT16 HeaderVersion;
        UINT32 PCRIndex;
        UINT32 EventType;
} __attribute__ ((packed)) EFI_TCG2_EVENT_HEADER;

typedef struct tdEFI_TCG2_EVENT {
        UINT32 Size;
        EFI_TCG2_EVENT_HEADER Header;
        UINT8 Event[1];
} __attribute__ ((packed)) EFI_TCG2_EVENT;

typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_CAPABILITY) (IN EFI_TCG2_PROTOCOL * This,
                                                      IN OUT EFI_TCG2_BOOT_SERVICE_CAPABILITY * ProtocolCapability);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_EVENT_LOG) (IN EFI_TCG2_PROTOCOL * This,
                                                     IN EFI_TCG2_EVENT_LOG_FORMAT EventLogFormat,
                                                     OUT EFI_PHYSICAL_ADDRESS * EventLogLocation,
                                                     OUT EFI_PHYSICAL_ADDRESS * EventLogLastEntry,
                                                     OUT BOOLEAN * EventLogTruncated);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_HASH_LOG_EXTEND_EVENT) (IN EFI_TCG2_PROTOCOL * This,
                                                             IN UINT64 Flags,
                                                             IN EFI_PHYSICAL_ADDRESS DataToHash,
                                                             IN UINT64 DataToHashLen, IN EFI_TCG2_EVENT * EfiTcgEvent);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_SUBMIT_COMMAND) (IN EFI_TCG2_PROTOCOL * This,
                                                      IN UINT32 InputParameterBlockSize,
                                                      IN UINT8 * InputParameterBlock,
                                                      IN UINT32 OutputParameterBlockSize, IN UINT8 * OutputParameterBlock);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, OUT UINT32 * ActivePcrBanks);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This, IN UINT32 ActivePcrBanks);

typedef EFI_STATUS(EFIAPI * EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS) (IN EFI_TCG2_PROTOCOL * This,
                                                                          OUT UINT32 * OperationPresent, OUT UINT32 * Response);

typedef struct tdEFI_TCG2_PROTOCOL {
        EFI_TCG2_GET_CAPABILITY GetCapability;
        EFI_TCG2_GET_EVENT_LOG GetEventLog;
        EFI_TCG2_HASH_LOG_EXTEND_EVENT HashLogExtendEvent;
        EFI_TCG2_SUBMIT_COMMAND SubmitCommand;
        EFI_TCG2_GET_ACTIVE_PCR_BANKS GetActivePcrBanks;
        EFI_TCG2_SET_ACTIVE_PCR_BANKS SetActivePcrBanks;
        EFI_TCG2_GET_RESULT_OF_SET_ACTIVE_PCR_BANKS GetResultOfSetActivePcrBanks;
} EFI_TCG2;


static EFI_STATUS tpm1_measure_to_pcr_and_event_log(const EFI_TCG *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer,
                                                    UINTN buffer_size, const CHAR16 *description) {
        EFI_STATUS status;
        TCG_PCR_EVENT *tcg_event;
        UINT32 event_number;
        EFI_PHYSICAL_ADDRESS event_log_last;
        UINTN desc_len;

        desc_len = (StrLen(description) + 1) * sizeof(CHAR16);

        tcg_event = AllocateZeroPool(desc_len + sizeof(TCG_PCR_EVENT));

        if (!tcg_event)
                return EFI_OUT_OF_RESOURCES;

        tcg_event->EventSize = desc_len;
        CopyMem((VOID *) & tcg_event->Event[0], (VOID *) description, desc_len);

        tcg_event->PCRIndex = pcrindex;
        tcg_event->EventType = EV_IPL;

        event_number = 1;
        status = uefi_call_wrapper(tcg->HashLogExtendEvent, 7,
                                   (EFI_TCG *) tcg, buffer, buffer_size, TCG_ALG_SHA, tcg_event, &event_number, &event_log_last);

        if (EFI_ERROR(status))
                return status;

        uefi_call_wrapper(BS->FreePool, 1, tcg_event);

        return EFI_SUCCESS;
}

/*
 * According to TCG EFI Protocol Specification for TPM 2.0 family,
 * all events generated after the invocation of EFI_TCG2_GET_EVENT_LOG
 * shall be stored in an instance of an EFI_CONFIGURATION_TABLE aka
 * EFI TCG 2.0 final events table. Hence, it is necessary to trigger the
 * internal switch through calling get_event_log() in order to allow
 * to retrieve the logs from OS runtime.
 */
static EFI_STATUS trigger_tcg2_final_events_table(const EFI_TCG2 *tcg, EFI_TCG2_EVENT_LOG_FORMAT log_fmt)
{
        EFI_PHYSICAL_ADDRESS loc;
        EFI_PHYSICAL_ADDRESS last_loc;
        BOOLEAN truncated;
        return uefi_call_wrapper(tcg->GetEventLog, 5, (EFI_TCG2 *) tcg,
                                 log_fmt, &loc, &last_loc, &truncated);
}

static EFI_STATUS tpm2_measure_to_pcr_and_event_log(const EFI_TCG2 *tcg, UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer,
                                                    UINT64 buffer_size, const CHAR16 *description, EFI_TCG2_EVENT_LOG_FORMAT log_fmt) {
        EFI_STATUS status;
        EFI_TCG2_EVENT *tcg_event;
        UINTN desc_len;
        static BOOLEAN triggered = FALSE;

        if (triggered == FALSE) {
                status = trigger_tcg2_final_events_table(tcg, log_fmt);
                if (EFI_ERROR(status))
                        return status;

                triggered = TRUE;
        }

        desc_len = StrLen(description) * sizeof(CHAR16);

        tcg_event = AllocateZeroPool(sizeof(*tcg_event) - sizeof(tcg_event->Event) + desc_len + 1);

        if (!tcg_event)
                return EFI_OUT_OF_RESOURCES;

        tcg_event->Size = sizeof(*tcg_event) - sizeof(tcg_event->Event) + desc_len + 1;
        tcg_event->Header.HeaderSize = sizeof(EFI_TCG2_EVENT_HEADER);
        tcg_event->Header.HeaderVersion = EFI_TCG2_EVENT_HEADER_VERSION;
        tcg_event->Header.PCRIndex = pcrindex;
        tcg_event->Header.EventType = EV_IPL;

        CopyMem((VOID *) tcg_event->Event, (VOID *) description, desc_len);

        status = uefi_call_wrapper(tcg->HashLogExtendEvent, 5, (EFI_TCG2 *) tcg, 0, buffer, (UINT64) buffer_size, tcg_event);

        uefi_call_wrapper(BS->FreePool, 1, tcg_event);

        if (EFI_ERROR(status))
                return status;

        return EFI_SUCCESS;
}

static EFI_TCG * tcg1_interface_check(void) {
        EFI_GUID tpm_guid = EFI_TCG_PROTOCOL_GUID;
        EFI_STATUS status;
        EFI_TCG *tcg;
        TCG_BOOT_SERVICE_CAPABILITY capability;
        UINT32 features;
        EFI_PHYSICAL_ADDRESS event_log_location;
        EFI_PHYSICAL_ADDRESS event_log_last_entry;

        status = LibLocateProtocol(&tpm_guid, (void **) &tcg);

        if (EFI_ERROR(status))
                return NULL;

        capability.Size = (UINT8) sizeof(capability);
        status = uefi_call_wrapper(tcg->StatusCheck, 5, tcg, &capability, &features, &event_log_location, &event_log_last_entry);

        if (EFI_ERROR(status))
                return NULL;

        if (capability.TPMDeactivatedFlag)
                return NULL;

        if (!capability.TPMPresentFlag)
                return NULL;

        return tcg;
}

static EFI_TCG2 * tcg2_interface_check(EFI_TCG2_BOOT_SERVICE_CAPABILITY *caps) {
        EFI_GUID tpm2_guid = EFI_TCG2_PROTOCOL_GUID;
        EFI_STATUS status;
        EFI_TCG2 *tcg;

        status = LibLocateProtocol(&tpm2_guid, (void **) &tcg);

        if (EFI_ERROR(status))
                return NULL;

        caps->Size = (UINT8) sizeof(EFI_TCG2_BOOT_SERVICE_CAPABILITY);
        status = uefi_call_wrapper(tcg->GetCapability, 2, tcg, caps);

        if (EFI_ERROR(status))
                return NULL;

        if (caps->StructureVersion.Major == 1 &&
            caps->StructureVersion.Minor == 0) {
                TCG_BOOT_SERVICE_CAPABILITY *caps_1_0;
                caps_1_0 = (TCG_BOOT_SERVICE_CAPABILITY *)caps;
                if (caps_1_0->TPMPresentFlag)
                        return tcg;
        }

        if (!caps->TPMPresentFlag)
                return NULL;

        return tcg;
}

EFI_STATUS tpm_log_event(UINT32 pcrindex, const EFI_PHYSICAL_ADDRESS buffer, UINTN buffer_size, const CHAR16 *description) {
        EFI_TCG *tpm1;
        EFI_TCG2 *tpm2;
        EFI_TCG2_BOOT_SERVICE_CAPABILITY caps;

        tpm2 = tcg2_interface_check(&caps);
        if (tpm2) {
                EFI_TCG2_EVENT_LOG_BITMAP supported_logs;
                EFI_TCG2_EVENT_LOG_FORMAT log_fmt;

                if (caps.StructureVersion.Major == 1 &&
                    caps.StructureVersion.Minor == 0)
                        supported_logs = ((TREE_BOOT_SERVICE_CAPABILITY *)&caps)->SupportedEventLogs;
                else
                        supported_logs = caps.SupportedEventLogs;

                if (supported_logs & EFI_TCG2_EVENT_LOG_FORMAT_TCG_2)
                        log_fmt = EFI_TCG2_EVENT_LOG_FORMAT_TCG_2;
                else
                        log_fmt = EFI_TCG2_EVENT_LOG_FORMAT_TCG_1_2;

                uefi_call_wrapper(BS->Stall, 1, 2000 * 1000);
                return tpm2_measure_to_pcr_and_event_log(tpm2, pcrindex, buffer, buffer_size, description, log_fmt);
        }

        tpm1 = tcg1_interface_check();
        if (tpm1)
                return tpm1_measure_to_pcr_and_event_log(tpm1, pcrindex, buffer, buffer_size, description);

        /* No active TPM found, so don't return an error */
        return EFI_SUCCESS;
}

#endif
