//
// 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 & verify Timebased Authenticated varaibles 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 "AuthVariable.h"

extern LIST_ENTRY mVarListEntry;
extern EFI_GUID   gEfiCertTypePkcs7Guid;
extern UINT8      mHashOidValueSha256;

/*
  Function will split the "Buffer" parameter and getting the Auth2
  descriptor, and doing verification.

  @param[in]  Attributes                  Attribute value of the variable.
  @param[in]  Buffer                      Data pointer.
  @param[in]  BufferSize                  The size of Data in bytes.
  @param[in]  OrgTimeStamp                Pointer to original time stamp,
                                          original variable is not found if NULL.

  @retval     EFI_SECURITY_VIOLATION      Verification fail.
  @retval     EFI_SUCCESS                 Operation success.
*/
EFI_STATUS
VerifyAuth2Descriptor (
  IN    UINT32                        Attributes,
  IN    VOID                          *Buffer,
  IN    UINTN                         BufferSize,
  IN    EFI_TIME                      *OrgTimeStamp,
  OUT   UINT8                         **CertDataPtr,
  OUT   UINT32                        *CertDataSize
  )
{
  EFI_VARIABLE_AUTHENTICATION_2   *Auth2Ptr;
  EFI_TIME                        *Auth2TimeStamp;

  if (Buffer == NULL || CertDataPtr == NULL || CertDataSize == NULL) {
    return EFI_INVALID_PARAMETER;
  }

  Auth2Ptr = (EFI_VARIABLE_AUTHENTICATION_2 *)Buffer;

  Auth2TimeStamp = &Auth2Ptr->TimeStamp;
  if ( (Auth2TimeStamp->Pad1 != 0) ||
       (Auth2TimeStamp->Pad2 != 0) ||
       (Auth2TimeStamp->Nanosecond != 0) ||
       (Auth2TimeStamp->TimeZone != 0) ||
       (Auth2TimeStamp->Daylight !=0)
     ) {
    return EFI_SECURITY_VIOLATION;
  }

  if ((OrgTimeStamp != NULL) && ((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0)) {
    //
    // TimeStamp check fail, suspicious replay attack, return EFI_SECURITY_VIOLATION.
    //
    if (CompareTimeStamp (Auth2TimeStamp, OrgTimeStamp)) {
      return EFI_SECURITY_VIOLATION;
    }
  }

  if ( (Auth2Ptr->AuthInfo.Hdr.wCertificateType != WIN_CERT_TYPE_EFI_GUID) ||
       (CompareGuid (&Auth2Ptr->AuthInfo.CertType, &gEfiCertTypePkcs7Guid))
     ) {
    //
    // Invalid AuthInfo type, return EFI_SECURITY_VIOLATION.
    //
    return EFI_SECURITY_VIOLATION;
  }

  *CertDataPtr = ((UINT8 *)&Auth2Ptr->AuthInfo.CertType) + sizeof (EFI_GUID);
  *CertDataSize = Auth2Ptr->AuthInfo.Hdr.dwLength - (UINT32)(OFFSET_OF (WIN_CERTIFICATE_UEFI_GUID, CertType)) - sizeof (EFI_GUID);

  if ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) != 0) {
    if (*CertDataSize >= (13 + sizeof (mHashOidValueSha256))) {
      if ( ((*(((UINT8 *)&Auth2Ptr->AuthInfo.CertType) + sizeof (EFI_GUID) + 1) & TWO_BYTE_ENCODE) != TWO_BYTE_ENCODE) ||
            (CompareMem (*CertDataPtr + 13, &mHashOidValueSha256, sizeof (mHashOidValueSha256)) != 0)
         ) {
        return EFI_SECURITY_VIOLATION;
      }
    }
  }

  return EFI_SUCCESS;
}

/*
  This function will according to the opeartion type to processing the
  certs of auth variable.

  @param[in]  VariableName                Name of Variable to be found.
  @param[in]  VendorGuid                  Variable vendor GUID.
  @param[in]  Attributes                  Attribute value of the variable.
  @param[in]  OperationType               Indicate the operation of auth variable.
  @param[in]  PayloadSize                 Indicate the size of payload in bytes.
  @param[in]  SignerCert                  A pointer to SignerCert data.
  @param[in]  SignerCertSize              Length of SignerCert data.
  @param[in]  TopLevelCert                A pointer to TopLevelCert data.
  @param[in]  TopLevelCertSize            Length of TopLevelCert data.

  @retval     EFI_INVALID_PARAMETER       Invalid input parameters.
  @retval     EFI_SECURITY_VIOLATION      Verify or update certs failed.
  @retval     EFI_SUCCESS                 The operation is finished successfully.
  @retval     Others                      Other errors as indicated.
*/
EFI_STATUS
VerifyCertsAndUpdate (
  IN    CHAR16                        *VariableName,
  IN    EFI_GUID                      *VendorGuid,
  IN    UINT32                        Attributes,
  IN    EFI_COMMAND_OPER_TYPE         OperationType,
  IN    UINT32                        PayloadSize,
  IN    UINT8                         *SignerCert,
  IN    UINT32                        SignerCertSize,
  IN    UINT8                         *TopLevelCert,
  IN    UINT32                        TopLevelCertSize
  )
{
  EFI_STATUS                          Status;
  EFI_CERT_DATA                       *TempPtr;
  UINT8                               *CertDataPtr;
  UINT32                              CertDataSize;
  UINT8                               Sha256Digest[SHA256_DIGEST_SIZE];
  VARIABLE_INFO_PRIVATE               *PriVarInfo;

  Status                              = EFI_SUCCESS;
  TempPtr                             = NULL;
  CertDataPtr                         = NULL;
  CertDataSize                        = 0;

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

  PriVarInfo = FindVariableInfoPtr (VariableName, VendorGuid);

  /*
    Add:
      1. Create: certdb, variable.    OrgTimeStamp == NULL, payloadsize != 0.
      2. Modify: variable.            OrgTimeStamp != NULL, payloadsize != 0.

    Del:
      1. Delete: certdb, variable.    payloadsize == 0.

    Append:(No update timestamp)
      1. Create: certdb, variable.    OrgTimeStamp == NULL, payloadsize != 0.
      2. Modify: variable.            OrgTimeStamp != NULL, payloadsize != 0.
  */

  //
  //  Specified behavior is not matching the payload buffer.
  //  In this situation will delete certs from certdb but add variable.
  //
  if (OperationType == VariableTypeDel && PayloadSize != 0) {
    Error(NULL, 0, 0, "Operation type is not matching the .bin file.", NULL);
    return EFI_SECURITY_VIOLATION;
  }

  //
  //  Specified behavior is not matching the payload buffer.
  //  In this situation will add certs into certdb but delete variable.
  //
  if ( (OperationType == VariableTypeAdd || OperationType == VariableTypeAppend) &&
       (PayloadSize == 0)
     ) {
    Error(NULL, 0, 0, "Operation type is not matching the .bin file.", NULL);
    return EFI_SECURITY_VIOLATION;
  }

  if ((OperationType == VariableTypeAdd || OperationType == VariableTypeAppend) && (PriVarInfo == NULL)) {
     TempPtr = (EFI_CERT_DATA *)(SignerCert + 1);
     Status = InsertCertsToDb (
                VariableName,
                VendorGuid,
                Attributes,
                TempPtr->CertDataBuffer,
                ReadUnaligned32 ((UINT32 *)&TempPtr->CertDataLength),
                TopLevelCert,
                TopLevelCertSize
                );
      if (EFI_ERROR (Status)) {
        return Status;
      }
  } else {
    Status = FindCertsFromDb (
               VariableName,
               VendorGuid,
               Attributes,
               NULL,
               NULL,
               &CertDataPtr,
               &CertDataSize
               );
    if (EFI_ERROR (Status)) {
      return Status;
    }

    if (CertDataSize == SHA256_DIGEST_SIZE) {
      TempPtr = (EFI_CERT_DATA *)(SignerCert + 1);
      Status = CalculatePrivAuthVarSignChainSHA256Digest (
                 TempPtr->CertDataBuffer,
                 ReadUnaligned32 ((UINT32 *)&TempPtr->CertDataLength),
                 TopLevelCert,
                 TopLevelCertSize,
                 Sha256Digest
                 );
      if (EFI_ERROR (Status)) {
        return Status;
      }

      if (CompareMem (Sha256Digest, CertDataPtr, CertDataSize) != 0) {
        return EFI_SECURITY_VIOLATION;
      }
    } else {
      if (CertDataSize != TopLevelCertSize) {
        return EFI_SECURITY_VIOLATION;
      }

      if (CompareMem (SignerCert, CertDataPtr, CertDataSize) != 0) {
        return EFI_SECURITY_VIOLATION;
      }
    }
  }

  return EFI_SUCCESS;
}

/**
  Process variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set

  @param[in]  VariableName                Name of Variable to be found.
  @param[in]  VendorGuid                  Variable vendor GUID.
  @param[in]  Attributes                  Attribute value of the variable.
  @param[in]  Buffer                      Data pointer.
  @param[in]  BufferSize                  Size of Data found. If size is less than the
                                          data, this value contains the required size.
  @param[in]  OrgTimeStamp                Pointer to original time stamp,
                                          original variable is not found if NULL.
  @param[in]  OperationType               Indicates how to process the auth variable.
  @param[out] VarPayloadPtr               Pointer to variable payload address.
  @param[out] VarPayloadSize              Pointer to variable payload size.
  @param[out] CertTimeStamp               Pointer to timestamp of auth2 structure.

  @retval EFI_INVALID_PARAMETER           Invalid parameter.
  @retval EFI_SECURITY_VIOLATION          The variable does NOT pass the validation
                                          check carried out by the firmware.
  @retval EFI_OUT_OF_RESOURCES            Failed to process variable due to lack
                                          of resources.
  @retval EFI_SUCCESS                     Variable pass validation successfully.
**/
EFI_STATUS
Auth2VerifyAndUpdate (
  IN    CHAR16                        *VariableName,
  IN    EFI_GUID                      *VendorGuid,
  IN    UINT32                        Attributes,
  IN    VOID                          *Buffer,
  IN    UINTN                         BufferSize,
  IN    EFI_TIME                      *OrgTimeStamp,
  IN    EFI_COMMAND_OPER_TYPE         OperationType,
  OUT   UINT8                         **VarPayloadPtr,
  OUT   UINTN                         *VarPayloadSize,
  OUT   EFI_TIME                      **CertTimeStamp
  )
{
  EFI_STATUS                      Status;
  BOOLEAN                         VerifyStatus;
  EFI_VARIABLE_AUTHENTICATION_2   *Auth2Ptr;
  UINT8                           *CertDataPtr;
  UINT32                          CertDataSize;
  UINT8                           *PayloadPtr;
  UINT32                          PayloadSize;
  UINT8                           *Digest;
  UINT32                          DigestSize;
  UINT8                           *SignerCert;
  UINT32                          SignerCertSize;
  UINT8                           *TopLevelCert;
  UINT32                          TopLevelCertSize;
  UINT32                          Offset;
  VARIABLE_INFO_PRIVATE           *PriVarInfo;

  Status                          = EFI_SUCCESS;
  Digest                          = NULL;
  Offset                          = 0;

  PriVarInfo = FindVariableInfoPtr (VariableName, VendorGuid);

  Status = VerifyAuth2Descriptor (
             Attributes,
             Buffer,
             BufferSize,
             PriVarInfo == NULL ? NULL : &PriVarInfo->TimeStamp,
             &CertDataPtr,
             &CertDataSize
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  Auth2Ptr = (EFI_VARIABLE_AUTHENTICATION_2 *)Buffer;

  //
  // Find out the new data payload which follows Pkcs7 SignedData directly.
  //
  PayloadPtr = CertDataPtr + CertDataSize;
  PayloadSize = BufferSize - sizeof (EFI_TIME) - Auth2Ptr->AuthInfo.Hdr.dwLength;

  DigestSize =  PayloadSize +
                KeyEnrollStrLen (VariableName) * sizeof (CHAR16) +
                sizeof (UINT32) +
                sizeof (EFI_TIME) +
                sizeof (EFI_GUID);
  Digest = (UINT8 *)malloc (DigestSize);
  if (Digest == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (Digest + Offset, VariableName, KeyEnrollStrLen (VariableName) * sizeof (CHAR16));
  Offset = Offset + KeyEnrollStrLen (VariableName) * sizeof (CHAR16);

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

  CopyMem (Digest + Offset, &Attributes, sizeof (UINT32));
  Offset = Offset + sizeof (UINT32);

  CopyMem (Digest + Offset, &Auth2Ptr->TimeStamp, sizeof (EFI_TIME));
  Offset = Offset + sizeof (EFI_TIME);

  CopyMem (Digest + Offset, PayloadPtr, PayloadSize);

  VerifyStatus = Pkcs7GetSigners (
                   CertDataPtr,
                   CertDataSize,
                   &SignerCert,
                   (UINTN *)&SignerCertSize,
                   &TopLevelCert,
                   (UINTN *)&TopLevelCertSize
                   );
  if (!VerifyStatus) {
    Error (NULL, 0, 0, "Pkcs7GetSigners Fail.", NULL);
    goto ON_EXIT;
  }

  VerifyStatus = Pkcs7Verify (
                   CertDataPtr,
                   CertDataSize,
                   TopLevelCert,
                   TopLevelCertSize,
                   Digest,
                   DigestSize
                   );
  if (!VerifyStatus) {
    Error (NULL, 0, 0, "Pkcs7Verify Fail.", NULL);
    goto ON_EXIT;
  }

  Status = VerifyCertsAndUpdate (
             VariableName,
             VendorGuid,
             Attributes,
             OperationType,
             PayloadSize,
             SignerCert,
             SignerCertSize,
             TopLevelCert,
             TopLevelCertSize
             );

  if (EFI_ERROR (Status)) {
    VerifyStatus = FALSE;
  }

ON_EXIT:
  Pkcs7FreeSigners (TopLevelCert);
  Pkcs7FreeSigners (SignerCert);
  FREE_NON_NULL_PTR (Digest);

  if (!VerifyStatus) {
    return EFI_SECURITY_VIOLATION;
  }

  //
  // Find out the new data payload which follows Pkcs7 SignedData directly.
  //
  *VarPayloadPtr = CertDataPtr + CertDataSize;
  *VarPayloadSize = BufferSize - sizeof (EFI_TIME) - Auth2Ptr->AuthInfo.Hdr.dwLength;
  *CertTimeStamp = &Auth2Ptr->TimeStamp;

  return EFI_SUCCESS;
}

/*
  Process variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set.

  @param[in]    Name                      Name of Variable to be processed.
  @param[in]    Guid                      Variable vendor GUID.
  @param[in]    Data                      Data pointer.
  @param[in]    DataSize                  The size of Data in bytes.
  @param[in]    Attributes                Attribute value of the variable.
  @param[in]    OperType                  The operation type about variable.

  @retval       EFI_SUCCESS               The operation is finished successfully.
  @retval       EFI_INVALID_PARAMETER     Invalid input parameters, Name, Guid or Data is NULL.
  @retval       EFI_OUT_OF_RESOURCES      No more memory for allocation.
  @retval       Others                    Other errors as indicated.
*/
EFI_STATUS
ProcessAuthVar (
  IN    CHAR8                       *Name,
  IN    EFI_GUID                    *Guid,
  IN    VOID                        *Data,
  IN    UINTN                       DataSize,
  IN    UINT32                      Attributes,
  IN    EFI_COMMAND_OPER_TYPE       OperType
  )
{
  EFI_STATUS                      Status;
  EFI_TIME                        *CertTimeStamp;
  LIST_ENTRY                      *ListEntry;
  VARIABLE_INFO_PRIVATE           *PriVarInfo;
  CHAR16                          *UnicodeVariableName;
  UINT8                           *PayloadPtr;
  UINTN                           PayloadSize;

  Status                          = EFI_SUCCESS;
  CertTimeStamp                   = NULL;
  ListEntry                       = NULL;
  PriVarInfo                      = NULL;
  UnicodeVariableName             = NULL;
  PayloadPtr                      = NULL;
  PayloadSize                     = 0;

  if (Name == NULL || Guid == NULL || Data == NULL || OperType == VariableTypeMax) {
    return EFI_INVALID_PARAMETER;
  }

  UnicodeVariableName = malloc ((strlen (Name) + 1) * sizeof (CHAR16));
  if (UnicodeVariableName == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }
  Ascii2UnicodeString (Name, UnicodeVariableName);

  //
  //  Try to get auth variable.
  //
  ListEntry = FindVariableList (&mVarListEntry, UnicodeVariableName, Guid);

  if (ListEntry != NULL) {
    PriVarInfo = VARIABLE_INFO_PRIVATE_FROM_LINK (ListEntry);
  }

  Status = Auth2VerifyAndUpdate (
             UnicodeVariableName,
             Guid,
             Attributes,
             Data,
             DataSize,
             PriVarInfo == NULL ? NULL : &PriVarInfo->TimeStamp,
             OperType,
             &PayloadPtr,
             &PayloadSize,
             &CertTimeStamp
             );
  if (EFI_ERROR (Status)) {
    return Status;
  }

  if (OperType == VariableTypeDel) {
    Status = DeleteVariableList (&mVarListEntry, UnicodeVariableName, Guid);
    if (Status == EFI_SUCCESS) {
      Status = DeleteCertsFromDb (UnicodeVariableName, Guid, Attributes);
    }
  } else {
    Status = CreateVariableList (
               &mVarListEntry,
               UnicodeVariableName,
               Guid,
               Attributes,
               CertTimeStamp,
               PayloadSize,
               PayloadPtr,
               OperType == VariableTypeAppend
               );
  }

  FREE_NON_NULL_PTR (UnicodeVariableName);
  return Status;
}
