/** @file | |
Implement authentication services for the authenticated variables. | |
Caution: This module requires additional review when modified. | |
This driver will have external input - variable data. It may be input in SMM mode. | |
This external input must be validated carefully to avoid security issue like | |
buffer overflow, integer overflow. | |
Variable attribute should also be checked to avoid authentication bypass. | |
The whole SMM authentication variable design relies on the integrity of flash part and SMM. | |
which is assumed to be protected by platform. All variable code and metadata in flash/SMM Memory | |
may not be modified without authorization. If platform fails to protect these resources, | |
the authentication service provided in this driver will be broken, and the behavior is undefined. | |
ProcessVarWithPk(), ProcessVarWithKek() and ProcessVariable() are the function to do | |
variable authentication. | |
VerifyTimeBasedPayloadAndUpdate() and VerifyCounterBasedPayload() are sub function to do verification. | |
They will do basic validation for authentication data structure, then call crypto library | |
to verify the signature. | |
Copyright (c) 2009 - 2019, Intel Corporation. All rights reserved.<BR> | |
Copyright (c) Microsoft Corporation. | |
SPDX-License-Identifier: BSD-2-Clause-Patent | |
**/ | |
#include "AuthServiceInternal.h" | |
#include <Protocol/VariablePolicy.h> | |
#include <Library/VariablePolicyLib.h> | |
#define SHA_DIGEST_SIZE_MAX SHA512_DIGEST_SIZE | |
/** | |
Retrieves the size, in bytes, of the context buffer required for hash operations. | |
If this interface is not supported, then return zero. | |
@return The size, in bytes, of the context buffer required for hash operations. | |
@retval 0 This interface is not supported. | |
**/ | |
typedef | |
UINTN | |
(EFIAPI *EFI_HASH_GET_CONTEXT_SIZE)( | |
VOID | |
); | |
/** | |
Initializes user-supplied memory pointed by Sha1Context as hash context for | |
subsequent use. | |
If HashContext is NULL, then return FALSE. | |
If this interface is not supported, then return FALSE. | |
@param[out] HashContext Pointer to Hashcontext being initialized. | |
@retval TRUE Hash context initialization succeeded. | |
@retval FALSE Hash context initialization failed. | |
@retval FALSE This interface is not supported. | |
**/ | |
typedef | |
BOOLEAN | |
(EFIAPI *EFI_HASH_INIT)( | |
OUT VOID *HashContext | |
); | |
/** | |
Digests the input data and updates Hash context. | |
This function performs Hash digest on a data buffer of the specified size. | |
It can be called multiple times to compute the digest of long or discontinuous data streams. | |
Hash context should be already correctly initialized by HashInit(), and should not be finalized | |
by HashFinal(). Behavior with invalid context is undefined. | |
If HashContext is NULL, then return FALSE. | |
If this interface is not supported, then return FALSE. | |
@param[in, out] HashContext Pointer to the Hash context. | |
@param[in] Data Pointer to the buffer containing the data to be hashed. | |
@param[in] DataSize Size of Data buffer in bytes. | |
@retval TRUE SHA-1 data digest succeeded. | |
@retval FALSE SHA-1 data digest failed. | |
@retval FALSE This interface is not supported. | |
**/ | |
typedef | |
BOOLEAN | |
(EFIAPI *EFI_HASH_UPDATE)( | |
IN OUT VOID *HashContext, | |
IN CONST VOID *Data, | |
IN UINTN DataSize | |
); | |
/** | |
Completes computation of the Hash digest value. | |
This function completes hash computation and retrieves the digest value into | |
the specified memory. After this function has been called, the Hash context cannot | |
be used again. | |
Hash context should be already correctly initialized by HashInit(), and should not be | |
finalized by HashFinal(). Behavior with invalid Hash context is undefined. | |
If HashContext is NULL, then return FALSE. | |
If HashValue is NULL, then return FALSE. | |
If this interface is not supported, then return FALSE. | |
@param[in, out] HashContext Pointer to the Hash context. | |
@param[out] HashValue Pointer to a buffer that receives the Hash digest | |
value. | |
@retval TRUE Hash digest computation succeeded. | |
@retval FALSE Hash digest computation failed. | |
@retval FALSE This interface is not supported. | |
**/ | |
typedef | |
BOOLEAN | |
(EFIAPI *EFI_HASH_FINAL)( | |
IN OUT VOID *HashContext, | |
OUT UINT8 *HashValue | |
); | |
typedef struct { | |
UINT32 HashSize; | |
EFI_HASH_GET_CONTEXT_SIZE GetContextSize; | |
EFI_HASH_INIT Init; | |
EFI_HASH_UPDATE Update; | |
EFI_HASH_FINAL Final; | |
VOID **HashShaCtx; | |
UINT8 *OidValue; | |
UINTN OidLength; | |
} EFI_HASH_INFO; | |
// | |
// Public Exponent of RSA Key. | |
// | |
CONST UINT8 mRsaE[] = { 0x01, 0x00, 0x01 }; | |
UINT8 mSha256OidValue[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 }; | |
UINT8 mSha384OidValue[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 }; | |
UINT8 mSha512OidValue[] = { 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 }; | |
EFI_HASH_INFO mHashInfo[] = { | |
{ SHA256_DIGEST_SIZE, Sha256GetContextSize, Sha256Init, Sha256Update, Sha256Final, &mHashSha256Ctx, mSha256OidValue, 9 }, | |
{ SHA384_DIGEST_SIZE, Sha384GetContextSize, Sha384Init, Sha384Update, Sha384Final, &mHashSha384Ctx, mSha384OidValue, 9 }, | |
{ SHA512_DIGEST_SIZE, Sha512GetContextSize, Sha512Init, Sha512Update, Sha512Final, &mHashSha512Ctx, mSha512OidValue, 9 }, | |
}; | |
// | |
// Requirement for different signature type which have been defined in UEFI spec. | |
// These data are used to perform SignatureList format check while setting PK/KEK variable. | |
// | |
EFI_SIGNATURE_ITEM mSupportSigItem[] = { | |
// {SigType, SigHeaderSize, SigDataSize } | |
{ EFI_CERT_SHA256_GUID, 0, 32 }, | |
{ EFI_CERT_RSA2048_GUID, 0, 256 }, | |
{ EFI_CERT_RSA2048_SHA256_GUID, 0, 256 }, | |
{ EFI_CERT_SHA1_GUID, 0, 20 }, | |
{ EFI_CERT_RSA2048_SHA1_GUID, 0, 256 }, | |
{ EFI_CERT_X509_GUID, 0, ((UINT32) ~0) }, | |
{ EFI_CERT_SHA224_GUID, 0, 28 }, | |
{ EFI_CERT_SHA384_GUID, 0, 48 }, | |
{ EFI_CERT_SHA512_GUID, 0, 64 }, | |
{ EFI_CERT_X509_SHA256_GUID, 0, 48 }, | |
{ EFI_CERT_X509_SHA384_GUID, 0, 64 }, | |
{ EFI_CERT_X509_SHA512_GUID, 0, 80 } | |
}; | |
/** | |
Finds variable in storage blocks of volatile and non-volatile storage areas. | |
This code finds variable in storage blocks of volatile and non-volatile storage areas. | |
If VariableName is an empty string, then we just return the first | |
qualified variable without comparing VariableName and VendorGuid. | |
@param[in] VariableName Name of the variable to be found. | |
@param[in] VendorGuid Variable vendor GUID to be found. | |
@param[out] Data Pointer to data address. | |
@param[out] DataSize Pointer to data size. | |
@retval EFI_INVALID_PARAMETER If VariableName is not an empty string, | |
while VendorGuid is NULL. | |
@retval EFI_SUCCESS Variable successfully found. | |
@retval EFI_NOT_FOUND Variable not found | |
**/ | |
EFI_STATUS | |
AuthServiceInternalFindVariable ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
OUT VOID **Data, | |
OUT UINTN *DataSize | |
) | |
{ | |
EFI_STATUS Status; | |
AUTH_VARIABLE_INFO AuthVariableInfo; | |
ZeroMem (&AuthVariableInfo, sizeof (AuthVariableInfo)); | |
Status = mAuthVarLibContextIn->FindVariable ( | |
VariableName, | |
VendorGuid, | |
&AuthVariableInfo | |
); | |
*Data = AuthVariableInfo.Data; | |
*DataSize = AuthVariableInfo.DataSize; | |
return Status; | |
} | |
/** | |
Update the variable region with Variable information. | |
@param[in] VariableName Name of variable. | |
@param[in] VendorGuid Guid of variable. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data. | |
@param[in] Attributes Attribute value of the variable. | |
@retval EFI_SUCCESS The update operation is success. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_WRITE_PROTECTED Variable is write-protected. | |
@retval EFI_OUT_OF_RESOURCES There is not enough resource. | |
**/ | |
EFI_STATUS | |
AuthServiceInternalUpdateVariable ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes | |
) | |
{ | |
AUTH_VARIABLE_INFO AuthVariableInfo; | |
ZeroMem (&AuthVariableInfo, sizeof (AuthVariableInfo)); | |
AuthVariableInfo.VariableName = VariableName; | |
AuthVariableInfo.VendorGuid = VendorGuid; | |
AuthVariableInfo.Data = Data; | |
AuthVariableInfo.DataSize = DataSize; | |
AuthVariableInfo.Attributes = Attributes; | |
return mAuthVarLibContextIn->UpdateVariable ( | |
&AuthVariableInfo | |
); | |
} | |
/** | |
Update the variable region with Variable information. | |
@param[in] VariableName Name of variable. | |
@param[in] VendorGuid Guid of variable. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data. | |
@param[in] Attributes Attribute value of the variable. | |
@param[in] TimeStamp Value of associated TimeStamp. | |
@retval EFI_SUCCESS The update operation is success. | |
@retval EFI_INVALID_PARAMETER Invalid parameter. | |
@retval EFI_WRITE_PROTECTED Variable is write-protected. | |
@retval EFI_OUT_OF_RESOURCES There is not enough resource. | |
**/ | |
EFI_STATUS | |
AuthServiceInternalUpdateVariableWithTimeStamp ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes, | |
IN EFI_TIME *TimeStamp | |
) | |
{ | |
EFI_STATUS FindStatus; | |
VOID *OrgData; | |
UINTN OrgDataSize; | |
AUTH_VARIABLE_INFO AuthVariableInfo; | |
FindStatus = AuthServiceInternalFindVariable ( | |
VariableName, | |
VendorGuid, | |
&OrgData, | |
&OrgDataSize | |
); | |
// | |
// EFI_VARIABLE_APPEND_WRITE attribute only effects for existing variable | |
// | |
if (!EFI_ERROR (FindStatus) && ((Attributes & EFI_VARIABLE_APPEND_WRITE) != 0)) { | |
if ((CompareGuid (VendorGuid, &gEfiImageSecurityDatabaseGuid) && | |
((StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE) == 0) || (StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE1) == 0) || | |
(StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE2) == 0))) || | |
(CompareGuid (VendorGuid, &gEfiGlobalVariableGuid) && (StrCmp (VariableName, EFI_KEY_EXCHANGE_KEY_NAME) == 0))) | |
{ | |
// | |
// For variables with formatted as EFI_SIGNATURE_LIST, the driver shall not perform an append of | |
// EFI_SIGNATURE_DATA values that are already part of the existing variable value. | |
// | |
FilterSignatureList ( | |
OrgData, | |
OrgDataSize, | |
Data, | |
&DataSize | |
); | |
} | |
} | |
ZeroMem (&AuthVariableInfo, sizeof (AuthVariableInfo)); | |
AuthVariableInfo.VariableName = VariableName; | |
AuthVariableInfo.VendorGuid = VendorGuid; | |
AuthVariableInfo.Data = Data; | |
AuthVariableInfo.DataSize = DataSize; | |
AuthVariableInfo.Attributes = Attributes; | |
AuthVariableInfo.TimeStamp = TimeStamp; | |
return mAuthVarLibContextIn->UpdateVariable ( | |
&AuthVariableInfo | |
); | |
} | |
/** | |
Determine whether this operation needs a physical present user. | |
@param[in] VariableName Name of the Variable. | |
@param[in] VendorGuid GUID of the Variable. | |
@retval TRUE This variable is protected, only a physical present user could set this variable. | |
@retval FALSE This variable is not protected. | |
**/ | |
BOOLEAN | |
NeedPhysicallyPresent ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid | |
) | |
{ | |
// If the VariablePolicy engine is disabled, allow deletion of any authenticated variables. | |
if (IsVariablePolicyEnabled ()) { | |
if ( (CompareGuid (VendorGuid, &gEfiSecureBootEnableDisableGuid) && (StrCmp (VariableName, EFI_SECURE_BOOT_ENABLE_NAME) == 0)) | |
|| (CompareGuid (VendorGuid, &gEfiCustomModeEnableGuid) && (StrCmp (VariableName, EFI_CUSTOM_MODE_NAME) == 0))) | |
{ | |
return TRUE; | |
} | |
} | |
return FALSE; | |
} | |
/** | |
Determine whether the platform is operating in Custom Secure Boot mode. | |
@retval TRUE The platform is operating in Custom mode. | |
@retval FALSE The platform is operating in Standard mode. | |
**/ | |
BOOLEAN | |
InCustomMode ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *Data; | |
UINTN DataSize; | |
Status = AuthServiceInternalFindVariable (EFI_CUSTOM_MODE_NAME, &gEfiCustomModeEnableGuid, &Data, &DataSize); | |
if (!EFI_ERROR (Status) && (*(UINT8 *)Data == CUSTOM_SECURE_BOOT_MODE)) { | |
return TRUE; | |
} | |
return FALSE; | |
} | |
/** | |
Update platform mode. | |
@param[in] Mode SETUP_MODE or USER_MODE. | |
@return EFI_INVALID_PARAMETER Invalid parameter. | |
@return EFI_SUCCESS Update platform mode successfully. | |
**/ | |
EFI_STATUS | |
UpdatePlatformMode ( | |
IN UINT32 Mode | |
) | |
{ | |
EFI_STATUS Status; | |
VOID *Data; | |
UINTN DataSize; | |
UINT8 SecureBootMode; | |
UINT8 SecureBootEnable; | |
UINTN VariableDataSize; | |
Status = AuthServiceInternalFindVariable ( | |
EFI_SETUP_MODE_NAME, | |
&gEfiGlobalVariableGuid, | |
&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Update the value of SetupMode variable by a simple mem copy, this could avoid possible | |
// variable storage reclaim at runtime. | |
// | |
mPlatformMode = (UINT8)Mode; | |
CopyMem (Data, &mPlatformMode, sizeof (UINT8)); | |
if (mAuthVarLibContextIn->AtRuntime ()) { | |
// | |
// SecureBoot Variable indicates whether the platform firmware is operating | |
// in Secure boot mode (1) or not (0), so we should not change SecureBoot | |
// Variable in runtime. | |
// | |
return Status; | |
} | |
// | |
// Check "SecureBoot" variable's existence. | |
// If it doesn't exist, firmware has no capability to perform driver signing verification, | |
// then set "SecureBoot" to 0. | |
// | |
Status = AuthServiceInternalFindVariable ( | |
EFI_SECURE_BOOT_MODE_NAME, | |
&gEfiGlobalVariableGuid, | |
&Data, | |
&DataSize | |
); | |
// | |
// If "SecureBoot" variable exists, then check "SetupMode" variable update. | |
// If "SetupMode" variable is USER_MODE, "SecureBoot" variable is set to 1. | |
// If "SetupMode" variable is SETUP_MODE, "SecureBoot" variable is set to 0. | |
// | |
if (EFI_ERROR (Status)) { | |
SecureBootMode = SECURE_BOOT_MODE_DISABLE; | |
} else { | |
if (mPlatformMode == USER_MODE) { | |
SecureBootMode = SECURE_BOOT_MODE_ENABLE; | |
} else if (mPlatformMode == SETUP_MODE) { | |
SecureBootMode = SECURE_BOOT_MODE_DISABLE; | |
} else { | |
return EFI_NOT_FOUND; | |
} | |
} | |
Status = AuthServiceInternalUpdateVariable ( | |
EFI_SECURE_BOOT_MODE_NAME, | |
&gEfiGlobalVariableGuid, | |
&SecureBootMode, | |
sizeof (UINT8), | |
EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Check "SecureBootEnable" variable's existence. It can enable/disable secure boot feature. | |
// | |
Status = AuthServiceInternalFindVariable ( | |
EFI_SECURE_BOOT_ENABLE_NAME, | |
&gEfiSecureBootEnableDisableGuid, | |
&Data, | |
&DataSize | |
); | |
if (SecureBootMode == SECURE_BOOT_MODE_ENABLE) { | |
// | |
// Create the "SecureBootEnable" variable as secure boot is enabled. | |
// | |
SecureBootEnable = SECURE_BOOT_ENABLE; | |
VariableDataSize = sizeof (SecureBootEnable); | |
} else { | |
// | |
// Delete the "SecureBootEnable" variable if this variable exist as "SecureBoot" | |
// variable is not in secure boot state. | |
// | |
if (EFI_ERROR (Status)) { | |
return EFI_SUCCESS; | |
} | |
SecureBootEnable = SECURE_BOOT_DISABLE; | |
VariableDataSize = 0; | |
} | |
Status = AuthServiceInternalUpdateVariable ( | |
EFI_SECURE_BOOT_ENABLE_NAME, | |
&gEfiSecureBootEnableDisableGuid, | |
&SecureBootEnable, | |
VariableDataSize, | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | |
); | |
return Status; | |
} | |
/** | |
Check input data form to make sure it is a valid EFI_SIGNATURE_LIST for PK/KEK/db/dbx/dbt variable. | |
@param[in] VariableName Name of Variable to be check. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Point to the variable data to be checked. | |
@param[in] DataSize Size of Data. | |
@return EFI_INVALID_PARAMETER Invalid signature list format. | |
@return EFI_SUCCESS Passed signature list format check successfully. | |
**/ | |
EFI_STATUS | |
CheckSignatureListFormat ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize | |
) | |
{ | |
EFI_SIGNATURE_LIST *SigList; | |
UINTN SigDataSize; | |
UINT32 Index; | |
UINT32 SigCount; | |
BOOLEAN IsPk; | |
VOID *RsaContext; | |
EFI_SIGNATURE_DATA *CertData; | |
UINTN CertLen; | |
if (DataSize == 0) { | |
return EFI_SUCCESS; | |
} | |
ASSERT (VariableName != NULL && VendorGuid != NULL && Data != NULL); | |
if (CompareGuid (VendorGuid, &gEfiGlobalVariableGuid) && (StrCmp (VariableName, EFI_PLATFORM_KEY_NAME) == 0)) { | |
IsPk = TRUE; | |
} else if ((CompareGuid (VendorGuid, &gEfiGlobalVariableGuid) && (StrCmp (VariableName, EFI_KEY_EXCHANGE_KEY_NAME) == 0)) || | |
(CompareGuid (VendorGuid, &gEfiImageSecurityDatabaseGuid) && | |
((StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE) == 0) || (StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE1) == 0) || | |
(StrCmp (VariableName, EFI_IMAGE_SECURITY_DATABASE2) == 0)))) | |
{ | |
IsPk = FALSE; | |
} else { | |
return EFI_SUCCESS; | |
} | |
SigCount = 0; | |
SigList = (EFI_SIGNATURE_LIST *)Data; | |
SigDataSize = DataSize; | |
RsaContext = NULL; | |
// | |
// Walk through the input signature list and check the data format. | |
// If any signature is incorrectly formed, the whole check will fail. | |
// | |
while ((SigDataSize > 0) && (SigDataSize >= SigList->SignatureListSize)) { | |
for (Index = 0; Index < (sizeof (mSupportSigItem) / sizeof (EFI_SIGNATURE_ITEM)); Index++ ) { | |
if (CompareGuid (&SigList->SignatureType, &mSupportSigItem[Index].SigType)) { | |
// | |
// The value of SignatureSize should always be 16 (size of SignatureOwner | |
// component) add the data length according to signature type. | |
// | |
if ((mSupportSigItem[Index].SigDataSize != ((UINT32) ~0)) && | |
((SigList->SignatureSize - sizeof (EFI_GUID)) != mSupportSigItem[Index].SigDataSize)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((mSupportSigItem[Index].SigHeaderSize != ((UINT32) ~0)) && | |
(SigList->SignatureHeaderSize != mSupportSigItem[Index].SigHeaderSize)) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
break; | |
} | |
} | |
if (Index == (sizeof (mSupportSigItem) / sizeof (EFI_SIGNATURE_ITEM))) { | |
// | |
// Undefined signature type. | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
if (CompareGuid (&SigList->SignatureType, &gEfiCertX509Guid)) { | |
// | |
// Try to retrieve the RSA public key from the X.509 certificate. | |
// If this operation fails, it's not a valid certificate. | |
// | |
RsaContext = RsaNew (); | |
if (RsaContext == NULL) { | |
return EFI_INVALID_PARAMETER; | |
} | |
CertData = (EFI_SIGNATURE_DATA *)((UINT8 *)SigList + sizeof (EFI_SIGNATURE_LIST) + SigList->SignatureHeaderSize); | |
CertLen = SigList->SignatureSize - sizeof (EFI_GUID); | |
if (!RsaGetPublicKeyFromX509 (CertData->SignatureData, CertLen, &RsaContext)) { | |
RsaFree (RsaContext); | |
return EFI_INVALID_PARAMETER; | |
} | |
RsaFree (RsaContext); | |
} | |
if ((SigList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - SigList->SignatureHeaderSize) % SigList->SignatureSize != 0) { | |
return EFI_INVALID_PARAMETER; | |
} | |
SigCount += (SigList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - SigList->SignatureHeaderSize) / SigList->SignatureSize; | |
SigDataSize -= SigList->SignatureListSize; | |
SigList = (EFI_SIGNATURE_LIST *)((UINT8 *)SigList + SigList->SignatureListSize); | |
} | |
if (((UINTN)SigList - (UINTN)Data) != DataSize) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (IsPk && (SigCount > 1)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Update "VendorKeys" variable to record the out of band secure boot key modification. | |
@return EFI_SUCCESS Variable is updated successfully. | |
@return Others Failed to update variable. | |
**/ | |
EFI_STATUS | |
VendorKeyIsModified ( | |
VOID | |
) | |
{ | |
EFI_STATUS Status; | |
if (mVendorKeyState == VENDOR_KEYS_MODIFIED) { | |
return EFI_SUCCESS; | |
} | |
mVendorKeyState = VENDOR_KEYS_MODIFIED; | |
Status = AuthServiceInternalUpdateVariable ( | |
EFI_VENDOR_KEYS_NV_VARIABLE_NAME, | |
&gEfiVendorKeysNvGuid, | |
&mVendorKeyState, | |
sizeof (UINT8), | |
EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
return AuthServiceInternalUpdateVariable ( | |
EFI_VENDOR_KEYS_VARIABLE_NAME, | |
&gEfiGlobalVariableGuid, | |
&mVendorKeyState, | |
sizeof (UINT8), | |
EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | |
); | |
} | |
/** | |
Process variable with platform key for verification. | |
Caution: This function may receive untrusted input. | |
This function may be invoked in SMM mode, and datasize and data are external input. | |
This function will do basic validation, before parse the data. | |
This function will parse the authentication carefully to avoid security issues, like | |
buffer overflow, integer overflow. | |
This function will check attribute carefully to avoid authentication bypass. | |
@param[in] VariableName Name of Variable to be found. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data found. If size is less than the | |
data, this value contains the required size. | |
@param[in] Attributes Attribute value of the variable | |
@param[in] IsPk Indicate whether it is to process pk. | |
@return EFI_INVALID_PARAMETER Invalid parameter. | |
@return EFI_SECURITY_VIOLATION The variable does NOT pass the validation. | |
check carried out by the firmware. | |
@return EFI_SUCCESS Variable passed validation successfully. | |
**/ | |
EFI_STATUS | |
ProcessVarWithPk ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes OPTIONAL, | |
IN BOOLEAN IsPk | |
) | |
{ | |
EFI_STATUS Status; | |
BOOLEAN Del; | |
UINT8 *Payload; | |
UINTN PayloadSize; | |
if (((Attributes & EFI_VARIABLE_NON_VOLATILE) == 0) || | |
((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) == 0)) | |
{ | |
// | |
// PK, KEK and db/dbx/dbt should set EFI_VARIABLE_NON_VOLATILE attribute and should be a time-based | |
// authenticated variable. | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Init state of Del. State may change due to secure check | |
// | |
Del = FALSE; | |
if ( (InCustomMode () && UserPhysicalPresent ()) | |
|| ( (mPlatformMode == SETUP_MODE) | |
&& !(FeaturePcdGet (PcdRequireSelfSignedPk) && IsPk))) | |
{ | |
Payload = (UINT8 *)Data + AUTHINFO2_SIZE (Data); | |
PayloadSize = DataSize - AUTHINFO2_SIZE (Data); | |
if (PayloadSize == 0) { | |
Del = TRUE; | |
} | |
Status = CheckSignatureListFormat (VariableName, VendorGuid, Payload, PayloadSize); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = AuthServiceInternalUpdateVariableWithTimeStamp ( | |
VariableName, | |
VendorGuid, | |
Payload, | |
PayloadSize, | |
Attributes, | |
&((EFI_VARIABLE_AUTHENTICATION_2 *)Data)->TimeStamp | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ( (mPlatformMode != SETUP_MODE) | |
|| (FeaturePcdGet (PcdRequireSelfSignedPk) && IsPk)) | |
{ | |
Status = VendorKeyIsModified (); | |
} | |
} else if (mPlatformMode == USER_MODE) { | |
// | |
// Verify against X509 Cert in PK database. | |
// | |
Status = VerifyTimeBasedPayloadAndUpdate ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
Attributes, | |
AuthVarTypePk, | |
&Del | |
); | |
} else { | |
// | |
// Verify against the certificate in data payload. | |
// | |
Status = VerifyTimeBasedPayloadAndUpdate ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
Attributes, | |
AuthVarTypePayload, | |
&Del | |
); | |
} | |
if (!EFI_ERROR (Status) && IsPk) { | |
if ((mPlatformMode == SETUP_MODE) && !Del) { | |
// | |
// If enroll PK in setup mode, need change to user mode. | |
// | |
Status = UpdatePlatformMode (USER_MODE); | |
} else if ((mPlatformMode == USER_MODE) && Del) { | |
// | |
// If delete PK in user mode, need change to setup mode. | |
// | |
Status = UpdatePlatformMode (SETUP_MODE); | |
} | |
} | |
return Status; | |
} | |
/** | |
Process variable with key exchange key for verification. | |
Caution: This function may receive untrusted input. | |
This function may be invoked in SMM mode, and datasize and data are external input. | |
This function will do basic validation, before parse the data. | |
This function will parse the authentication carefully to avoid security issues, like | |
buffer overflow, integer overflow. | |
This function will check attribute carefully to avoid authentication bypass. | |
@param[in] VariableName Name of Variable to be found. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data found. If size is less than the | |
data, this value contains the required size. | |
@param[in] Attributes Attribute value of the variable. | |
@return EFI_INVALID_PARAMETER Invalid parameter. | |
@return EFI_SECURITY_VIOLATION The variable does NOT pass the validation | |
check carried out by the firmware. | |
@return EFI_SUCCESS Variable pass validation successfully. | |
**/ | |
EFI_STATUS | |
ProcessVarWithKek ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes OPTIONAL | |
) | |
{ | |
EFI_STATUS Status; | |
UINT8 *Payload; | |
UINTN PayloadSize; | |
if (((Attributes & EFI_VARIABLE_NON_VOLATILE) == 0) || | |
((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) == 0)) | |
{ | |
// | |
// DB, DBX and DBT should set EFI_VARIABLE_NON_VOLATILE attribute and should be a time-based | |
// authenticated variable. | |
// | |
return EFI_INVALID_PARAMETER; | |
} | |
Status = EFI_SUCCESS; | |
if ((mPlatformMode == USER_MODE) && !(InCustomMode () && UserPhysicalPresent ())) { | |
// | |
// Time-based, verify against X509 Cert KEK. | |
// | |
return VerifyTimeBasedPayloadAndUpdate ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
Attributes, | |
AuthVarTypeKek, | |
NULL | |
); | |
} else { | |
// | |
// If in setup mode or custom secure boot mode, no authentication needed. | |
// | |
Payload = (UINT8 *)Data + AUTHINFO2_SIZE (Data); | |
PayloadSize = DataSize - AUTHINFO2_SIZE (Data); | |
Status = CheckSignatureListFormat (VariableName, VendorGuid, Payload, PayloadSize); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
Status = AuthServiceInternalUpdateVariableWithTimeStamp ( | |
VariableName, | |
VendorGuid, | |
Payload, | |
PayloadSize, | |
Attributes, | |
&((EFI_VARIABLE_AUTHENTICATION_2 *)Data)->TimeStamp | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (mPlatformMode != SETUP_MODE) { | |
Status = VendorKeyIsModified (); | |
} | |
} | |
return Status; | |
} | |
/** | |
Check if it is to delete auth variable. | |
@param[in] OrgAttributes Original attribute value of the variable. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data. | |
@param[in] Attributes Attribute value of the variable. | |
@retval TRUE It is to delete auth variable. | |
@retval FALSE It is not to delete auth variable. | |
**/ | |
BOOLEAN | |
IsDeleteAuthVariable ( | |
IN UINT32 OrgAttributes, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes | |
) | |
{ | |
BOOLEAN Del; | |
UINTN PayloadSize; | |
Del = FALSE; | |
// | |
// To delete a variable created with the EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | |
// or the EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS attribute, | |
// SetVariable must be used with attributes matching the existing variable | |
// and the DataSize set to the size of the AuthInfo descriptor. | |
// | |
if ((Attributes == OrgAttributes) && | |
((Attributes & (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) != 0)) | |
{ | |
if ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) != 0) { | |
PayloadSize = DataSize - AUTHINFO2_SIZE (Data); | |
if (PayloadSize == 0) { | |
Del = TRUE; | |
} | |
} else { | |
PayloadSize = DataSize - AUTHINFO_SIZE; | |
if (PayloadSize == 0) { | |
Del = TRUE; | |
} | |
} | |
} | |
return Del; | |
} | |
/** | |
Process variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set | |
Caution: This function may receive untrusted input. | |
This function may be invoked in SMM mode, and datasize and data are external input. | |
This function will do basic validation, before parse the data. | |
This function will parse the authentication carefully to avoid security issues, like | |
buffer overflow, integer overflow. | |
This function will check attribute carefully to avoid authentication bypass. | |
@param[in] VariableName Name of the variable. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data. | |
@param[in] Attributes Attribute value of the variable. | |
@return EFI_INVALID_PARAMETER Invalid parameter. | |
@return EFI_WRITE_PROTECTED Variable is write-protected and needs authentication with | |
EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS or EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set. | |
@return EFI_OUT_OF_RESOURCES The Database to save the public key is full. | |
@return EFI_SECURITY_VIOLATION The variable is with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS | |
set, but the AuthInfo does NOT pass the validation | |
check carried out by the firmware. | |
@return EFI_SUCCESS Variable is not write-protected or pass validation successfully. | |
**/ | |
EFI_STATUS | |
ProcessVariable ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes | |
) | |
{ | |
EFI_STATUS Status; | |
AUTH_VARIABLE_INFO OrgVariableInfo; | |
Status = EFI_SUCCESS; | |
ZeroMem (&OrgVariableInfo, sizeof (OrgVariableInfo)); | |
Status = mAuthVarLibContextIn->FindVariable ( | |
VariableName, | |
VendorGuid, | |
&OrgVariableInfo | |
); | |
// If the VariablePolicy engine is disabled, allow deletion of any authenticated variables. | |
if ((!EFI_ERROR (Status)) && IsDeleteAuthVariable (OrgVariableInfo.Attributes, Data, DataSize, Attributes) && (UserPhysicalPresent () || !IsVariablePolicyEnabled ())) { | |
// | |
// Allow the delete operation of common authenticated variable(AT or AW) at user physical presence. | |
// | |
Status = AuthServiceInternalUpdateVariable ( | |
VariableName, | |
VendorGuid, | |
NULL, | |
0, | |
0 | |
); | |
if (!EFI_ERROR (Status) && ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) != 0)) { | |
Status = DeleteCertsFromDb (VariableName, VendorGuid, Attributes); | |
} | |
return Status; | |
} | |
if (NeedPhysicallyPresent (VariableName, VendorGuid) && !UserPhysicalPresent ()) { | |
// | |
// This variable is protected, only physical present user could modify its value. | |
// | |
return EFI_SECURITY_VIOLATION; | |
} | |
// | |
if ((Attributes & EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS) != 0) { | |
// | |
// Reject Counter Based Auth Variable processing request. | |
// | |
return EFI_UNSUPPORTED; | |
} else if ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) != 0) { | |
// | |
// Process Time-based Authenticated variable. | |
// | |
return VerifyTimeBasedPayloadAndUpdate ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
Attributes, | |
AuthVarTypePriv, | |
NULL | |
); | |
} | |
if ((OrgVariableInfo.Data != NULL) && | |
((OrgVariableInfo.Attributes & (EFI_VARIABLE_AUTHENTICATED_WRITE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS)) != 0)) | |
{ | |
// | |
// If the variable is already write-protected, it always needs authentication before update. | |
// | |
return EFI_WRITE_PROTECTED; | |
} | |
// | |
// Not authenticated variable, just update variable as usual. | |
// | |
Status = AuthServiceInternalUpdateVariable (VariableName, VendorGuid, Data, DataSize, Attributes); | |
return Status; | |
} | |
/** | |
Filter out the duplicated EFI_SIGNATURE_DATA from the new data by comparing to the original data. | |
@param[in] Data Pointer to original EFI_SIGNATURE_LIST. | |
@param[in] DataSize Size of Data buffer. | |
@param[in, out] NewData Pointer to new EFI_SIGNATURE_LIST. | |
@param[in, out] NewDataSize Size of NewData buffer. | |
**/ | |
EFI_STATUS | |
FilterSignatureList ( | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN OUT VOID *NewData, | |
IN OUT UINTN *NewDataSize | |
) | |
{ | |
EFI_SIGNATURE_LIST *CertList; | |
EFI_SIGNATURE_DATA *Cert; | |
UINTN CertCount; | |
EFI_SIGNATURE_LIST *NewCertList; | |
EFI_SIGNATURE_DATA *NewCert; | |
UINTN NewCertCount; | |
UINTN Index; | |
UINTN Index2; | |
UINTN Size; | |
UINT8 *Tail; | |
UINTN CopiedCount; | |
UINTN SignatureListSize; | |
BOOLEAN IsNewCert; | |
UINT8 *TempData; | |
UINTN TempDataSize; | |
EFI_STATUS Status; | |
if (*NewDataSize == 0) { | |
return EFI_SUCCESS; | |
} | |
TempDataSize = *NewDataSize; | |
Status = mAuthVarLibContextIn->GetScratchBuffer (&TempDataSize, (VOID **)&TempData); | |
if (EFI_ERROR (Status)) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Tail = TempData; | |
NewCertList = (EFI_SIGNATURE_LIST *)NewData; | |
while ((*NewDataSize > 0) && (*NewDataSize >= NewCertList->SignatureListSize)) { | |
NewCert = (EFI_SIGNATURE_DATA *)((UINT8 *)NewCertList + sizeof (EFI_SIGNATURE_LIST) + NewCertList->SignatureHeaderSize); | |
NewCertCount = (NewCertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - NewCertList->SignatureHeaderSize) / NewCertList->SignatureSize; | |
CopiedCount = 0; | |
for (Index = 0; Index < NewCertCount; Index++) { | |
IsNewCert = TRUE; | |
Size = DataSize; | |
CertList = (EFI_SIGNATURE_LIST *)Data; | |
while ((Size > 0) && (Size >= CertList->SignatureListSize)) { | |
if (CompareGuid (&CertList->SignatureType, &NewCertList->SignatureType) && | |
(CertList->SignatureSize == NewCertList->SignatureSize)) | |
{ | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); | |
CertCount = (CertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; | |
for (Index2 = 0; Index2 < CertCount; Index2++) { | |
// | |
// Iterate each Signature Data in this Signature List. | |
// | |
if (CompareMem (NewCert, Cert, CertList->SignatureSize) == 0) { | |
IsNewCert = FALSE; | |
break; | |
} | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)Cert + CertList->SignatureSize); | |
} | |
} | |
if (!IsNewCert) { | |
break; | |
} | |
Size -= CertList->SignatureListSize; | |
CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); | |
} | |
if (IsNewCert) { | |
// | |
// New EFI_SIGNATURE_DATA, keep it. | |
// | |
if (CopiedCount == 0) { | |
// | |
// Copy EFI_SIGNATURE_LIST header for only once. | |
// | |
CopyMem (Tail, NewCertList, sizeof (EFI_SIGNATURE_LIST) + NewCertList->SignatureHeaderSize); | |
Tail = Tail + sizeof (EFI_SIGNATURE_LIST) + NewCertList->SignatureHeaderSize; | |
} | |
CopyMem (Tail, NewCert, NewCertList->SignatureSize); | |
Tail += NewCertList->SignatureSize; | |
CopiedCount++; | |
} | |
NewCert = (EFI_SIGNATURE_DATA *)((UINT8 *)NewCert + NewCertList->SignatureSize); | |
} | |
// | |
// Update SignatureListSize in the kept EFI_SIGNATURE_LIST. | |
// | |
if (CopiedCount != 0) { | |
SignatureListSize = sizeof (EFI_SIGNATURE_LIST) + NewCertList->SignatureHeaderSize + (CopiedCount * NewCertList->SignatureSize); | |
CertList = (EFI_SIGNATURE_LIST *)(Tail - SignatureListSize); | |
CertList->SignatureListSize = (UINT32)SignatureListSize; | |
} | |
*NewDataSize -= NewCertList->SignatureListSize; | |
NewCertList = (EFI_SIGNATURE_LIST *)((UINT8 *)NewCertList + NewCertList->SignatureListSize); | |
} | |
TempDataSize = (Tail - (UINT8 *)TempData); | |
CopyMem (NewData, TempData, TempDataSize); | |
*NewDataSize = TempDataSize; | |
return EFI_SUCCESS; | |
} | |
/** | |
Compare two EFI_TIME data. | |
@param FirstTime A pointer to the first EFI_TIME data. | |
@param SecondTime A pointer to the second EFI_TIME data. | |
@retval TRUE The FirstTime is not later than the SecondTime. | |
@retval FALSE The FirstTime is later than the SecondTime. | |
**/ | |
BOOLEAN | |
AuthServiceInternalCompareTimeStamp ( | |
IN EFI_TIME *FirstTime, | |
IN EFI_TIME *SecondTime | |
) | |
{ | |
if (FirstTime->Year != SecondTime->Year) { | |
return (BOOLEAN)(FirstTime->Year < SecondTime->Year); | |
} else if (FirstTime->Month != SecondTime->Month) { | |
return (BOOLEAN)(FirstTime->Month < SecondTime->Month); | |
} else if (FirstTime->Day != SecondTime->Day) { | |
return (BOOLEAN)(FirstTime->Day < SecondTime->Day); | |
} else if (FirstTime->Hour != SecondTime->Hour) { | |
return (BOOLEAN)(FirstTime->Hour < SecondTime->Hour); | |
} else if (FirstTime->Minute != SecondTime->Minute) { | |
return (BOOLEAN)(FirstTime->Minute < SecondTime->Minute); | |
} | |
return (BOOLEAN)(FirstTime->Second <= SecondTime->Second); | |
} | |
/** | |
Calculate SHA digest of SignerCert CommonName + ToplevelCert tbsCertificate. | |
SignerCert and ToplevelCert are inside the signer certificate chain. | |
@param[in] HashAlgId Hash algorithm index. | |
@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. | |
@param[out] ShaDigest Sha digest calculated. | |
@return EFI_ABORTED Digest process failed. | |
@return EFI_SUCCESS SHA Digest is successfully calculated. | |
**/ | |
EFI_STATUS | |
CalculatePrivAuthVarSignChainSHADigest ( | |
IN UINT8 HashAlgId, | |
IN UINT8 *SignerCert, | |
IN UINTN SignerCertSize, | |
IN UINT8 *TopLevelCert, | |
IN UINTN TopLevelCertSize, | |
OUT UINT8 *ShaDigest | |
) | |
{ | |
UINT8 *TbsCert; | |
UINTN TbsCertSize; | |
CHAR8 CertCommonName[128]; | |
UINTN CertCommonNameSize; | |
BOOLEAN CryptoStatus; | |
EFI_STATUS Status; | |
if (HashAlgId >= (sizeof (mHashInfo) / sizeof (EFI_HASH_INFO))) { | |
DEBUG ((DEBUG_INFO, "%a Unsupported Hash Algorithm %d\n", __func__, HashAlgId)); | |
return EFI_ABORTED; | |
} | |
CertCommonNameSize = sizeof (CertCommonName); | |
// | |
// Get SignerCert CommonName | |
// | |
Status = X509GetCommonName (SignerCert, SignerCertSize, CertCommonName, &CertCommonNameSize); | |
if (EFI_ERROR (Status)) { | |
DEBUG ((DEBUG_INFO, "%a Get SignerCert CommonName failed with status %x\n", __func__, Status)); | |
return EFI_ABORTED; | |
} | |
// | |
// Get TopLevelCert tbsCertificate | |
// | |
if (!X509GetTBSCert (TopLevelCert, TopLevelCertSize, &TbsCert, &TbsCertSize)) { | |
DEBUG ((DEBUG_INFO, "%a Get Top-level Cert tbsCertificate failed!\n", __func__)); | |
return EFI_ABORTED; | |
} | |
// | |
// Digest SignerCert CN + TopLevelCert tbsCertificate | |
// | |
ZeroMem (ShaDigest, mHashInfo[HashAlgId].HashSize); | |
CryptoStatus = mHashInfo[HashAlgId].Init (*(mHashInfo[HashAlgId].HashShaCtx)); | |
if (!CryptoStatus) { | |
return EFI_ABORTED; | |
} | |
// | |
// '\0' is forced in CertCommonName. No overflow issue | |
// | |
CryptoStatus = mHashInfo[HashAlgId].Update ( | |
*(mHashInfo[HashAlgId].HashShaCtx), | |
CertCommonName, | |
AsciiStrLen (CertCommonName) | |
); | |
if (!CryptoStatus) { | |
return EFI_ABORTED; | |
} | |
CryptoStatus = mHashInfo[HashAlgId].Update (*(mHashInfo[HashAlgId].HashShaCtx), TbsCert, TbsCertSize); | |
if (!CryptoStatus) { | |
return EFI_ABORTED; | |
} | |
CryptoStatus = mHashInfo[HashAlgId].Final (*(mHashInfo[HashAlgId].HashShaCtx), ShaDigest); | |
if (!CryptoStatus) { | |
return EFI_ABORTED; | |
} | |
return EFI_SUCCESS; | |
} | |
/** | |
Find matching signer's certificates for common authenticated variable | |
by corresponding VariableName and VendorGuid from "certdb" or "certdbv". | |
The data format of "certdb" or "certdbv": | |
// | |
// UINT32 CertDbListSize; | |
// /// AUTH_CERT_DB_DATA Certs1[]; | |
// /// AUTH_CERT_DB_DATA Certs2[]; | |
// /// ... | |
// /// AUTH_CERT_DB_DATA Certsn[]; | |
// | |
@param[in] VariableName Name of authenticated Variable. | |
@param[in] VendorGuid Vendor GUID of authenticated Variable. | |
@param[in] Data Pointer to variable "certdb" or "certdbv". | |
@param[in] DataSize Size of variable "certdb" or "certdbv". | |
@param[out] CertOffset Offset of matching CertData, from starting of Data. | |
@param[out] CertDataSize Length of CertData in bytes. | |
@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. | |
@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 UINT8 *Data, | |
IN UINTN DataSize, | |
OUT UINT32 *CertOffset OPTIONAL, | |
OUT UINT32 *CertDataSize OPTIONAL, | |
OUT UINT32 *CertNodeOffset OPTIONAL, | |
OUT UINT32 *CertNodeSize OPTIONAL | |
) | |
{ | |
UINT32 Offset; | |
AUTH_CERT_DB_DATA *Ptr; | |
UINT32 CertSize; | |
UINT32 NameSize; | |
UINT32 NodeSize; | |
UINT32 CertDbListSize; | |
if ((VariableName == NULL) || (VendorGuid == NULL) || (Data == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
// | |
// Check whether DataSize matches recorded CertDbListSize. | |
// | |
if (DataSize < sizeof (UINT32)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
CertDbListSize = ReadUnaligned32 ((UINT32 *)Data); | |
if (CertDbListSize != (UINT32)DataSize) { | |
return EFI_INVALID_PARAMETER; | |
} | |
Offset = sizeof (UINT32); | |
// | |
// Get corresponding certificates by VendorGuid and VariableName. | |
// | |
while (Offset < (UINT32)DataSize) { | |
Ptr = (AUTH_CERT_DB_DATA *)(Data + Offset); | |
// | |
// Check whether VendorGuid matches. | |
// | |
if (CompareGuid (&Ptr->VendorGuid, VendorGuid)) { | |
NodeSize = ReadUnaligned32 (&Ptr->CertNodeSize); | |
NameSize = ReadUnaligned32 (&Ptr->NameSize); | |
CertSize = ReadUnaligned32 (&Ptr->CertDataSize); | |
if (NodeSize != sizeof (EFI_GUID) + sizeof (UINT32) * 3 + CertSize + | |
sizeof (CHAR16) * NameSize) | |
{ | |
return EFI_INVALID_PARAMETER; | |
} | |
Offset = Offset + sizeof (EFI_GUID) + sizeof (UINT32) * 3; | |
// | |
// Check whether VariableName matches. | |
// | |
if ((NameSize == StrLen (VariableName)) && | |
(CompareMem (Data + Offset, VariableName, NameSize * sizeof (CHAR16)) == 0)) | |
{ | |
Offset = Offset + NameSize * sizeof (CHAR16); | |
if (CertOffset != NULL) { | |
*CertOffset = Offset; | |
} | |
if (CertDataSize != NULL) { | |
*CertDataSize = CertSize; | |
} | |
if (CertNodeOffset != NULL) { | |
*CertNodeOffset = (UINT32)((UINT8 *)Ptr - Data); | |
} | |
if (CertNodeSize != NULL) { | |
*CertNodeSize = NodeSize; | |
} | |
return EFI_SUCCESS; | |
} else { | |
Offset = Offset + NameSize * sizeof (CHAR16) + CertSize; | |
} | |
} else { | |
NodeSize = ReadUnaligned32 (&Ptr->CertNodeSize); | |
Offset = Offset + NodeSize; | |
} | |
} | |
return EFI_NOT_FOUND; | |
} | |
/** | |
Retrieve signer's certificates for common authenticated variable | |
by corresponding VariableName and VendorGuid from "certdb" | |
or "certdbv" according to authenticated variable attributes. | |
@param[in] VariableName Name of authenticated Variable. | |
@param[in] VendorGuid Vendor GUID of authenticated Variable. | |
@param[in] Attributes Attributes of authenticated variable. | |
@param[out] CertData Pointer to signer's certificates. | |
@param[out] CertDataSize Length of CertData in bytes. | |
@retval EFI_INVALID_PARAMETER Any input parameter is invalid. | |
@retval EFI_NOT_FOUND Fail to find "certdb"/"certdbv" or matching certs. | |
@retval EFI_SUCCESS Get signer's certificates successfully. | |
**/ | |
EFI_STATUS | |
GetCertsFromDb ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN UINT32 Attributes, | |
OUT UINT8 **CertData, | |
OUT UINT32 *CertDataSize | |
) | |
{ | |
EFI_STATUS Status; | |
UINT8 *Data; | |
UINTN DataSize; | |
UINT32 CertOffset; | |
CHAR16 *DbName; | |
if ((VariableName == NULL) || (VendorGuid == NULL) || (CertData == NULL) || (CertDataSize == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) { | |
// | |
// Get variable "certdb". | |
// | |
DbName = EFI_CERT_DB_NAME; | |
} else { | |
// | |
// Get variable "certdbv". | |
// | |
DbName = EFI_CERT_DB_VOLATILE_NAME; | |
} | |
// | |
// Get variable "certdb" or "certdbv". | |
// | |
Status = AuthServiceInternalFindVariable ( | |
DbName, | |
&gEfiCertDbGuid, | |
(VOID **)&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((DataSize == 0) || (Data == NULL)) { | |
ASSERT (FALSE); | |
return EFI_NOT_FOUND; | |
} | |
Status = FindCertsFromDb ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
&CertOffset, | |
CertDataSize, | |
NULL, | |
NULL | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
*CertData = Data + CertOffset; | |
return EFI_SUCCESS; | |
} | |
/** | |
Delete matching signer's certificates when deleting common authenticated | |
variable by corresponding VariableName and VendorGuid from "certdb" or | |
"certdbv" according to authenticated variable attributes. | |
@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"/"certdbv" 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; | |
UINT8 *Data; | |
UINTN DataSize; | |
UINT32 VarAttr; | |
UINT32 CertNodeOffset; | |
UINT32 CertNodeSize; | |
UINT8 *NewCertDb; | |
UINT32 NewCertDbSize; | |
CHAR16 *DbName; | |
if ((VariableName == NULL) || (VendorGuid == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) { | |
// | |
// Get variable "certdb". | |
// | |
DbName = EFI_CERT_DB_NAME; | |
VarAttr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
} else { | |
// | |
// Get variable "certdbv". | |
// | |
DbName = EFI_CERT_DB_VOLATILE_NAME; | |
VarAttr = EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
} | |
Status = AuthServiceInternalFindVariable ( | |
DbName, | |
&gEfiCertDbGuid, | |
(VOID **)&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((DataSize == 0) || (Data == NULL)) { | |
ASSERT (FALSE); | |
return EFI_NOT_FOUND; | |
} | |
if (DataSize == sizeof (UINT32)) { | |
// | |
// There is no certs in "certdb" or "certdbv". | |
// | |
return EFI_SUCCESS; | |
} | |
// | |
// Get corresponding cert node from "certdb" or "certdbv". | |
// | |
Status = FindCertsFromDb ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
NULL, | |
NULL, | |
&CertNodeOffset, | |
&CertNodeSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if (DataSize < (CertNodeOffset + CertNodeSize)) { | |
return EFI_NOT_FOUND; | |
} | |
// | |
// Construct new data content of variable "certdb" or "certdbv". | |
// | |
NewCertDbSize = (UINT32)DataSize - CertNodeSize; | |
NewCertDb = (UINT8 *)mCertDbStore; | |
// | |
// Copy the DB entries before deleting node. | |
// | |
CopyMem (NewCertDb, Data, CertNodeOffset); | |
// | |
// Update CertDbListSize. | |
// | |
CopyMem (NewCertDb, &NewCertDbSize, sizeof (UINT32)); | |
// | |
// Copy the DB entries after deleting node. | |
// | |
if (DataSize > (CertNodeOffset + CertNodeSize)) { | |
CopyMem ( | |
NewCertDb + CertNodeOffset, | |
Data + CertNodeOffset + CertNodeSize, | |
DataSize - CertNodeOffset - CertNodeSize | |
); | |
} | |
// | |
// Set "certdb" or "certdbv". | |
// | |
Status = AuthServiceInternalUpdateVariable ( | |
DbName, | |
&gEfiCertDbGuid, | |
NewCertDb, | |
NewCertDbSize, | |
VarAttr | |
); | |
return Status; | |
} | |
/** | |
Insert signer's certificates for common authenticated variable with VariableName | |
and VendorGuid in AUTH_CERT_DB_DATA to "certdb" or "certdbv" according to | |
time based authenticated variable attributes. CertData is the SHA digest of | |
SignerCert CommonName + TopLevelCert tbsCertificate. | |
@param[in] HashAlgId Hash algorithm index. | |
@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 UINT8 HashAlgId, | |
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 *Data; | |
UINTN DataSize; | |
UINT32 VarAttr; | |
UINT8 *NewCertDb; | |
UINT32 NewCertDbSize; | |
UINT32 CertNodeSize; | |
UINT32 NameSize; | |
UINT32 CertDataSize; | |
AUTH_CERT_DB_DATA *Ptr; | |
CHAR16 *DbName; | |
UINT8 ShaDigest[SHA_DIGEST_SIZE_MAX]; | |
if ((VariableName == NULL) || (VendorGuid == NULL) || (SignerCert == NULL) || (TopLevelCert == NULL)) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if (HashAlgId >= (sizeof (mHashInfo) / sizeof (EFI_HASH_INFO))) { | |
return EFI_INVALID_PARAMETER; | |
} | |
if ((Attributes & EFI_VARIABLE_NON_VOLATILE) != 0) { | |
// | |
// Get variable "certdb". | |
// | |
DbName = EFI_CERT_DB_NAME; | |
VarAttr = EFI_VARIABLE_NON_VOLATILE | EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
} else { | |
// | |
// Get variable "certdbv". | |
// | |
DbName = EFI_CERT_DB_VOLATILE_NAME; | |
VarAttr = EFI_VARIABLE_RUNTIME_ACCESS | EFI_VARIABLE_BOOTSERVICE_ACCESS | EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS; | |
} | |
// | |
// Get variable "certdb" or "certdbv". | |
// | |
Status = AuthServiceInternalFindVariable ( | |
DbName, | |
&gEfiCertDbGuid, | |
(VOID **)&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((DataSize == 0) || (Data == NULL)) { | |
ASSERT (FALSE); | |
return EFI_NOT_FOUND; | |
} | |
// | |
// Find whether matching cert node already exists in "certdb" or "certdbv". | |
// If yes return error. | |
// | |
Status = FindCertsFromDb ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
NULL, | |
NULL, | |
NULL, | |
NULL | |
); | |
if (!EFI_ERROR (Status)) { | |
ASSERT (FALSE); | |
return EFI_ACCESS_DENIED; | |
} | |
// | |
// Construct new data content of variable "certdb" or "certdbv". | |
// | |
NameSize = (UINT32)StrLen (VariableName); | |
CertDataSize = mHashInfo[HashAlgId].HashSize; | |
CertNodeSize = sizeof (AUTH_CERT_DB_DATA) + (UINT32)CertDataSize + NameSize * sizeof (CHAR16); | |
NewCertDbSize = (UINT32)DataSize + CertNodeSize; | |
if (NewCertDbSize > mMaxCertDbSize) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Status = CalculatePrivAuthVarSignChainSHADigest ( | |
HashAlgId, | |
SignerCert, | |
SignerCertSize, | |
TopLevelCert, | |
TopLevelCertSize, | |
ShaDigest | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
NewCertDb = (UINT8 *)mCertDbStore; | |
// | |
// Copy the DB entries before inserting node. | |
// | |
CopyMem (NewCertDb, Data, DataSize); | |
// | |
// Update CertDbListSize. | |
// | |
CopyMem (NewCertDb, &NewCertDbSize, sizeof (UINT32)); | |
// | |
// Construct new cert node. | |
// | |
Ptr = (AUTH_CERT_DB_DATA *)(NewCertDb + DataSize); | |
CopyGuid (&Ptr->VendorGuid, VendorGuid); | |
CopyMem (&Ptr->CertNodeSize, &CertNodeSize, sizeof (UINT32)); | |
CopyMem (&Ptr->NameSize, &NameSize, sizeof (UINT32)); | |
CopyMem (&Ptr->CertDataSize, &CertDataSize, sizeof (UINT32)); | |
CopyMem ( | |
(UINT8 *)Ptr + sizeof (AUTH_CERT_DB_DATA), | |
VariableName, | |
NameSize * sizeof (CHAR16) | |
); | |
CopyMem ( | |
(UINT8 *)Ptr + sizeof (AUTH_CERT_DB_DATA) + NameSize * sizeof (CHAR16), | |
ShaDigest, | |
CertDataSize | |
); | |
// | |
// Set "certdb" or "certdbv". | |
// | |
Status = AuthServiceInternalUpdateVariable ( | |
DbName, | |
&gEfiCertDbGuid, | |
NewCertDb, | |
NewCertDbSize, | |
VarAttr | |
); | |
return Status; | |
} | |
/** | |
Clean up signer's certificates for common authenticated variable | |
by corresponding VariableName and VendorGuid from "certdb". | |
System may break down during Timebased Variable update & certdb update, | |
make them inconsistent, this function is called in AuthVariable Init | |
to ensure consistency. | |
@retval EFI_NOT_FOUND Fail to find variable "certdb". | |
@retval EFI_OUT_OF_RESOURCES The operation is failed due to lack of resources. | |
@retval EFI_SUCCESS The operation is completed successfully. | |
**/ | |
EFI_STATUS | |
CleanCertsFromDb ( | |
VOID | |
) | |
{ | |
UINT32 Offset; | |
AUTH_CERT_DB_DATA *Ptr; | |
UINT32 NameSize; | |
UINT32 NodeSize; | |
CHAR16 *VariableName; | |
EFI_STATUS Status; | |
BOOLEAN CertCleaned; | |
UINT8 *Data; | |
UINTN DataSize; | |
EFI_GUID AuthVarGuid; | |
AUTH_VARIABLE_INFO AuthVariableInfo; | |
Status = EFI_SUCCESS; | |
// | |
// Get corresponding certificates by VendorGuid and VariableName. | |
// | |
do { | |
CertCleaned = FALSE; | |
// | |
// Get latest variable "certdb" | |
// | |
Status = AuthServiceInternalFindVariable ( | |
EFI_CERT_DB_NAME, | |
&gEfiCertDbGuid, | |
(VOID **)&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ((DataSize == 0) || (Data == NULL)) { | |
ASSERT (FALSE); | |
return EFI_NOT_FOUND; | |
} | |
Offset = sizeof (UINT32); | |
while (Offset < (UINT32)DataSize) { | |
Ptr = (AUTH_CERT_DB_DATA *)(Data + Offset); | |
NodeSize = ReadUnaligned32 (&Ptr->CertNodeSize); | |
NameSize = ReadUnaligned32 (&Ptr->NameSize); | |
// | |
// Get VarName tailed with '\0' | |
// | |
VariableName = AllocateZeroPool ((NameSize + 1) * sizeof (CHAR16)); | |
if (VariableName == NULL) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
CopyMem (VariableName, (UINT8 *)Ptr + sizeof (AUTH_CERT_DB_DATA), NameSize * sizeof (CHAR16)); | |
// | |
// Keep VarGuid aligned | |
// | |
CopyMem (&AuthVarGuid, &Ptr->VendorGuid, sizeof (EFI_GUID)); | |
// | |
// Find corresponding time auth variable | |
// | |
ZeroMem (&AuthVariableInfo, sizeof (AuthVariableInfo)); | |
Status = mAuthVarLibContextIn->FindVariable ( | |
VariableName, | |
&AuthVarGuid, | |
&AuthVariableInfo | |
); | |
if (EFI_ERROR (Status) || ((AuthVariableInfo.Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) == 0)) { | |
// | |
// While cleaning certdb, always delete the variable in certdb regardless of it attributes. | |
// | |
Status = DeleteCertsFromDb ( | |
VariableName, | |
&AuthVarGuid, | |
AuthVariableInfo.Attributes | EFI_VARIABLE_NON_VOLATILE | |
); | |
CertCleaned = TRUE; | |
DEBUG ((DEBUG_INFO, "Recovery!! Cert for Auth Variable %s Guid %g is removed for consistency\n", VariableName, &AuthVarGuid)); | |
FreePool (VariableName); | |
break; | |
} | |
FreePool (VariableName); | |
Offset = Offset + NodeSize; | |
} | |
} while (CertCleaned); | |
return Status; | |
} | |
/** | |
Find hash algorithm index. | |
@param[in] SigData Pointer to the PKCS#7 message. | |
@param[in] SigDataSize Length of the PKCS#7 message. | |
@retval UINT8 Hash Algorithm Index. | |
**/ | |
UINT8 | |
FindHashAlgorithmIndex ( | |
IN UINT8 *SigData, | |
IN UINT32 SigDataSize | |
) | |
{ | |
UINT8 i; | |
for (i = 0; i < (sizeof (mHashInfo) / sizeof (EFI_HASH_INFO)); i++) { | |
if ( ( (SigDataSize >= (13 + mHashInfo[i].OidLength)) | |
&& ( ((*(SigData + 1) & TWO_BYTE_ENCODE) == TWO_BYTE_ENCODE) | |
&& (CompareMem (SigData + 13, mHashInfo[i].OidValue, mHashInfo[i].OidLength) == 0))) | |
|| ( ((SigDataSize >= (32 + mHashInfo[i].OidLength))) | |
&& ( ((*(SigData + 20) & TWO_BYTE_ENCODE) == TWO_BYTE_ENCODE) | |
&& (CompareMem (SigData + 32, mHashInfo[i].OidValue, mHashInfo[i].OidLength) == 0)))) | |
{ | |
break; | |
} | |
} | |
return i; | |
} | |
/** | |
Process variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set | |
Caution: This function may receive untrusted input. | |
This function may be invoked in SMM mode, and datasize and data are external input. | |
This function will do basic validation, before parse the data. | |
This function will parse the authentication carefully to avoid security issues, like | |
buffer overflow, integer overflow. | |
@param[in] VariableName Name of Variable to be found. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data found. If size is less than the | |
data, this value contains the required size. | |
@param[in] Attributes Attribute value of the variable. | |
@param[in] AuthVarType Verify against PK, KEK database, private database or certificate in data payload. | |
@param[in] OrgTimeStamp Pointer to original time stamp, | |
original variable is not found if NULL. | |
@param[out] VarPayloadPtr Pointer to variable payload address. | |
@param[out] VarPayloadSize Pointer to variable payload size. | |
@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 | |
VerifyTimeBasedPayload ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes, | |
IN AUTHVAR_TYPE AuthVarType, | |
IN EFI_TIME *OrgTimeStamp, | |
OUT UINT8 **VarPayloadPtr, | |
OUT UINTN *VarPayloadSize | |
) | |
{ | |
EFI_VARIABLE_AUTHENTICATION_2 *CertData; | |
UINT8 *SigData; | |
UINT32 SigDataSize; | |
UINT8 *PayloadPtr; | |
UINTN PayloadSize; | |
UINT32 Attr; | |
BOOLEAN VerifyStatus; | |
EFI_STATUS Status; | |
EFI_SIGNATURE_LIST *CertList; | |
EFI_SIGNATURE_DATA *Cert; | |
UINTN Index; | |
UINTN CertCount; | |
UINT32 KekDataSize; | |
UINT8 *NewData; | |
UINTN NewDataSize; | |
UINT8 *Buffer; | |
UINTN Length; | |
UINT8 *TopLevelCert; | |
UINTN TopLevelCertSize; | |
UINT8 *TrustedCert; | |
UINTN TrustedCertSize; | |
UINT8 *SignerCerts; | |
UINTN CertStackSize; | |
UINT8 *CertsInCertDb; | |
UINT32 CertsSizeinDb; | |
UINT8 ShaDigest[SHA_DIGEST_SIZE_MAX]; | |
EFI_CERT_DATA *CertDataPtr; | |
UINT8 HashAlgId; | |
// | |
// 1. TopLevelCert is the top-level issuer certificate in signature Signer Cert Chain | |
// 2. TrustedCert is the certificate which firmware trusts. It could be saved in protected | |
// storage or PK payload on PK init | |
// | |
VerifyStatus = FALSE; | |
CertData = NULL; | |
NewData = NULL; | |
Attr = Attributes; | |
SignerCerts = NULL; | |
TopLevelCert = NULL; | |
CertsInCertDb = NULL; | |
CertDataPtr = NULL; | |
// | |
// When the attribute EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS is | |
// set, then the Data buffer shall begin with an instance of a complete (and serialized) | |
// EFI_VARIABLE_AUTHENTICATION_2 descriptor. The descriptor shall be followed by the new | |
// variable value and DataSize shall reflect the combined size of the descriptor and the new | |
// variable value. The authentication descriptor is not part of the variable data and is not | |
// returned by subsequent calls to GetVariable(). | |
// | |
CertData = (EFI_VARIABLE_AUTHENTICATION_2 *)Data; | |
// | |
// Verify that Pad1, Nanosecond, TimeZone, Daylight and Pad2 components of the | |
// TimeStamp value are set to zero. | |
// | |
if ((CertData->TimeStamp.Pad1 != 0) || | |
(CertData->TimeStamp.Nanosecond != 0) || | |
(CertData->TimeStamp.TimeZone != 0) || | |
(CertData->TimeStamp.Daylight != 0) || | |
(CertData->TimeStamp.Pad2 != 0)) | |
{ | |
return EFI_SECURITY_VIOLATION; | |
} | |
if ((OrgTimeStamp != NULL) && ((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0)) { | |
if (AuthServiceInternalCompareTimeStamp (&CertData->TimeStamp, OrgTimeStamp)) { | |
// | |
// TimeStamp check fail, suspicious replay attack, return EFI_SECURITY_VIOLATION. | |
// | |
return EFI_SECURITY_VIOLATION; | |
} | |
} | |
// | |
// wCertificateType should be WIN_CERT_TYPE_EFI_GUID. | |
// Cert type should be EFI_CERT_TYPE_PKCS7_GUID. | |
// | |
if ((CertData->AuthInfo.Hdr.wCertificateType != WIN_CERT_TYPE_EFI_GUID) || | |
!CompareGuid (&CertData->AuthInfo.CertType, &gEfiCertPkcs7Guid)) | |
{ | |
// | |
// Invalid AuthInfo type, return EFI_SECURITY_VIOLATION. | |
// | |
return EFI_SECURITY_VIOLATION; | |
} | |
// | |
// Find out Pkcs7 SignedData which follows the EFI_VARIABLE_AUTHENTICATION_2 descriptor. | |
// AuthInfo.Hdr.dwLength is the length of the entire certificate, including the length of the header. | |
// | |
SigData = CertData->AuthInfo.CertData; | |
SigDataSize = CertData->AuthInfo.Hdr.dwLength - (UINT32)(OFFSET_OF (WIN_CERTIFICATE_UEFI_GUID, CertData)); | |
// | |
// SignedData.digestAlgorithms shall contain the digest algorithm used when preparing the | |
// signature. Only a digest algorithm of SHA-256, SHA-384 or SHA-512 is accepted. | |
// | |
// According to PKCS#7 Definition (https://www.rfc-editor.org/rfc/rfc2315): | |
// SignedData ::= SEQUENCE { | |
// version Version, | |
// digestAlgorithms DigestAlgorithmIdentifiers, | |
// contentInfo ContentInfo, | |
// .... } | |
// The DigestAlgorithmIdentifiers can be used to determine the hash algorithm | |
// in VARIABLE_AUTHENTICATION_2 descriptor. | |
// This field has the fixed offset (+13) or (+32) based on whether the DER-encoded | |
// ContentInfo structure is present or not, and can be calculated based on two | |
// bytes of length encoding. | |
// | |
// Both condition can be handled in WrapPkcs7Data() in CryptPkcs7VerifyCommon.c. | |
// | |
// See below examples: | |
// | |
// 1. Without ContentInfo | |
// 30 82 0c da // SEQUENCE (5 element) (3294 BYTES) -- SignedData | |
// 02 01 01 // INTEGER 1 -- Version | |
// 31 0f // SET (1 element) (15 BYTES) -- DigestAlgorithmIdentifiers | |
// 30 0d // SEQUENCE (2 element) (13 BYTES) -- AlgorithmIdentifier | |
// 06 09 // OBJECT-IDENTIFIER (9 BYTES) -- algorithm | |
// 60 86 48 01 65 03 04 02 01 // sha256 [2.16.840.1.101.3.4.2.1] | |
// 05 00 // NULL (0 BYTES) -- parameters | |
// | |
// Example from: https://uefi.org/revocationlistfile | |
// | |
// 2. With ContentInfo | |
// 30 82 05 90 // SEQUENCE (1424 BYTES) -- ContentInfo | |
// 06 09 // OBJECT-IDENTIFIER (9 BYTES) -- ContentType | |
// 2a 86 48 86 f7 0d 01 07 02 // signedData [1.2.840.113549.1.7.2] | |
// a0 82 05 81 // CONTEXT-SPECIFIC CONSTRUCTED TAG 0 (1409 BYTES) -- content | |
// 30 82 05 7d // SEQUENCE (1405 BYTES) -- SignedData | |
// 02 01 01 // INTEGER 1 -- Version | |
// 31 0f // SET (1 element) (15 BYTES) -- DigestAlgorithmIdentifiers | |
// 30 0d // SEQUENCE (13 BYTES) -- AlgorithmIdentifier | |
// 06 09 // OBJECT-IDENTIFIER (9 BYTES) -- algorithm | |
// 60 86 48 01 65 03 04 02 01 // sha256 [2.16.840.1.101.3.4.2.1] | |
// 05 00 // NULL (0 BYTES) -- parameters | |
// | |
// Example generated with: https://wiki.archlinux.org/title/Unified_Extensible_Firmware_Interface/Secure_Boot#Manual_process | |
// | |
HashAlgId = FindHashAlgorithmIndex (SigData, SigDataSize); | |
if ((Attributes & EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS) != 0) { | |
if (HashAlgId >= (sizeof (mHashInfo) / sizeof (EFI_HASH_INFO))) { | |
return EFI_SECURITY_VIOLATION; | |
} | |
} | |
// | |
// Find out the new data payload which follows Pkcs7 SignedData directly. | |
// | |
PayloadPtr = SigData + SigDataSize; | |
PayloadSize = DataSize - OFFSET_OF_AUTHINFO2_CERT_DATA - (UINTN)SigDataSize; | |
// If the VariablePolicy engine is disabled, allow deletion of any authenticated variables. | |
if ((PayloadSize == 0) && ((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0) && !IsVariablePolicyEnabled ()) { | |
VerifyStatus = TRUE; | |
goto Exit; | |
} | |
// | |
// Construct a serialization buffer of the values of the VariableName, VendorGuid and Attributes | |
// parameters of the SetVariable() call and the TimeStamp component of the | |
// EFI_VARIABLE_AUTHENTICATION_2 descriptor followed by the variable's new value | |
// i.e. (VariableName, VendorGuid, Attributes, TimeStamp, Data) | |
// | |
NewDataSize = PayloadSize + sizeof (EFI_TIME) + sizeof (UINT32) + | |
sizeof (EFI_GUID) + StrSize (VariableName) - sizeof (CHAR16); | |
// | |
// Here is to reuse scratch data area(at the end of volatile variable store) | |
// to reduce SMRAM consumption for SMM variable driver. | |
// The scratch buffer is enough to hold the serialized data and safe to use, | |
// because it is only used at here to do verification temporarily first | |
// and then used in UpdateVariable() for a time based auth variable set. | |
// | |
Status = mAuthVarLibContextIn->GetScratchBuffer (&NewDataSize, (VOID **)&NewData); | |
if (EFI_ERROR (Status)) { | |
return EFI_OUT_OF_RESOURCES; | |
} | |
Buffer = NewData; | |
Length = StrLen (VariableName) * sizeof (CHAR16); | |
CopyMem (Buffer, VariableName, Length); | |
Buffer += Length; | |
Length = sizeof (EFI_GUID); | |
CopyMem (Buffer, VendorGuid, Length); | |
Buffer += Length; | |
Length = sizeof (UINT32); | |
CopyMem (Buffer, &Attr, Length); | |
Buffer += Length; | |
Length = sizeof (EFI_TIME); | |
CopyMem (Buffer, &CertData->TimeStamp, Length); | |
Buffer += Length; | |
CopyMem (Buffer, PayloadPtr, PayloadSize); | |
if (AuthVarType == AuthVarTypePk) { | |
// | |
// Verify that the signature has been made with the current Platform Key (no chaining for PK). | |
// First, get signer's certificates from SignedData. | |
// | |
VerifyStatus = Pkcs7GetSigners ( | |
SigData, | |
SigDataSize, | |
&SignerCerts, | |
&CertStackSize, | |
&TopLevelCert, | |
&TopLevelCertSize | |
); | |
if (!VerifyStatus) { | |
goto Exit; | |
} | |
// | |
// Second, get the current platform key from variable. Check whether it's identical with signer's certificates | |
// in SignedData. If not, return error immediately. | |
// | |
Status = AuthServiceInternalFindVariable ( | |
EFI_PLATFORM_KEY_NAME, | |
&gEfiGlobalVariableGuid, | |
&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
VerifyStatus = FALSE; | |
goto Exit; | |
} | |
CertList = (EFI_SIGNATURE_LIST *)Data; | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); | |
if ((TopLevelCertSize != (CertList->SignatureSize - (sizeof (EFI_SIGNATURE_DATA) - 1))) || | |
(CompareMem (Cert->SignatureData, TopLevelCert, TopLevelCertSize) != 0)) | |
{ | |
VerifyStatus = FALSE; | |
goto Exit; | |
} | |
// | |
// Verify Pkcs7 SignedData via Pkcs7Verify library. | |
// | |
VerifyStatus = Pkcs7Verify ( | |
SigData, | |
SigDataSize, | |
TopLevelCert, | |
TopLevelCertSize, | |
NewData, | |
NewDataSize | |
); | |
} else if (AuthVarType == AuthVarTypeKek) { | |
// | |
// Get KEK database from variable. | |
// | |
Status = AuthServiceInternalFindVariable ( | |
EFI_KEY_EXCHANGE_KEY_NAME, | |
&gEfiGlobalVariableGuid, | |
&Data, | |
&DataSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
// | |
// Ready to verify Pkcs7 SignedData. Go through KEK Signature Database to find out X.509 CertList. | |
// | |
KekDataSize = (UINT32)DataSize; | |
CertList = (EFI_SIGNATURE_LIST *)Data; | |
while ((KekDataSize > 0) && (KekDataSize >= CertList->SignatureListSize)) { | |
if (CompareGuid (&CertList->SignatureType, &gEfiCertX509Guid)) { | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); | |
CertCount = (CertList->SignatureListSize - sizeof (EFI_SIGNATURE_LIST) - CertList->SignatureHeaderSize) / CertList->SignatureSize; | |
for (Index = 0; Index < CertCount; Index++) { | |
// | |
// Iterate each Signature Data Node within this CertList for a verify | |
// | |
TrustedCert = Cert->SignatureData; | |
TrustedCertSize = CertList->SignatureSize - (sizeof (EFI_SIGNATURE_DATA) - 1); | |
// | |
// Verify Pkcs7 SignedData via Pkcs7Verify library. | |
// | |
VerifyStatus = Pkcs7Verify ( | |
SigData, | |
SigDataSize, | |
TrustedCert, | |
TrustedCertSize, | |
NewData, | |
NewDataSize | |
); | |
if (VerifyStatus) { | |
goto Exit; | |
} | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)Cert + CertList->SignatureSize); | |
} | |
} | |
KekDataSize -= CertList->SignatureListSize; | |
CertList = (EFI_SIGNATURE_LIST *)((UINT8 *)CertList + CertList->SignatureListSize); | |
} | |
} else if (AuthVarType == AuthVarTypePriv) { | |
// | |
// Process common authenticated variable except PK/KEK/DB/DBX/DBT. | |
// Get signer's certificates from SignedData. | |
// | |
VerifyStatus = Pkcs7GetSigners ( | |
SigData, | |
SigDataSize, | |
&SignerCerts, | |
&CertStackSize, | |
&TopLevelCert, | |
&TopLevelCertSize | |
); | |
if (!VerifyStatus) { | |
goto Exit; | |
} | |
// | |
// Get previously stored signer's certificates from certdb or certdbv for existing | |
// variable. Check whether they are identical with signer's certificates | |
// in SignedData. If not, return error immediately. | |
// | |
if (OrgTimeStamp != NULL) { | |
VerifyStatus = FALSE; | |
Status = GetCertsFromDb (VariableName, VendorGuid, Attributes, &CertsInCertDb, &CertsSizeinDb); | |
if (EFI_ERROR (Status)) { | |
goto Exit; | |
} | |
if ((HashAlgId < (sizeof (mHashInfo) / sizeof (EFI_HASH_INFO))) && (CertsSizeinDb == mHashInfo[HashAlgId].HashSize)) { | |
// | |
// Check hash of signer cert CommonName + Top-level issuer tbsCertificate against data in CertDb | |
// | |
CertDataPtr = (EFI_CERT_DATA *)(SignerCerts + 1); | |
Status = CalculatePrivAuthVarSignChainSHADigest ( | |
HashAlgId, | |
CertDataPtr->CertDataBuffer, | |
ReadUnaligned32 ((UINT32 *)&(CertDataPtr->CertDataLength)), | |
TopLevelCert, | |
TopLevelCertSize, | |
ShaDigest | |
); | |
if (EFI_ERROR (Status) || (CompareMem (ShaDigest, CertsInCertDb, CertsSizeinDb) != 0)) { | |
goto Exit; | |
} | |
} else { | |
// | |
// Keep backward compatible with previous solution which saves whole signer certs stack in CertDb | |
// | |
if ((CertStackSize != CertsSizeinDb) || | |
(CompareMem (SignerCerts, CertsInCertDb, CertsSizeinDb) != 0)) | |
{ | |
goto Exit; | |
} | |
} | |
} | |
VerifyStatus = Pkcs7Verify ( | |
SigData, | |
SigDataSize, | |
TopLevelCert, | |
TopLevelCertSize, | |
NewData, | |
NewDataSize | |
); | |
if (!VerifyStatus) { | |
goto Exit; | |
} | |
if ((OrgTimeStamp == NULL) && (PayloadSize != 0)) { | |
// | |
// When adding a new common authenticated variable, always save Hash of cn of signer cert + tbsCertificate of Top-level issuer | |
// | |
CertDataPtr = (EFI_CERT_DATA *)(SignerCerts + 1); | |
Status = InsertCertsToDb ( | |
HashAlgId, | |
VariableName, | |
VendorGuid, | |
Attributes, | |
CertDataPtr->CertDataBuffer, | |
ReadUnaligned32 ((UINT32 *)&(CertDataPtr->CertDataLength)), | |
TopLevelCert, | |
TopLevelCertSize | |
); | |
if (EFI_ERROR (Status)) { | |
VerifyStatus = FALSE; | |
goto Exit; | |
} | |
} | |
} else if (AuthVarType == AuthVarTypePayload) { | |
CertList = (EFI_SIGNATURE_LIST *)PayloadPtr; | |
Cert = (EFI_SIGNATURE_DATA *)((UINT8 *)CertList + sizeof (EFI_SIGNATURE_LIST) + CertList->SignatureHeaderSize); | |
TrustedCert = Cert->SignatureData; | |
TrustedCertSize = CertList->SignatureSize - (sizeof (EFI_SIGNATURE_DATA) - 1); | |
// | |
// Verify Pkcs7 SignedData via Pkcs7Verify library. | |
// | |
VerifyStatus = Pkcs7Verify ( | |
SigData, | |
SigDataSize, | |
TrustedCert, | |
TrustedCertSize, | |
NewData, | |
NewDataSize | |
); | |
} else { | |
return EFI_SECURITY_VIOLATION; | |
} | |
Exit: | |
if ((AuthVarType == AuthVarTypePk) || (AuthVarType == AuthVarTypePriv)) { | |
if (TopLevelCert != NULL) { | |
Pkcs7FreeSigners (TopLevelCert); | |
} | |
if (SignerCerts != NULL) { | |
Pkcs7FreeSigners (SignerCerts); | |
} | |
} | |
if (!VerifyStatus) { | |
return EFI_SECURITY_VIOLATION; | |
} | |
Status = CheckSignatureListFormat (VariableName, VendorGuid, PayloadPtr, PayloadSize); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
*VarPayloadPtr = PayloadPtr; | |
*VarPayloadSize = PayloadSize; | |
return EFI_SUCCESS; | |
} | |
/** | |
Process variable with EFI_VARIABLE_TIME_BASED_AUTHENTICATED_WRITE_ACCESS set | |
Caution: This function may receive untrusted input. | |
This function may be invoked in SMM mode, and datasize and data are external input. | |
This function will do basic validation, before parse the data. | |
This function will parse the authentication carefully to avoid security issues, like | |
buffer overflow, integer overflow. | |
@param[in] VariableName Name of Variable to be found. | |
@param[in] VendorGuid Variable vendor GUID. | |
@param[in] Data Data pointer. | |
@param[in] DataSize Size of Data found. If size is less than the | |
data, this value contains the required size. | |
@param[in] Attributes Attribute value of the variable. | |
@param[in] AuthVarType Verify against PK, KEK database, private database or certificate in data payload. | |
@param[out] VarDel Delete the variable or not. | |
@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 | |
VerifyTimeBasedPayloadAndUpdate ( | |
IN CHAR16 *VariableName, | |
IN EFI_GUID *VendorGuid, | |
IN VOID *Data, | |
IN UINTN DataSize, | |
IN UINT32 Attributes, | |
IN AUTHVAR_TYPE AuthVarType, | |
OUT BOOLEAN *VarDel | |
) | |
{ | |
EFI_STATUS Status; | |
EFI_STATUS FindStatus; | |
UINT8 *PayloadPtr; | |
UINTN PayloadSize; | |
EFI_VARIABLE_AUTHENTICATION_2 *CertData; | |
AUTH_VARIABLE_INFO OrgVariableInfo; | |
BOOLEAN IsDel; | |
ZeroMem (&OrgVariableInfo, sizeof (OrgVariableInfo)); | |
FindStatus = mAuthVarLibContextIn->FindVariable ( | |
VariableName, | |
VendorGuid, | |
&OrgVariableInfo | |
); | |
Status = VerifyTimeBasedPayload ( | |
VariableName, | |
VendorGuid, | |
Data, | |
DataSize, | |
Attributes, | |
AuthVarType, | |
(!EFI_ERROR (FindStatus)) ? OrgVariableInfo.TimeStamp : NULL, | |
&PayloadPtr, | |
&PayloadSize | |
); | |
if (EFI_ERROR (Status)) { | |
return Status; | |
} | |
if ( !EFI_ERROR (FindStatus) | |
&& (PayloadSize == 0) | |
&& ((Attributes & EFI_VARIABLE_APPEND_WRITE) == 0)) | |
{ | |
IsDel = TRUE; | |
} else { | |
IsDel = FALSE; | |
} | |
CertData = (EFI_VARIABLE_AUTHENTICATION_2 *)Data; | |
// | |
// Final step: Update/Append Variable if it pass Pkcs7Verify | |
// | |
Status = AuthServiceInternalUpdateVariableWithTimeStamp ( | |
VariableName, | |
VendorGuid, | |
PayloadPtr, | |
PayloadSize, | |
Attributes, | |
&CertData->TimeStamp | |
); | |
// | |
// Delete signer's certificates when delete the common authenticated variable. | |
// | |
if (IsDel && (AuthVarType == AuthVarTypePriv) && !EFI_ERROR (Status)) { | |
Status = DeleteCertsFromDb (VariableName, VendorGuid, Attributes); | |
} | |
if (VarDel != NULL) { | |
if (IsDel && !EFI_ERROR (Status)) { | |
*VarDel = TRUE; | |
} else { | |
*VarDel = FALSE; | |
} | |
} | |
return Status; | |
} |