464 lines
13 KiB
C
464 lines
13 KiB
C
/** @file
|
|
Firmware authentication routines
|
|
;******************************************************************************
|
|
;* Copyright (c) 2012 - 2021, Insyde Software Corp. All Rights Reserved.
|
|
;*
|
|
;* You may not reproduce, distribute, publish, display, perform, modify, adapt,
|
|
;* transmit, broadcast, present, recite, release, license or otherwise exploit
|
|
;* any part of this publication in any form, by any means, without the prior
|
|
;* written permission of Insyde Software Corporation.
|
|
;*
|
|
;******************************************************************************
|
|
*/
|
|
|
|
#include <PiPei.h>
|
|
#include <Library/BaseLib.h>
|
|
#include <Library/PcdLib.h>
|
|
#include <Library/RetrieveSpecificFfsInFv.h>
|
|
#include <Library/BaseMemoryLib.h>
|
|
#include <Library/MemoryAllocationLib.h>
|
|
#include <Library/DebugLib.h>
|
|
#include <Library/BaseCryptLib.h>
|
|
#include <Guid/FirmwareFileSystem2.h>
|
|
#include <Ppi/FirmwareAuthentication.h>
|
|
|
|
#define MEMORY_SIZE_4K 0x1000
|
|
#define EFI_CERT_TYPE_RSA2048_SHA256_SIZE 256
|
|
#define EFI_CERT_TYPE_RSA1024_SHA256_SIZE 128
|
|
|
|
CONST STATIC UINT8 mRsaE[] = { 0x01, 0x00, 0x01 };
|
|
|
|
//
|
|
// Signature of Certification, for not having signature in machine code of this library, assign value one by one byte
|
|
//
|
|
STATIC CHAR8 mCertSignature[] = {'$', '_', 'I', 'F', 'L', 'A','S','H', '_', 'B', 'I', 'O', 'S', 'C', 'E', 'R'};
|
|
|
|
/**
|
|
SHA256 HASH calculation
|
|
|
|
@param [in] Message The message data to be calculated
|
|
@param [in] MessageSize The size in byte of the message data
|
|
@param [out] Digest The caclulated HASH digest
|
|
|
|
@retval EFI_SUCCESS The HASH value is calculated
|
|
@retval EFI_SECURITY_VIOLATION Failed to calculate the HASH
|
|
|
|
**/
|
|
EFI_STATUS
|
|
CalculateSha256Hash (
|
|
IN UINT8 *Message,
|
|
IN UINTN MessageSize,
|
|
OUT UINT8 *Digest
|
|
)
|
|
{
|
|
VOID *HashCtx;
|
|
UINTN CtxSize;
|
|
EFI_STATUS Status;
|
|
|
|
SetMem (Digest, SHA256_DIGEST_SIZE, 0);
|
|
CtxSize = Sha256GetContextSize ();
|
|
HashCtx = NULL;
|
|
HashCtx = AllocatePool (CtxSize);
|
|
if (HashCtx == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
if (!Sha256Init (HashCtx)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
goto Done;
|
|
}
|
|
if(!Sha256Update (HashCtx, Message, MessageSize)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
goto Done;
|
|
}
|
|
if(!Sha256Final (HashCtx, Digest)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
} else {
|
|
Status = EFI_SUCCESS;
|
|
}
|
|
|
|
Done:
|
|
FreePool (HashCtx);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
SHA1 HASH calculation
|
|
|
|
@param [in] Message The message data to be calculated
|
|
@param [in] MessageSize The size in byte of the message data
|
|
@param [out] Digest The caclulated HASH digest
|
|
|
|
@retval EFI_SUCCESS The HASH value is calculated
|
|
@retval EFI_SECURITY_VIOLATION Failed to calculate the HASH
|
|
|
|
**/
|
|
EFI_STATUS
|
|
EFIAPI
|
|
CalculateSha1Hash (
|
|
IN UINT8 *Message,
|
|
IN UINTN MessageSize,
|
|
OUT UINT8 *Digest
|
|
)
|
|
{
|
|
VOID *HashCtx;
|
|
UINTN CtxSize;
|
|
EFI_STATUS Status;
|
|
|
|
SetMem (Digest, SHA1_DIGEST_SIZE, 0);
|
|
CtxSize = Sha1GetContextSize ();
|
|
HashCtx = NULL;
|
|
HashCtx = AllocatePool (CtxSize);
|
|
if (HashCtx == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
|
|
if (!Sha1Init (HashCtx)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
goto Done;
|
|
}
|
|
if(!Sha1Update (HashCtx, Message, MessageSize)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
goto Done;
|
|
}
|
|
if(!Sha1Final (HashCtx, Digest)) {
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
} else {
|
|
Status = EFI_SUCCESS;
|
|
}
|
|
|
|
Done:
|
|
FreePool (HashCtx);
|
|
return Status;
|
|
}
|
|
|
|
/**
|
|
Update the data of the unsigned FV region.
|
|
|
|
@param ImageBuffer The image address
|
|
@param ImageSize The size of the image
|
|
|
|
@retval EFI_SUCCESS update succuessfully
|
|
@retval EFI_NOT_FOUND cannot find the unsigned FV region
|
|
|
|
**/
|
|
EFI_STATUS
|
|
UnsignedFvRegionUpdate (
|
|
IN UINT8 *ImageBuffer,
|
|
IN UINTN ImageSize
|
|
)
|
|
{
|
|
UINTN Index;
|
|
UINTN Index2;
|
|
EFI_FIRMWARE_VOLUME_HEADER *FvHeaderPtr;
|
|
EFI_FFS_FILE_HEADER *FileHeaderPtr;
|
|
UINT32 FileLength;
|
|
BOOLEAN FileGuidFound;
|
|
UINT8 *SkipRegionAddress;
|
|
UINTN SkipRegionSize;
|
|
|
|
FvHeaderPtr = NULL;
|
|
FileHeaderPtr = NULL;
|
|
FileGuidFound = FALSE;
|
|
|
|
//
|
|
// Find the skip region from the image, preserve it before modifying it.
|
|
//
|
|
for (Index = 0; Index < ImageSize; Index += ALIGHMENT_SIZE) {
|
|
FvHeaderPtr = (EFI_FIRMWARE_VOLUME_HEADER *)(ImageBuffer + Index);
|
|
if ((FvHeaderPtr->Signature == EFI_FVH_SIGNATURE) &&
|
|
(CompareGuid (&FvHeaderPtr->FileSystemGuid, &gEfiFirmwareFileSystem2Guid))) {
|
|
Index2 = 0;
|
|
while (Index2 < FvHeaderPtr->FvLength) {
|
|
FileHeaderPtr = (EFI_FFS_FILE_HEADER *)(ImageBuffer + Index + FvHeaderPtr->HeaderLength + Index2);
|
|
if (CompareGuid (PcdGetPtr (PcdUnsignedFvKeyFile), &FileHeaderPtr->Name) == TRUE) {
|
|
FileGuidFound = TRUE;
|
|
break;
|
|
} else {
|
|
FileLength = *(UINT32 *)(FileHeaderPtr->Size) & 0x00FFFFFF;
|
|
Index2 += FileLength;
|
|
}
|
|
}
|
|
}
|
|
if (FileGuidFound) {
|
|
break;
|
|
}
|
|
}
|
|
if (!FileGuidFound) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
SkipRegionAddress = (UINT8 *)FvHeaderPtr;
|
|
SkipRegionSize = (UINTN)(FvHeaderPtr->FvLength);
|
|
SetMem (SkipRegionAddress, SkipRegionSize, 0xFF);
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
|
|
/**
|
|
Check if the image is a PE image
|
|
|
|
@param ImageBuffer The image data
|
|
|
|
|
|
@retval TRUE The image binary is a PE formated image
|
|
@retval FALSE The image binary is not a PE formated image
|
|
|
|
**/
|
|
BOOLEAN
|
|
FileIsPeImage (
|
|
IN UINT8 *ImageBuffer
|
|
)
|
|
{
|
|
ASSERT (ImageBuffer != NULL);
|
|
return ((CHAR8)ImageBuffer[0] == 'M') && ((CHAR8)ImageBuffer[1] == 'Z');
|
|
}
|
|
|
|
/**
|
|
Retrive firmware binary from a PE formated image (standalone flash utility)
|
|
|
|
@param[in] FirmwareFileData PE32 file image buffer
|
|
@param[in] FirmwareFileSize File size of the recovery firmware file
|
|
@param[out] BiosImageOffset BIOS image offset from the firmware file beginning
|
|
@param[out] BiosImageSize BIOS image size
|
|
@param[out] SignatureOffset RSA signature offset from the firmware file beginning
|
|
@param[out] SignatureSize RSA signature size
|
|
|
|
@retval EFI_SUCCESS The firmware binary is successfully retrieved
|
|
@retval EFI_INVALID_PARAMETER The given FirmwareBin or FirmwareSize are NULL pointers
|
|
@retval EFI_NOT_FOUND Unable to find BIOS image or signature data
|
|
**/
|
|
EFI_STATUS
|
|
RetrieveFirmwareFromPeImage (
|
|
IN UINT8 *FirmwareFileData,
|
|
IN UINTN FirmwareFileSize,
|
|
OUT UINTN *BiosImageOffset,
|
|
OUT UINTN *BiosImageSize,
|
|
OUT UINTN *SignatureOffset,
|
|
OUT UINTN *SignatureSize
|
|
)
|
|
{
|
|
UINTN Index;
|
|
ISFLASH_DATA_REGION_HEADER *DataRegion;
|
|
ISFLASH_DATA_REGION_HEADER *Cert;
|
|
|
|
|
|
if ((FirmwareFileData == NULL) || (FirmwareFileSize == 0)) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Search for BIOS image
|
|
//
|
|
for (Index = 0; Index < FirmwareFileSize - ISFLASH_TAG_SIZE; Index++) {
|
|
if (CompareMem (FirmwareFileData + Index, ISFLASH_BIOS_IMAGE_TAG, ISFLASH_TAG_SIZE) == 0) {
|
|
break;
|
|
}
|
|
}
|
|
if (Index == FirmwareFileSize - ISFLASH_TAG_SIZE) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Copy BIOS image to the start of FirmwareBin pointer
|
|
//
|
|
DataRegion = (ISFLASH_DATA_REGION_HEADER *)(FirmwareFileData + Index);
|
|
*BiosImageOffset = Index + sizeof (ISFLASH_DATA_REGION_HEADER);
|
|
*BiosImageSize = DataRegion->DataSize;
|
|
if (*BiosImageOffset + *BiosImageSize > FirmwareFileSize) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
//
|
|
// Search for RSA-2048, RSA-1024 signature
|
|
//
|
|
for (Index = 0;Index < FirmwareFileSize - ISFLASH_TAG_SIZE; Index++) {
|
|
Cert = (ISFLASH_DATA_REGION_HEADER*)(FirmwareFileData + Index);
|
|
if (CompareMem (Cert, mCertSignature, ISFLASH_TAG_SIZE) == 0 && \
|
|
( Cert->AllocatedSize >= Cert->DataSize ) && \
|
|
( (Cert->DataSize >= EFI_CERT_TYPE_RSA1024_SHA256_SIZE))) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (Index == FirmwareFileSize - ISFLASH_TAG_SIZE) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
|
|
|
|
//
|
|
// Append signature to the BIOS image
|
|
//
|
|
DataRegion = (ISFLASH_DATA_REGION_HEADER *)(FirmwareFileData + Index);
|
|
*SignatureOffset = Index + sizeof (ISFLASH_DATA_REGION_HEADER);
|
|
*SignatureSize = DataRegion->DataSize;
|
|
if (*SignatureOffset + *SignatureSize > FirmwareFileSize) {
|
|
return EFI_NOT_FOUND;
|
|
}
|
|
|
|
return EFI_SUCCESS;
|
|
}
|
|
|
|
/**
|
|
Helper function to verify signature by using input public key
|
|
|
|
@param [in] PublicKey Pointer to input public key.
|
|
@param [in] PublicKeySize Public key size in bytes.
|
|
@param [in] Signature Pointer to input signature.
|
|
@param [in] SignatureSize Signature buffer size in bytes.
|
|
@param [in] Digest Pointer to input digest.
|
|
@param [in] DigestSize Digest size in bytes.
|
|
|
|
@retval EFI_SUCCESS The signature verification is successful
|
|
@retval EFI_SECURITY_VIOLATION Failed to verify the firmware
|
|
**/
|
|
STATIC
|
|
EFI_STATUS
|
|
VerifyImageByPublicKey (
|
|
IN UINT8 *PublicKey,
|
|
IN UINTN PublicKeySize,
|
|
IN UINT8 *Signature,
|
|
IN UINTN SignatureSize,
|
|
IN UINT8 *Digest,
|
|
IN UINTN DigestSize
|
|
)
|
|
{
|
|
VOID *Rsa;
|
|
EFI_STATUS Status;
|
|
|
|
Rsa = RsaNew ();
|
|
if (Rsa == NULL) {
|
|
return EFI_SECURITY_VIOLATION;
|
|
}
|
|
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
if(!RsaSetKey (Rsa, RsaKeyN, PublicKey, PublicKeySize)) {
|
|
goto Done;
|
|
}
|
|
if(!RsaSetKey (Rsa, RsaKeyE, mRsaE, sizeof (mRsaE))) {
|
|
goto Done;
|
|
}
|
|
|
|
if (RsaPkcs1Verify (Rsa, Digest, DigestSize, Signature, SignatureSize)) {
|
|
Status = EFI_SUCCESS;
|
|
}
|
|
|
|
Done:
|
|
RsaFree (Rsa);
|
|
return Status;
|
|
}
|
|
|
|
|
|
/**
|
|
Firmware verification with RSA2048-SHA256
|
|
|
|
@param [in] FirmwareFileData Firmware file data buffer
|
|
@param [in] FirmwareFileSize The firmware file size including signature
|
|
|
|
@retval EFI_SUCCESS The firmware verification is successful
|
|
@retval EFI_OUT_OF_RESOURCES Out of resources
|
|
@retval EFI_SECURITY_VIOLATION Failed to verify the firmware
|
|
|
|
**/
|
|
EFI_STATUS
|
|
VerifyFirmware (
|
|
IN UINT8 *FirmwareFileData,
|
|
IN UINTN FirmwareFileSize
|
|
)
|
|
{
|
|
EFI_STATUS Status;
|
|
EFI_STATUS StatusSha1;
|
|
EFI_STATUS StatusSha256;
|
|
UINT8 *Signature;
|
|
UINT8 *AllPublickKeys;
|
|
UINTN TotalKeySize;
|
|
UINT8 *PublicKey;
|
|
UINTN PublicKeySize;
|
|
UINT8 DigestSha256[SHA256_DIGEST_SIZE];
|
|
UINT8 DigestSha1[SHA1_DIGEST_SIZE];
|
|
UINT8 *BiosImage;
|
|
UINTN BiosImageOffset;
|
|
UINTN BiosImageSize;
|
|
UINTN SignatureOffset;
|
|
UINTN SignatureSize;
|
|
UINT8 *BiosImageBackup;
|
|
UINTN Offset;
|
|
|
|
BiosImageBackup = NULL;
|
|
|
|
if (FirmwareFileData == NULL || FirmwareFileSize == 0) {
|
|
return EFI_INVALID_PARAMETER;
|
|
}
|
|
|
|
//
|
|
// Initial setting for BIOS image and signature information
|
|
//
|
|
BiosImageOffset = 0;
|
|
BiosImageSize = FirmwareFileSize - EFI_CERT_TYPE_RSA2048_SHA256_SIZE;
|
|
SignatureOffset = FirmwareFileSize - EFI_CERT_TYPE_RSA2048_SHA256_SIZE;
|
|
SignatureSize = EFI_CERT_TYPE_RSA2048_SHA256_SIZE;
|
|
|
|
if (!FeaturePcdGet (PcdSecureFlashSupported)) {
|
|
return EFI_SUCCESS;
|
|
}
|
|
//
|
|
// If firmware file is an executable secure flash image,
|
|
// update BIOS image and signature information
|
|
//
|
|
if (FileIsPeImage (FirmwareFileData)) {
|
|
Status = RetrieveFirmwareFromPeImage (
|
|
FirmwareFileData,
|
|
FirmwareFileSize,
|
|
&BiosImageOffset,
|
|
&BiosImageSize,
|
|
&SignatureOffset,
|
|
&SignatureSize
|
|
);
|
|
if (EFI_ERROR (Status)) {
|
|
ASSERT_EFI_ERROR (Status);
|
|
return EFI_SECURITY_VIOLATION;
|
|
}
|
|
}
|
|
BiosImage = FirmwareFileData + BiosImageOffset;
|
|
Signature = FirmwareFileData + SignatureOffset;
|
|
|
|
if (PcdGetBool (PcdUnsignedFvSupported)) {
|
|
BiosImageBackup = AllocatePages ((BiosImageSize / MEMORY_SIZE_4K) + 1);
|
|
if (BiosImageBackup == NULL) {
|
|
return EFI_OUT_OF_RESOURCES;
|
|
}
|
|
CopyMem (BiosImageBackup, BiosImage, BiosImageSize);
|
|
UnsignedFvRegionUpdate (BiosImage, BiosImageSize);
|
|
}
|
|
|
|
Status = RetrieveSpecificFfsInFv (PcdGetPtr (PcdSecureFlashPublicKeyFile), (VOID **)&AllPublickKeys, &TotalKeySize);
|
|
if (EFI_ERROR (Status)) {
|
|
goto Done;
|
|
}
|
|
StatusSha256 = CalculateSha256Hash (BiosImage, BiosImageSize, DigestSha256);
|
|
StatusSha1 = CalculateSha1Hash (BiosImage, BiosImageSize, DigestSha1);
|
|
Status = EFI_SECURITY_VIOLATION;
|
|
for (Offset = 0; Offset < TotalKeySize; Offset += (PublicKeySize + sizeof (UINT32))) {
|
|
PublicKey = AllPublickKeys + Offset + sizeof (UINT32);
|
|
PublicKeySize = *((UINT32 *)(AllPublickKeys + Offset));
|
|
if (!EFI_ERROR (StatusSha256)) {
|
|
Status = VerifyImageByPublicKey (PublicKey, PublicKeySize, Signature, SignatureSize, DigestSha256, SHA256_DIGEST_SIZE);
|
|
}
|
|
if (!EFI_ERROR (StatusSha1) && EFI_ERROR (Status)) {
|
|
Status = VerifyImageByPublicKey (PublicKey, PublicKeySize, Signature, SignatureSize, DigestSha1, SHA1_DIGEST_SIZE);
|
|
}
|
|
if (!EFI_ERROR (Status)) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
Done:
|
|
if (PcdGetBool (PcdUnsignedFvSupported)) {
|
|
CopyMem (BiosImage, BiosImageBackup, BiosImageSize);
|
|
FreePool (BiosImageBackup);
|
|
}
|
|
return Status;
|
|
}
|