//
// This file contains a 'Sample Driver' and is licensed as such
// under the terms of your license agreement with Intel or your
// vendor.  This file may be modified by the user, subject to
// the additional terms of the license agreement
//
/**
  Manage trusted certificate database in KeyEnroll tool.

Copyright (c) 2017 - 2018, Intel Corporation. All rights reserved.<BR>
This software and associated documentation (if any) is furnished
under a license and may only be used or copied in accordance
with the terms of the license. Except as permitted by such
license, no part of this software or documentation may be
reproduced, stored in a retrieval system, or transmitted in any
form or by any means without the express written consent of
Intel Corporation.

**/

#include "AuthVarCertDB.h"

extern LIST_ENTRY mVarListEntry;
EFI_GUID          gEfiCertDbGuid  = { 0xd9bee56e, 0x75dc, 0x49d9, { 0xb4, 0xd7, 0xb5, 0x34, 0x21, 0xf, 0x63, 0x7a } };
CONST UINT32      gEfiCertDBAttr  = EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS |
                                    EFI_VARIABLE_NON_VOLATILE |
                                    EFI_VARIABLE_RUNTIME_ACCESS |
                                    EFI_VARIABLE_BOOTSERVICE_ACCESS;

/*
  Function to get "certdb" variable according to Attributes parameter.

  @param[in]    Attributes              Attributes of authenticated variable.
  @param[out]   CertDBPtr               Point to "certdb" variable buffer.

  @retval       EFI_SUCCESS             Find matching certs and output parameters.
  @retval       EFI_INVALID_PARAMETER   CertDBPtr is NULL or Attributes is invalid.
  @retval       EFI_SECURITY_VIOLATION  CertDB structure has been corrupted.
  @retval       EFI_NOT_FOUND           Fail to find matching certs.
*/
EFI_STATUS
GetCertDB (
  IN    UINT32              Attributes,
  OUT   VOID                **CertDBPtr
  )
{
  CHAR16                    *CertDBName;
  VARIABLE_INFO_PRIVATE     *VariableInfo;

  if (CertDBPtr == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) {
    CertDBName = EFI_CERT_DB;
  } else {
    Error (NULL, 0, 0, "Just support EFI_VARIABLE_NON_VOLATILE attribute.", NULL);
    return EFI_INVALID_PARAMETER;
  }

  VariableInfo = FindVariableInfoPtr (CertDBName, &gEfiCertDbGuid);
  if (VariableInfo == NULL || VariableInfo->Size == 0) {
    return EFI_NOT_FOUND;
  }

  if (*(UINT32 *)(VariableInfo->Buffer) != VariableInfo->Size) {
    Error (NULL, 0, 0, "CertDB corrupt.", NULL);
    return EFI_SECURITY_VIOLATION;
  }

  *CertDBPtr = VariableInfo;
  return EFI_SUCCESS;
}

/**
  Find matching signer's certificates for common authenticated variable
  by corresponding VariableName and VendorGuid from "certdb".

  @param[in]    VariableName            Name of authenticated Variable.
  @param[in]    VendorGuid              Vendor GUID of authenticated Variable.
  @param[in]    Data                    Pointer to variable "certdb".
  @param[in]    DataSize                Size of variable "certdb".
  @param[out]   CertNodeOffset          Offset of matching AUTH_CERT_DB_DATA, from starting of Data.
  @param[out]   CertNodeSize            Length of AUTH_CERT_DB_DATA in bytes.
  @param[out]   CertDataPtr             Pointer to the matching CertData.
  @param[out]   CertDataSize            Lenght of CertData in bytes.

  @retval       EFI_INVALID_PARAMETER   Any input parameter is invalid.
  @retval       EFI_NOT_FOUND           Fail to find matching certs.
  @retval       EFI_SUCCESS             Find matching certs and output parameters.
**/
EFI_STATUS
FindCertsFromDb (
  IN      CHAR16            *VariableName,
  IN      EFI_GUID          *VendorGuid,
  IN      UINT32            Attributes,
  OUT     UINT32            *CertNodeOffset   OPTIONAL,
  OUT     UINT32            *CertNodeSize     OPTIONAL,
  OUT     UINT8             **CertDataPtr     OPTIONAL,
  OUT     UINT32            *CertDataSize     OPTIONAL
  )
{
  EFI_STATUS              Status;
  VARIABLE_INFO_PRIVATE   *VariableInfo;
  AUTH_CERT_DB_DATA       *Ptr;
  UINT32                  CertDBListSize;
  UINT32                  Offset;
  UINT32                  NodeSize;
  UINT32                  NameSize;
  UINT32                  DataSize;

  Status                  = EFI_SUCCESS;

  if ((VariableName == NULL) || (VendorGuid == NULL)) {
    return EFI_INVALID_PARAMETER;
  }

  Status = GetCertDB (Attributes, (VOID **)&VariableInfo);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (VariableInfo->Buffer == NULL || VariableInfo->Size == 0) {
    return EFI_NOT_FOUND;
  }

  if (VariableInfo->Size != *((UINT32 *)VariableInfo->Buffer)) {
    return EFI_NOT_FOUND;
  }

  CertDBListSize = VariableInfo->Size;
  Offset = sizeof (UINT32);

  //
  //  Traverse CertDB and try to matching certs.
  //
  while (Offset < CertDBListSize) {
    Ptr = (AUTH_CERT_DB_DATA *) ((UINT8 *)VariableInfo->Buffer + Offset);

    NodeSize = ReadUnaligned32 (&Ptr->CertNodeSize);
    NameSize = ReadUnaligned32 (&Ptr->NameSize);
    DataSize = ReadUnaligned32 (&Ptr->CertDataSize);

    if (NodeSize != sizeof (EFI_GUID) + sizeof (UINT32) * 3 + DataSize + NameSize * sizeof (CHAR16)) {
        return EFI_NOT_FOUND;
    }

    Offset = Offset + sizeof (EFI_GUID) + sizeof (UINT32) * 3;
    if ((CompareGuid (&Ptr->VendorGuid, VendorGuid) == 0) &&
        (NameSize == KeyEnrollStrLen (VariableName)) &&
        (CompareMem (VariableName, VariableInfo->Buffer + Offset, NameSize * sizeof (CHAR16)) == 0)
       ) {
      if (CertNodeOffset != NULL) {
        *CertNodeOffset = (UINT32) ((UINT8 *)Ptr - VariableInfo->Buffer);
      }
      if (CertNodeSize != NULL) {
        *CertNodeSize = NodeSize;
      }
      if (CertDataPtr != NULL) {
        *CertDataPtr = (UINT8 *)Ptr + sizeof (EFI_GUID) + sizeof (UINT32) * 3 + NameSize * sizeof (CHAR16);
      }
      if (CertDataSize != NULL) {
        *CertDataSize = DataSize;
      }
      return EFI_SUCCESS;
    }
    Offset = Offset + NameSize * sizeof (CHAR16) + DataSize;
  }

  return EFI_NOT_FOUND;
}

/**
  Delete matching signer's certificates when deleting common authenticated
  variable by corresponding VariableName and VendorGuid from "certdb".

  @param[in]    VariableName            Name of authenticated Variable.
  @param[in]    VendorGuid              Vendor GUID of authenticated Variable.
  @param[in]    Attributes              Attributes of authenticated variable.

  @retval       EFI_INVALID_PARAMETER   Any input parameter is invalid.
  @retval       EFI_NOT_FOUND           Fail to find "certdb" or matching certs.
  @retval       EFI_OUT_OF_RESOURCES    The operation is failed due to lack of resources.
  @retval       EFI_SUCCESS             The operation is completed successfully.
**/
EFI_STATUS
DeleteCertsFromDb (
  IN      CHAR16            *VariableName,
  IN      EFI_GUID          *VendorGuid,
  IN      UINT32            Attributes
  )
{
  EFI_STATUS              Status;
  VARIABLE_INFO_PRIVATE   *VariableInfo;
  UINT32                  CertNodeOffset;
  UINT32                  CertNodeSize;
  UINT8                   *NewBuffer;
  UINTN                   NewBufferSize;

  Status                  = EFI_SUCCESS;
  VariableInfo            = NULL;
  NewBuffer               = NULL;
  CertNodeOffset          = 0;
  CertNodeSize            = 0;

  if (VariableName == NULL || VendorGuid == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Status = GetCertDB (Attributes, (VOID **)&VariableInfo);
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Status = FindCertsFromDb (
             VariableName,
             VendorGuid,
             Attributes,
             &CertNodeOffset,
             &CertNodeSize,
             NULL,
             NULL
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  NewBufferSize = VariableInfo->Size - CertNodeSize;

  //
  //  If the NewBufferSize == 4, it means no AUTH_CERT_DB_DATA in "certDB".
  //
  if (NewBufferSize == 4) {
    return DeleteVariableList (&mVarListEntry, VariableInfo->Name, &gEfiCertDbGuid);
  }

  NewBuffer = (UINT8 *)malloc (NewBufferSize);
  if (NewBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  //  Copy first-part from old buffer to new buffer.
  //
  CopyMem (NewBuffer, VariableInfo->Buffer, CertNodeOffset);
  //
  //  Copy second-part to new buffer.
  //
  CopyMem (
    NewBuffer + CertNodeOffset,
    VariableInfo->Buffer + CertNodeOffset + CertNodeSize,
    VariableInfo->Size - CertNodeOffset - CertNodeSize
    );

  Status = CreateVariableList (
             &mVarListEntry,
             VariableInfo->Name,
             &gEfiCertDbGuid,
             gEfiCertDBAttr,
             NULL,
             NewBufferSize,
             NewBuffer,
             FALSE
             );

  FREE_NON_NULL_PTR (NewBuffer);
  return Status;
}

/**
  Insert signer's certificates for common authenticated variable
  with VariableName and VendorGuid in AUTH_CERT_DB_DATA to "certdb".

  @param[in]  VariableName      Name of authenticated Variable.
  @param[in]  VendorGuid        Vendor GUID of authenticated Variable.
  @param[in]  Attributes        Attributes of authenticated variable.
  @param[in]  SignerCert        Signer certificate data.
  @param[in]  SignerCertSize    Length of signer certificate.
  @param[in]  TopLevelCert      Top-level certificate data.
  @param[in]  TopLevelCertSize  Length of top-level certificate.


  @retval  EFI_INVALID_PARAMETER Any input parameter is invalid.
  @retval  EFI_ACCESS_DENIED     An AUTH_CERT_DB_DATA entry with same VariableName
                                 and VendorGuid already exists.
  @retval  EFI_OUT_OF_RESOURCES  The operation is failed due to lack of resources.
  @retval  EFI_SUCCESS           Insert an AUTH_CERT_DB_DATA entry to "certdb" or "certdbv"
**/
EFI_STATUS
InsertCertsToDb (
  IN      CHAR16            *VariableName,
  IN      EFI_GUID          *VendorGuid,
  IN      UINT32            Attributes,
  IN      UINT8             *SignerCert,
  IN      UINTN             SignerCertSize,
  IN      UINT8             *TopLevelCert,
  IN      UINTN             TopLevelCertSize
  )
{
  EFI_STATUS                Status;
  UINT8                     Sha256Digest[SHA256_DIGEST_SIZE];
  UINT8                     *NewBuffer;
  UINTN                     NewBufferSize;
  UINT32                    NameSize;
  UINT32                    NodeSize;
  UINT32                    DataSize;
  UINT32                    Offset;
  CHAR16                    *CertDBName;

  Status                    = EFI_SUCCESS;
  NewBuffer                 = NULL;

  if (VariableName == NULL || VendorGuid == NULL || SignerCert == NULL || TopLevelCert == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) {
    CertDBName = EFI_CERT_DB;
  } else {
    Error (NULL, 0, 0, "Just support EFI_VARIABLE_NON_VOLATILE attribute.", NULL);
    return EFI_INVALID_PARAMETER;
  }

  Status = FindCertsFromDb (VariableName, VendorGuid, Attributes, NULL, NULL, NULL, NULL);
  if (Status == EFI_SUCCESS) {
    return EFI_SECURITY_VIOLATION;
  } else if (EFI_ERROR (Status) && Status != EFI_NOT_FOUND) {
    return Status;
  }

  Status = CalculatePrivAuthVarSignChainSHA256Digest (
             SignerCert,
             SignerCertSize,
             TopLevelCert,
             TopLevelCertSize,
             Sha256Digest
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  ///
  /// AUTH_CERT_DB_DATA format:
  ///
  /// EFI_GUID    VendorGuid;
  /// UINT32      CertNodeSize;
  /// UINT32      NameSize;
  /// UINT32      CertDataSize;
  /// CHAR16      VariableName[NameSize];
  /// UINT8       CertData[CertDataSize];
  ///

  NameSize = (UINT32)(KeyEnrollStrLen (VariableName));
  DataSize = SHA256_DIGEST_SIZE;
  NodeSize = sizeof (AUTH_CERT_DB_DATA) + NameSize * sizeof (CHAR16) + DataSize;

  NewBufferSize = sizeof (UINT32) + NodeSize;

  NewBuffer = (UINT8 *)malloc (NewBufferSize);
  if (NewBuffer == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  Offset = 0;
  CopyMem (NewBuffer + Offset, &NewBufferSize, sizeof (UINT32));
  Offset = Offset + sizeof (UINT32);

  CopyMem (NewBuffer + Offset, VendorGuid, sizeof (EFI_GUID));
  Offset = Offset + sizeof (EFI_GUID);

  CopyMem (NewBuffer + Offset, &NodeSize, sizeof (UINT32));
  Offset = Offset + sizeof (UINT32);

  CopyMem (NewBuffer + Offset, &NameSize, sizeof (UINT32));
  Offset = Offset + sizeof (UINT32);

  CopyMem (NewBuffer + Offset, &DataSize, sizeof (UINT32));
  Offset = Offset + sizeof (UINT32);

  CopyMem (NewBuffer + Offset, VariableName, NameSize * sizeof (CHAR16));
  Offset = Offset + NameSize * sizeof (CHAR16);

  CopyMem (NewBuffer + Offset, Sha256Digest, DataSize);

  Status = CreateVariableList (
             &mVarListEntry,
             CertDBName,
             &gEfiCertDbGuid,
             gEfiCertDBAttr,
             NULL,
             NewBufferSize,
             NewBuffer,
             FALSE
             );

  FREE_NON_NULL_PTR (NewBuffer);
  return Status;
}
