From 319b042e81bbad7ed7f7722df7ac51c88d6900d4 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:21:35 -0700 Subject: [PATCH 1/9] OpensslPkg: Implement GetAuthenticodeHash Add the OneCrypto v1.1 GetAuthenticodeHash() implementation in the OpensslPkg BaseCryptLib instance. The function computes a PE/COFF Authenticode-style image hash: it parses and validates the PE/COFF headers, hashes the image header up to and excluding the CheckSum and Cert Directory entry, hashes the sections in PointerToRawData order, and hashes any trailing bytes between the end of the last section and the start of the certificate table. The hashing primitives are the BaseCryptLib Sha1 / Sha256 / Sha384 / Sha512 routines, so this source is independent of the underlying provider library. The digest algorithm is selected by GUID (gEfiCertSha1Guid, gEfiCertSha256Guid, gEfiCertSha384Guid, gEfiCertSha512Guid). A separate CryptAuthenticodeHashNull.c stub is added for the PEI / Runtime / SEC / SMM phase library instances that do not provide the implementation. Caution: The PE/COFF image is treated as untrusted input. All header fields are bounds-checked against FileSize before use to avoid out-of-bounds reads. Signed-off-by: Doug Flick --- .../Library/BaseCryptLib/BaseCryptLib.inf | 8 + .../Library/BaseCryptLib/PeiCryptLib.inf | 1 + .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 447 ++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 43 ++ .../Library/BaseCryptLib/RuntimeCryptLib.inf | 1 + .../Library/BaseCryptLib/SecCryptLib.inf | 1 + .../Library/BaseCryptLib/SmmCryptLib.inf | 1 + .../BaseCryptLib/UnitTestHostBaseCryptLib.inf | 8 + 8 files changed, 510 insertions(+) create mode 100644 OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c create mode 100644 OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf index 2f4e10383..d2678e6d1 100644 --- a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -58,6 +58,7 @@ Pk/CryptDh.c Pk/CryptX509.c Pk/CryptAuthenticode.c + Pk/CryptAuthenticodeHash.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c @@ -108,6 +109,13 @@ [Protocols] # gEfiMpServiceProtocolGuid # MU_CHANGE +[Guids] + ## SOMETIMES_CONSUMES ## GUID # Image hash type used by GetAuthenticodeHash + gEfiCertSha1Guid # MU_CHANGE + gEfiCertSha256Guid # MU_CHANGE + gEfiCertSha384Guid # MU_CHANGE + gEfiCertSha512Guid # MU_CHANGE + # # Remove these [BuildOptions] after this library is cleaned up # diff --git a/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf index d4eb940ac..8c4d49563 100644 --- a/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -54,6 +54,7 @@ Pk/CryptDhNull.c Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c new file mode 100644 index 000000000..0b0fbafa0 --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -0,0 +1,447 @@ +/** @file + PE/COFF Authenticode Image Hash computation. + + Implements GetAuthenticodeHash() per the "Windows Authenticode Portable + Executable Signature Format" specification. The hash covers the entire + PE/COFF image except for the image checksum, the Certificate Table + data-directory entry, and the certificate table content itself. + + Caution: This module operates on untrusted input (the PE/COFF image), + so each header field is validated against FileSize before use. + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +#include +#include + +// +// Function pointer table type for a single hash algorithm. +// We use the BaseCryptLib hash primitives so this implementation is +// independent of the underlying crypto provider (OpenSSL / Mbed TLS). +// +typedef +UINTN +(EFIAPI *AUTH_HASH_GET_CONTEXT_SIZE)( + VOID + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_INIT)( + OUT VOID *HashContext + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_UPDATE)( + IN OUT VOID *HashContext, + IN CONST VOID *Data, + IN UINTN DataSize + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_FINAL)( + IN OUT VOID *HashContext, + OUT UINT8 *HashValue + ); + +typedef struct { + CONST EFI_GUID *HashGuid; + UINTN DigestSize; + AUTH_HASH_GET_CONTEXT_SIZE GetContextSize; + AUTH_HASH_INIT Init; + AUTH_HASH_UPDATE Update; + AUTH_HASH_FINAL Final; +} AUTH_HASH_INFO; + +// +// Forward references to BaseCryptLib hash primitives. These are provided +// by the BaseCryptLib hash sources in this same library instance. +// +STATIC CONST AUTH_HASH_INFO mAuthHashInfo[] = { + { &gEfiCertSha1Guid, SHA1_DIGEST_SIZE, Sha1GetContextSize, Sha1Init, Sha1Update, Sha1Final }, + { &gEfiCertSha256Guid, SHA256_DIGEST_SIZE, Sha256GetContextSize, Sha256Init, Sha256Update, Sha256Final }, + { &gEfiCertSha384Guid, SHA384_DIGEST_SIZE, Sha384GetContextSize, Sha384Init, Sha384Update, Sha384Final }, + { &gEfiCertSha512Guid, SHA512_DIGEST_SIZE, Sha512GetContextSize, Sha512Init, Sha512Update, Sha512Final }, +}; + +#define AUTH_HASH_INFO_COUNT (sizeof (mAuthHashInfo) / sizeof (mAuthHashInfo[0])) + +/** + Look up an entry in mAuthHashInfo by HashType GUID. + + @param[in] HashType Signature-type GUID identifying the hash algorithm. + + @retval Pointer to the AUTH_HASH_INFO on match. + @retval NULL if HashType does not match a supported algorithm. +**/ +STATIC +CONST AUTH_HASH_INFO * +LookupAuthHashInfo ( + IN CONST EFI_GUID *HashType + ) +{ + UINTN Index; + + for (Index = 0; Index < AUTH_HASH_INFO_COUNT; Index++) { + if (CompareGuid (HashType, mAuthHashInfo[Index].HashGuid)) { + return &mAuthHashInfo[Index]; + } + } + + return NULL; +} + +/** + Compute the PE/COFF Authenticode-style image hash of a loaded image, + as described in the "Windows Authenticode Portable Executable + Signature Format" specification. + + The caller selects the digest algorithm by HashType (e.g. + gEfiCertSha256Guid, gEfiCertSha384Guid). The digest is written to + Digest, which must be large enough to hold the largest supported + digest (at least SHA512_DIGEST_SIZE bytes). + + Caution: This function may receive untrusted input. The PE/COFF image + is external input, so this function validates the image's data + structure before hashing. + + @param[in] FileBuffer Pointer to the in-memory PE/COFF image. + @param[in] FileSize Size of FileBuffer in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. Must be at least + SHA512_DIGEST_SIZE bytes. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_SUCCESS Digest was computed successfully. + @retval EFI_INVALID_PARAMETER A required pointer is NULL or + FileSize is 0. + @retval EFI_UNSUPPORTED HashType is not a recognized image + hash algorithm, or this interface is + not supported by the underlying + library instance. +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHash ( + IN VOID *FileBuffer, + IN UINTN FileSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + EFI_STATUS Status; + CONST AUTH_HASH_INFO *HashInfo; + UINT8 *ImageBase; + UINTN PeCoffHeaderOffset; + EFI_IMAGE_DOS_HEADER *DosHdr; + EFI_IMAGE_OPTIONAL_HEADER_UNION *NtHdr; + UINT16 Magic; + UINT32 NumberOfRvaAndSizes; + UINT32 SizeOfHeaders; + UINT16 NumberOfSections; + UINT16 SizeOfOptionalHeader; + UINT8 *CheckSumPtr; + UINT8 *SecDirPtr; + EFI_IMAGE_DATA_DIRECTORY *SecDir; + UINT32 CertSize; + VOID *HashCtx; + UINTN CtxSize; + UINT8 *HashBase; + UINTN HashSize; + UINTN SumOfBytesHashed; + EFI_IMAGE_SECTION_HEADER *SectionHeaders; + EFI_IMAGE_SECTION_HEADER *Section; + UINTN SectionHeadersSize; + UINTN FirstSectionOffset; + UINTN Index; + UINTN Pos; + + HashCtx = NULL; + SectionHeaders = NULL; + Status = EFI_INVALID_PARAMETER; + + if ((FileBuffer == NULL) || (FileSize == 0) || (HashType == NULL) || + (Digest == NULL) || (DigestSize == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashInfo = LookupAuthHashInfo (HashType); + if (HashInfo == NULL) { + return EFI_UNSUPPORTED; + } + + ImageBase = (UINT8 *)FileBuffer; + + // + // Locate the PE header. + // + if (FileSize < sizeof (EFI_IMAGE_DOS_HEADER)) { + return EFI_INVALID_PARAMETER; + } + + DosHdr = (EFI_IMAGE_DOS_HEADER *)ImageBase; + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + PeCoffHeaderOffset = DosHdr->e_lfanew; + } else { + PeCoffHeaderOffset = 0; + } + + if ((PeCoffHeaderOffset > FileSize) || + ((FileSize - PeCoffHeaderOffset) < sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + sizeof (UINT16))) + { + return EFI_INVALID_PARAMETER; + } + + NtHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)(ImageBase + PeCoffHeaderOffset); + if (NtHdr->Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) { + return EFI_INVALID_PARAMETER; + } + + // + // FileHeader is at the same offset for PE32 and PE32+. + // + SizeOfOptionalHeader = NtHdr->Pe32.FileHeader.SizeOfOptionalHeader; + NumberOfSections = NtHdr->Pe32.FileHeader.NumberOfSections; + + // + // Make sure the optional header fits. + // + if ((FileSize - PeCoffHeaderOffset) < + sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader) + { + return EFI_INVALID_PARAMETER; + } + + Magic = NtHdr->Pe32.OptionalHeader.Magic; + + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + if (SizeOfOptionalHeader < sizeof (EFI_IMAGE_OPTIONAL_HEADER32)) { + return EFI_INVALID_PARAMETER; + } + + CheckSumPtr = (UINT8 *)&NtHdr->Pe32.OptionalHeader.CheckSum; + NumberOfRvaAndSizes = NtHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; + SizeOfHeaders = NtHdr->Pe32.OptionalHeader.SizeOfHeaders; + } else if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + if (SizeOfOptionalHeader < sizeof (EFI_IMAGE_OPTIONAL_HEADER64)) { + return EFI_INVALID_PARAMETER; + } + + CheckSumPtr = (UINT8 *)&NtHdr->Pe32Plus.OptionalHeader.CheckSum; + NumberOfRvaAndSizes = NtHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; + SizeOfHeaders = NtHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + } else { + return EFI_UNSUPPORTED; + } + + // + // SizeOfHeaders must be within FileSize. + // + if ((SizeOfHeaders > FileSize) || + (SizeOfHeaders < (PeCoffHeaderOffset + sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader))) + { + return EFI_INVALID_PARAMETER; + } + + // + // The section headers must lie within the headers region. + // + FirstSectionOffset = PeCoffHeaderOffset + sizeof (UINT32) + + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader; + SectionHeadersSize = (UINTN)NumberOfSections * sizeof (EFI_IMAGE_SECTION_HEADER); + if ((SectionHeadersSize / sizeof (EFI_IMAGE_SECTION_HEADER)) != (UINTN)NumberOfSections) { + return EFI_INVALID_PARAMETER; + } + + if ((FirstSectionOffset > FileSize) || + ((FileSize - FirstSectionOffset) < SectionHeadersSize) || + ((FirstSectionOffset + SectionHeadersSize) > SizeOfHeaders)) + { + return EFI_INVALID_PARAMETER; + } + + // + // Allocate and initialize the hash context. + // + CtxSize = HashInfo->GetContextSize (); + HashCtx = AllocatePool (CtxSize); + if (HashCtx == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (!HashInfo->Init (HashCtx)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + // + // Step 1-4: Hash the image header from its base to the start of the + // CheckSum field. + // + HashBase = ImageBase; + HashSize = (UINTN)(CheckSumPtr - ImageBase); + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + // + // Step 5: Skip over the image checksum (4 bytes). + // Step 6/7: Hash everything from the end of the checksum to either the + // end of the optional header or the start of the Cert Directory entry. + // + if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY) { + HashBase = CheckSumPtr + sizeof (UINT32); + HashSize = SizeOfHeaders - (UINTN)(HashBase - ImageBase); + SecDir = NULL; + } else { + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + SecDirPtr = (UINT8 *)&NtHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + SecDir = &NtHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } else { + SecDirPtr = (UINT8 *)&NtHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + SecDir = &NtHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } + + HashBase = CheckSumPtr + sizeof (UINT32); + HashSize = (UINTN)(SecDirPtr - HashBase); + } + + if (HashSize != 0) { + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } + + // + // Step 8/9: Skip the Cert Directory entry. Hash from end of cert dir + // entry to end of image header. + // + if (SecDir != NULL) { + HashBase = SecDirPtr + sizeof (EFI_IMAGE_DATA_DIRECTORY); + HashSize = SizeOfHeaders - (UINTN)(HashBase - ImageBase); + if (HashSize != 0) { + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } + } + + // + // Step 10: SUM_OF_BYTES_HASHED = SizeOfHeaders. + // + SumOfBytesHashed = SizeOfHeaders; + + // + // Step 11-12: Build a sorted table of section headers. + // + if (NumberOfSections != 0) { + SectionHeaders = AllocateZeroPool (SectionHeadersSize); + if (SectionHeaders == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + + Section = (EFI_IMAGE_SECTION_HEADER *)(ImageBase + FirstSectionOffset); + + // + // Insertion sort by PointerToRawData. + // + for (Index = 0; Index < (UINTN)NumberOfSections; Index++) { + Pos = Index; + while ((Pos > 0) && (Section->PointerToRawData < SectionHeaders[Pos - 1].PointerToRawData)) { + CopyMem (&SectionHeaders[Pos], &SectionHeaders[Pos - 1], sizeof (EFI_IMAGE_SECTION_HEADER)); + Pos--; + } + + CopyMem (&SectionHeaders[Pos], Section, sizeof (EFI_IMAGE_SECTION_HEADER)); + Section++; + } + + // + // Step 13-15: Hash each section in order. + // + for (Index = 0; Index < (UINTN)NumberOfSections; Index++) { + Section = &SectionHeaders[Index]; + if (Section->SizeOfRawData == 0) { + continue; + } + + // + // Validate the section bounds against the file size. + // + if ((Section->PointerToRawData > FileSize) || + ((FileSize - Section->PointerToRawData) < (UINTN)Section->SizeOfRawData)) + { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + HashBase = ImageBase + Section->PointerToRawData; + HashSize = (UINTN)Section->SizeOfRawData; + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + SumOfBytesHashed += HashSize; + } + } + + // + // Step 16: Hash any trailing bytes between SUM_OF_BYTES_HASHED and the + // start of the certificate table (or end of file if no cert table). + // + if (FileSize > SumOfBytesHashed) { + HashBase = ImageBase + SumOfBytesHashed; + + if ((SecDir == NULL) || (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY)) { + CertSize = 0; + } else { + CertSize = SecDir->Size; + } + + if (FileSize > (UINTN)CertSize + SumOfBytesHashed) { + HashSize = FileSize - (UINTN)CertSize - SumOfBytesHashed; + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } else if (FileSize < (UINTN)CertSize + SumOfBytesHashed) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + } + + if (!HashInfo->Final (HashCtx, Digest)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + *DigestSize = HashInfo->DigestSize; + Status = EFI_SUCCESS; + +Done: + if (HashCtx != NULL) { + FreePool (HashCtx); + } + + if (SectionHeaders != NULL) { + FreePool (SectionHeaders); + } + + return Status; +} diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c new file mode 100644 index 000000000..5ea092e4c --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -0,0 +1,43 @@ +/** @file + GetAuthenticodeHash() Null implementation. + + Returns EFI_UNSUPPORTED to indicate this interface is not provided by + the current library instance (e.g., PEI / Runtime / SEC / SMM phases + that do not include the Pk/CryptAuthenticodeHash.c implementation). + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +/** + Compute the PE/COFF Authenticode-style image hash of a loaded image. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] FileBuffer Pointer to the in-memory PE/COFF image. + @param[in] FileSize Size of FileBuffer in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHash ( + IN VOID *FileBuffer, + IN UINTN FileSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} diff --git a/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf index 1bee26764..cd728a359 100644 --- a/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -57,6 +57,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPssNull.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf index 100686d6c..b2bd97e91 100644 --- a/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -49,6 +49,7 @@ Pk/CryptDhNull.c Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pem/CryptPemNull.c Rand/CryptRandNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf index ae075b49d..9b36f20ba 100644 --- a/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -58,6 +58,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf index 23bc87e92..017727062 100644 --- a/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -45,6 +45,7 @@ Pk/CryptDh.c Pk/CryptX509.c Pk/CryptAuthenticode.c + Pk/CryptAuthenticodeHash.c # MU_CHANGE Pk/CryptTs.c Pem/CryptPem.c Pk/CryptRsaPss.c @@ -73,6 +74,13 @@ OpensslLib PrintLib +[Guids] + ## SOMETIMES_CONSUMES ## GUID # Image hash type used by GetAuthenticodeHash + gEfiCertSha1Guid # MU_CHANGE + gEfiCertSha256Guid # MU_CHANGE + gEfiCertSha384Guid # MU_CHANGE + gEfiCertSha512Guid # MU_CHANGE + # # Remove these [BuildOptions] after this library is cleaned up # From a3d7d74fa97d2c9d31abdedcc1036a15a3e7673f Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:22:26 -0700 Subject: [PATCH 2/9] MbedTlsPkg: Implement GetAuthenticodeHash Add the OneCrypto v1.1 GetAuthenticodeHash() implementation in the MbedTlsPkg BaseCryptLib instance. The function computes a PE/COFF Authenticode-style image hash: it parses and validates the PE/COFF headers, hashes the image header up to and excluding the CheckSum and Cert Directory entry, hashes the sections in PointerToRawData order, and hashes any trailing bytes between the end of the last section and the start of the certificate table. The hashing primitives are the BaseCryptLib Sha1 / Sha256 / Sha384 / Sha512 routines, so this source is independent of the underlying provider library. The digest algorithm is selected by GUID (gEfiCertSha1Guid, gEfiCertSha256Guid, gEfiCertSha384Guid, gEfiCertSha512Guid). A separate CryptAuthenticodeHashNull.c stub is added for the PEI / Runtime / SEC / SMM phase library instances that do not provide the implementation. Caution: The PE/COFF image is treated as untrusted input. All header fields are bounds-checked against FileSize before use to avoid out-of-bounds reads. Signed-off-by: Doug Flick --- .../Library/BaseCryptLib/BaseCryptLib.inf | 8 + .../Library/BaseCryptLib/PeiCryptLib.inf | 1 + .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 447 ++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 43 ++ .../Library/BaseCryptLib/RuntimeCryptLib.inf | 1 + .../Library/BaseCryptLib/SecCryptLib.inf | 1 + .../Library/BaseCryptLib/SmmCryptLib.inf | 1 + .../Library/BaseCryptLib/TestBaseCryptLib.inf | 8 + .../BaseCryptLib/UnitTestHostBaseCryptLib.inf | 8 + 9 files changed, 518 insertions(+) create mode 100644 MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c create mode 100644 MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf index 6161df737..44f429330 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -43,6 +43,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticode.c + Pk/CryptAuthenticodeHash.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c @@ -78,6 +79,13 @@ SynchronizationLib [Protocols] gEfiMpServiceProtocolGuid + +[Guids] + ## SOMETIMES_CONSUMES ## GUID # Image hash type used by GetAuthenticodeHash + gEfiCertSha1Guid # MU_CHANGE + gEfiCertSha256Guid # MU_CHANGE + gEfiCertSha384Guid # MU_CHANGE + gEfiCertSha512Guid # MU_CHANGE # # Remove these [BuildOptions] after this library is cleaned up # diff --git a/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf index 92ec84a51..489ac9c92 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -56,6 +56,7 @@ Pk/CryptDhNull.c Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c new file mode 100644 index 000000000..0b0fbafa0 --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -0,0 +1,447 @@ +/** @file + PE/COFF Authenticode Image Hash computation. + + Implements GetAuthenticodeHash() per the "Windows Authenticode Portable + Executable Signature Format" specification. The hash covers the entire + PE/COFF image except for the image checksum, the Certificate Table + data-directory entry, and the certificate table content itself. + + Caution: This module operates on untrusted input (the PE/COFF image), + so each header field is validated against FileSize before use. + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +#include +#include + +// +// Function pointer table type for a single hash algorithm. +// We use the BaseCryptLib hash primitives so this implementation is +// independent of the underlying crypto provider (OpenSSL / Mbed TLS). +// +typedef +UINTN +(EFIAPI *AUTH_HASH_GET_CONTEXT_SIZE)( + VOID + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_INIT)( + OUT VOID *HashContext + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_UPDATE)( + IN OUT VOID *HashContext, + IN CONST VOID *Data, + IN UINTN DataSize + ); + +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_FINAL)( + IN OUT VOID *HashContext, + OUT UINT8 *HashValue + ); + +typedef struct { + CONST EFI_GUID *HashGuid; + UINTN DigestSize; + AUTH_HASH_GET_CONTEXT_SIZE GetContextSize; + AUTH_HASH_INIT Init; + AUTH_HASH_UPDATE Update; + AUTH_HASH_FINAL Final; +} AUTH_HASH_INFO; + +// +// Forward references to BaseCryptLib hash primitives. These are provided +// by the BaseCryptLib hash sources in this same library instance. +// +STATIC CONST AUTH_HASH_INFO mAuthHashInfo[] = { + { &gEfiCertSha1Guid, SHA1_DIGEST_SIZE, Sha1GetContextSize, Sha1Init, Sha1Update, Sha1Final }, + { &gEfiCertSha256Guid, SHA256_DIGEST_SIZE, Sha256GetContextSize, Sha256Init, Sha256Update, Sha256Final }, + { &gEfiCertSha384Guid, SHA384_DIGEST_SIZE, Sha384GetContextSize, Sha384Init, Sha384Update, Sha384Final }, + { &gEfiCertSha512Guid, SHA512_DIGEST_SIZE, Sha512GetContextSize, Sha512Init, Sha512Update, Sha512Final }, +}; + +#define AUTH_HASH_INFO_COUNT (sizeof (mAuthHashInfo) / sizeof (mAuthHashInfo[0])) + +/** + Look up an entry in mAuthHashInfo by HashType GUID. + + @param[in] HashType Signature-type GUID identifying the hash algorithm. + + @retval Pointer to the AUTH_HASH_INFO on match. + @retval NULL if HashType does not match a supported algorithm. +**/ +STATIC +CONST AUTH_HASH_INFO * +LookupAuthHashInfo ( + IN CONST EFI_GUID *HashType + ) +{ + UINTN Index; + + for (Index = 0; Index < AUTH_HASH_INFO_COUNT; Index++) { + if (CompareGuid (HashType, mAuthHashInfo[Index].HashGuid)) { + return &mAuthHashInfo[Index]; + } + } + + return NULL; +} + +/** + Compute the PE/COFF Authenticode-style image hash of a loaded image, + as described in the "Windows Authenticode Portable Executable + Signature Format" specification. + + The caller selects the digest algorithm by HashType (e.g. + gEfiCertSha256Guid, gEfiCertSha384Guid). The digest is written to + Digest, which must be large enough to hold the largest supported + digest (at least SHA512_DIGEST_SIZE bytes). + + Caution: This function may receive untrusted input. The PE/COFF image + is external input, so this function validates the image's data + structure before hashing. + + @param[in] FileBuffer Pointer to the in-memory PE/COFF image. + @param[in] FileSize Size of FileBuffer in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. Must be at least + SHA512_DIGEST_SIZE bytes. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_SUCCESS Digest was computed successfully. + @retval EFI_INVALID_PARAMETER A required pointer is NULL or + FileSize is 0. + @retval EFI_UNSUPPORTED HashType is not a recognized image + hash algorithm, or this interface is + not supported by the underlying + library instance. +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHash ( + IN VOID *FileBuffer, + IN UINTN FileSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + EFI_STATUS Status; + CONST AUTH_HASH_INFO *HashInfo; + UINT8 *ImageBase; + UINTN PeCoffHeaderOffset; + EFI_IMAGE_DOS_HEADER *DosHdr; + EFI_IMAGE_OPTIONAL_HEADER_UNION *NtHdr; + UINT16 Magic; + UINT32 NumberOfRvaAndSizes; + UINT32 SizeOfHeaders; + UINT16 NumberOfSections; + UINT16 SizeOfOptionalHeader; + UINT8 *CheckSumPtr; + UINT8 *SecDirPtr; + EFI_IMAGE_DATA_DIRECTORY *SecDir; + UINT32 CertSize; + VOID *HashCtx; + UINTN CtxSize; + UINT8 *HashBase; + UINTN HashSize; + UINTN SumOfBytesHashed; + EFI_IMAGE_SECTION_HEADER *SectionHeaders; + EFI_IMAGE_SECTION_HEADER *Section; + UINTN SectionHeadersSize; + UINTN FirstSectionOffset; + UINTN Index; + UINTN Pos; + + HashCtx = NULL; + SectionHeaders = NULL; + Status = EFI_INVALID_PARAMETER; + + if ((FileBuffer == NULL) || (FileSize == 0) || (HashType == NULL) || + (Digest == NULL) || (DigestSize == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashInfo = LookupAuthHashInfo (HashType); + if (HashInfo == NULL) { + return EFI_UNSUPPORTED; + } + + ImageBase = (UINT8 *)FileBuffer; + + // + // Locate the PE header. + // + if (FileSize < sizeof (EFI_IMAGE_DOS_HEADER)) { + return EFI_INVALID_PARAMETER; + } + + DosHdr = (EFI_IMAGE_DOS_HEADER *)ImageBase; + if (DosHdr->e_magic == EFI_IMAGE_DOS_SIGNATURE) { + PeCoffHeaderOffset = DosHdr->e_lfanew; + } else { + PeCoffHeaderOffset = 0; + } + + if ((PeCoffHeaderOffset > FileSize) || + ((FileSize - PeCoffHeaderOffset) < sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + sizeof (UINT16))) + { + return EFI_INVALID_PARAMETER; + } + + NtHdr = (EFI_IMAGE_OPTIONAL_HEADER_UNION *)(ImageBase + PeCoffHeaderOffset); + if (NtHdr->Pe32.Signature != EFI_IMAGE_NT_SIGNATURE) { + return EFI_INVALID_PARAMETER; + } + + // + // FileHeader is at the same offset for PE32 and PE32+. + // + SizeOfOptionalHeader = NtHdr->Pe32.FileHeader.SizeOfOptionalHeader; + NumberOfSections = NtHdr->Pe32.FileHeader.NumberOfSections; + + // + // Make sure the optional header fits. + // + if ((FileSize - PeCoffHeaderOffset) < + sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader) + { + return EFI_INVALID_PARAMETER; + } + + Magic = NtHdr->Pe32.OptionalHeader.Magic; + + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + if (SizeOfOptionalHeader < sizeof (EFI_IMAGE_OPTIONAL_HEADER32)) { + return EFI_INVALID_PARAMETER; + } + + CheckSumPtr = (UINT8 *)&NtHdr->Pe32.OptionalHeader.CheckSum; + NumberOfRvaAndSizes = NtHdr->Pe32.OptionalHeader.NumberOfRvaAndSizes; + SizeOfHeaders = NtHdr->Pe32.OptionalHeader.SizeOfHeaders; + } else if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR64_MAGIC) { + if (SizeOfOptionalHeader < sizeof (EFI_IMAGE_OPTIONAL_HEADER64)) { + return EFI_INVALID_PARAMETER; + } + + CheckSumPtr = (UINT8 *)&NtHdr->Pe32Plus.OptionalHeader.CheckSum; + NumberOfRvaAndSizes = NtHdr->Pe32Plus.OptionalHeader.NumberOfRvaAndSizes; + SizeOfHeaders = NtHdr->Pe32Plus.OptionalHeader.SizeOfHeaders; + } else { + return EFI_UNSUPPORTED; + } + + // + // SizeOfHeaders must be within FileSize. + // + if ((SizeOfHeaders > FileSize) || + (SizeOfHeaders < (PeCoffHeaderOffset + sizeof (UINT32) + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader))) + { + return EFI_INVALID_PARAMETER; + } + + // + // The section headers must lie within the headers region. + // + FirstSectionOffset = PeCoffHeaderOffset + sizeof (UINT32) + + sizeof (EFI_IMAGE_FILE_HEADER) + (UINTN)SizeOfOptionalHeader; + SectionHeadersSize = (UINTN)NumberOfSections * sizeof (EFI_IMAGE_SECTION_HEADER); + if ((SectionHeadersSize / sizeof (EFI_IMAGE_SECTION_HEADER)) != (UINTN)NumberOfSections) { + return EFI_INVALID_PARAMETER; + } + + if ((FirstSectionOffset > FileSize) || + ((FileSize - FirstSectionOffset) < SectionHeadersSize) || + ((FirstSectionOffset + SectionHeadersSize) > SizeOfHeaders)) + { + return EFI_INVALID_PARAMETER; + } + + // + // Allocate and initialize the hash context. + // + CtxSize = HashInfo->GetContextSize (); + HashCtx = AllocatePool (CtxSize); + if (HashCtx == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + if (!HashInfo->Init (HashCtx)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + // + // Step 1-4: Hash the image header from its base to the start of the + // CheckSum field. + // + HashBase = ImageBase; + HashSize = (UINTN)(CheckSumPtr - ImageBase); + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + // + // Step 5: Skip over the image checksum (4 bytes). + // Step 6/7: Hash everything from the end of the checksum to either the + // end of the optional header or the start of the Cert Directory entry. + // + if (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY) { + HashBase = CheckSumPtr + sizeof (UINT32); + HashSize = SizeOfHeaders - (UINTN)(HashBase - ImageBase); + SecDir = NULL; + } else { + if (Magic == EFI_IMAGE_NT_OPTIONAL_HDR32_MAGIC) { + SecDirPtr = (UINT8 *)&NtHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + SecDir = &NtHdr->Pe32.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } else { + SecDirPtr = (UINT8 *)&NtHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + SecDir = &NtHdr->Pe32Plus.OptionalHeader.DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY]; + } + + HashBase = CheckSumPtr + sizeof (UINT32); + HashSize = (UINTN)(SecDirPtr - HashBase); + } + + if (HashSize != 0) { + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } + + // + // Step 8/9: Skip the Cert Directory entry. Hash from end of cert dir + // entry to end of image header. + // + if (SecDir != NULL) { + HashBase = SecDirPtr + sizeof (EFI_IMAGE_DATA_DIRECTORY); + HashSize = SizeOfHeaders - (UINTN)(HashBase - ImageBase); + if (HashSize != 0) { + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } + } + + // + // Step 10: SUM_OF_BYTES_HASHED = SizeOfHeaders. + // + SumOfBytesHashed = SizeOfHeaders; + + // + // Step 11-12: Build a sorted table of section headers. + // + if (NumberOfSections != 0) { + SectionHeaders = AllocateZeroPool (SectionHeadersSize); + if (SectionHeaders == NULL) { + Status = EFI_OUT_OF_RESOURCES; + goto Done; + } + + Section = (EFI_IMAGE_SECTION_HEADER *)(ImageBase + FirstSectionOffset); + + // + // Insertion sort by PointerToRawData. + // + for (Index = 0; Index < (UINTN)NumberOfSections; Index++) { + Pos = Index; + while ((Pos > 0) && (Section->PointerToRawData < SectionHeaders[Pos - 1].PointerToRawData)) { + CopyMem (&SectionHeaders[Pos], &SectionHeaders[Pos - 1], sizeof (EFI_IMAGE_SECTION_HEADER)); + Pos--; + } + + CopyMem (&SectionHeaders[Pos], Section, sizeof (EFI_IMAGE_SECTION_HEADER)); + Section++; + } + + // + // Step 13-15: Hash each section in order. + // + for (Index = 0; Index < (UINTN)NumberOfSections; Index++) { + Section = &SectionHeaders[Index]; + if (Section->SizeOfRawData == 0) { + continue; + } + + // + // Validate the section bounds against the file size. + // + if ((Section->PointerToRawData > FileSize) || + ((FileSize - Section->PointerToRawData) < (UINTN)Section->SizeOfRawData)) + { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + + HashBase = ImageBase + Section->PointerToRawData; + HashSize = (UINTN)Section->SizeOfRawData; + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + SumOfBytesHashed += HashSize; + } + } + + // + // Step 16: Hash any trailing bytes between SUM_OF_BYTES_HASHED and the + // start of the certificate table (or end of file if no cert table). + // + if (FileSize > SumOfBytesHashed) { + HashBase = ImageBase + SumOfBytesHashed; + + if ((SecDir == NULL) || (NumberOfRvaAndSizes <= EFI_IMAGE_DIRECTORY_ENTRY_SECURITY)) { + CertSize = 0; + } else { + CertSize = SecDir->Size; + } + + if (FileSize > (UINTN)CertSize + SumOfBytesHashed) { + HashSize = FileSize - (UINTN)CertSize - SumOfBytesHashed; + if (!HashInfo->Update (HashCtx, HashBase, HashSize)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + } else if (FileSize < (UINTN)CertSize + SumOfBytesHashed) { + Status = EFI_INVALID_PARAMETER; + goto Done; + } + } + + if (!HashInfo->Final (HashCtx, Digest)) { + Status = EFI_DEVICE_ERROR; + goto Done; + } + + *DigestSize = HashInfo->DigestSize; + Status = EFI_SUCCESS; + +Done: + if (HashCtx != NULL) { + FreePool (HashCtx); + } + + if (SectionHeaders != NULL) { + FreePool (SectionHeaders); + } + + return Status; +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c new file mode 100644 index 000000000..5ea092e4c --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -0,0 +1,43 @@ +/** @file + GetAuthenticodeHash() Null implementation. + + Returns EFI_UNSUPPORTED to indicate this interface is not provided by + the current library instance (e.g., PEI / Runtime / SEC / SMM phases + that do not include the Pk/CryptAuthenticodeHash.c implementation). + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +/** + Compute the PE/COFF Authenticode-style image hash of a loaded image. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] FileBuffer Pointer to the in-memory PE/COFF image. + @param[in] FileSize Size of FileBuffer in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHash ( + IN VOID *FileBuffer, + IN UINTN FileSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf index f3bbefb6a..f061fe442 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -49,6 +49,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPssNull.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf index 40b31e03b..ba597416b 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -51,6 +51,7 @@ Pk/CryptPkcs7VerifyEkuNull.c Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Rand/CryptRandNull.c SysCall/CrtWrapper.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf index a1948cec6..715b19725 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -48,6 +48,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticodeNull.c + Pk/CryptAuthenticodeHashNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf index b96088ec3..a408b3305 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf @@ -42,6 +42,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticode.c + Pk/CryptAuthenticodeHash.c # MU_CHANGE Pk/CryptTs.c Pem/CryptPem.c Pk/CryptRsaPss.c @@ -68,6 +69,13 @@ PrintLib RngLib +[Guids] + ## SOMETIMES_CONSUMES ## GUID # Image hash type used by GetAuthenticodeHash + gEfiCertSha1Guid # MU_CHANGE + gEfiCertSha256Guid # MU_CHANGE + gEfiCertSha384Guid # MU_CHANGE + gEfiCertSha512Guid # MU_CHANGE + # # Remove these [BuildOptions] after this library is cleaned up # diff --git a/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf index 7accd5c24..21056ce13 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -48,6 +48,7 @@ Pk/CryptDhNull.c Pk/CryptX509.c Pk/CryptAuthenticode.c + Pk/CryptAuthenticodeHash.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c @@ -74,6 +75,13 @@ PrintLib RngLib +[Guids] + ## SOMETIMES_CONSUMES ## GUID # Image hash type used by GetAuthenticodeHash + gEfiCertSha1Guid # MU_CHANGE + gEfiCertSha256Guid # MU_CHANGE + gEfiCertSha384Guid # MU_CHANGE + gEfiCertSha512Guid # MU_CHANGE + # # Remove these [BuildOptions] after this library is cleaned up # From 38508340bb1de90c63e554e3aaf2150d79ba429b Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:22:35 -0700 Subject: [PATCH 3/9] OneCryptoPkg: Publish GetAuthenticodeHash in v1.1 protocol Wire the GetAuthenticodeHash function pointer into the OneCryptoBin ONE_CRYPTO_PROTOCOL initialization so the v1.1 protocol field is populated for both the OpensslPkg and MbedTlsPkg builds of the unified OneCrypto binary. Signed-off-by: Doug Flick --- OneCryptoPkg/OneCryptoBin/OneCryptoBin.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c index 051672e4d..d01b349f2 100644 --- a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c +++ b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c @@ -333,6 +333,12 @@ CryptoInit ( // ======================================================================================================== CryptoProtocol->GetCryptoProviderVersionString = GetCryptoProviderVersionString; + + // ======================================================================================================== + // v1.1 functions + // ======================================================================================================== + + CryptoProtocol->GetAuthenticodeHash = GetAuthenticodeHash; } /** From 56ba406c7636f8e176acc17072ec5068a9dc5c24 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:53:52 -0700 Subject: [PATCH 4/9] OpensslPkg: Implement GetTrustAnchorX509FromAuthData Add the OneCrypto v1.1 GetTrustAnchorX509FromAuthData() and FreeTrustAnchorX509Cache() implementation in the OpensslPkg BaseCryptLib instance. The function walks a PKCS#7 SignedData blob, hashes each embedded X.509 certificate's TBSCertificate, and returns the certificate whose digest matches the caller-supplied hash. The PKCS#7 ASN.1 DER is parsed in-place with bounds-checked length decoding; both the bare SignedData and the ContentInfo wrapper forms are accepted. The certificates [0] IMPLICIT field is enumerated and each Certificate SEQUENCE is matched on its TBSCertificate digest. The hash algorithm is selected by the caller-supplied hash size (20=SHA-1, 32=SHA-256, 48=SHA-384, 64=SHA-512). The hashing primitives are taken from BaseCryptLib so the source is independent of the underlying provider library. The optional caller-managed cache stores up to 64 entries; each entry holds a copy of the certificate DER bytes and lazily-computed TBS digests under each algorithm. A separate CryptTrustAnchorNull.c stub is added for the PEI / Runtime / SEC / SMM phase library instances that do not provide the implementation. Caution: AuthData is treated as untrusted input. All ASN.1 length fields are bounds-checked against the remaining input before the parser advances. Signed-off-by: Doug Flick --- .../Library/BaseCryptLib/BaseCryptLib.inf | 1 + .../Library/BaseCryptLib/PeiCryptLib.inf | 1 + .../BaseCryptLib/Pk/CryptTrustAnchor.c | 752 ++++++++++++++++++ .../BaseCryptLib/Pk/CryptTrustAnchorNull.c | 45 ++ .../Library/BaseCryptLib/RuntimeCryptLib.inf | 1 + .../Library/BaseCryptLib/SecCryptLib.inf | 1 + .../Library/BaseCryptLib/SmmCryptLib.inf | 1 + .../BaseCryptLib/UnitTestHostBaseCryptLib.inf | 1 + 8 files changed, 803 insertions(+) create mode 100644 OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c create mode 100644 OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf index d2678e6d1..6ffd829a4 100644 --- a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -59,6 +59,7 @@ Pk/CryptX509.c Pk/CryptAuthenticode.c Pk/CryptAuthenticodeHash.c # MU_CHANGE + Pk/CryptTrustAnchor.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c diff --git a/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf index 8c4d49563..6a8acdc08 100644 --- a/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -55,6 +55,7 @@ Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c new file mode 100644 index 000000000..f743e98d3 --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c @@ -0,0 +1,752 @@ +/** @file + GetTrustAnchorX509FromAuthData() and the matching cache lifecycle. + + Walks a PKCS#7 SignedData blob, hashes each embedded X.509 certificate's + TBSCertificate, and returns the certificate whose digest matches a + caller-supplied hash. Optional cache memoizes (cert -> TBS-hash[s]) + across calls so callers that ask repeatedly do not re-hash the same + certificates. + + Caution: AuthData is treated as untrusted input. Each ASN.1 length is + bounds-checked against the remaining input before the parser advances. + + Reference: + RFC 2315 / RFC 5652 (PKCS#7 / CMS SignedData) + RFC 5280 (X.509 Certificate) + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +// +// ASN.1 DER constants used during the walk. +// +#define ASN1_TAG_SEQUENCE 0x30 +#define ASN1_TAG_SET 0x31 +#define ASN1_TAG_OID 0x06 +#define ASN1_TAG_CTX_CONS_0 0xA0 // [0] EXPLICIT / IMPLICIT constructed + +// +// signedData OID 1.2.840.113549.1.7.2 = 06 09 2A 86 48 86 F7 0D 01 07 02 +// +STATIC CONST UINT8 mPkcs7SignedDataOid[] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 +}; + +// +// Hash algorithm dispatch table; selected by digest length. The one-shot +// ShaXxxHashAll() entry points handle context allocation, init, update, +// and finalization internally, so the table only needs the digest size +// and the function pointer. +// +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_ALL)( + IN CONST VOID *Data, + IN UINTN DataSize, + OUT UINT8 *HashValue + ); + +typedef struct { + UINTN DigestSize; + AUTH_HASH_ALL HashAll; +} TRUST_ANCHOR_HASH_INFO; + +STATIC CONST TRUST_ANCHOR_HASH_INFO mHashTable[] = { + { SHA1_DIGEST_SIZE, Sha1HashAll }, + { SHA256_DIGEST_SIZE, Sha256HashAll }, + { SHA384_DIGEST_SIZE, Sha384HashAll }, + { SHA512_DIGEST_SIZE, Sha512HashAll }, +}; + +#define HASH_TABLE_COUNT (sizeof (mHashTable) / sizeof (mHashTable[0])) + +// +// Cache structures. The cache stores up to TRUST_ANCHOR_CACHE_MAX entries; +// each entry is a copy of the certificate DER bytes plus a per-algorithm +// TBSCertificate digest computed lazily on first use. The digests are +// inlined (not heap-allocated) — at four 64-byte slots per entry the +// cost is bounded and the cache lifecycle has no inner allocations to +// fail or leak. +// +#define TRUST_ANCHOR_CACHE_SIGNATURE SIGNATURE_32 ('T', 'A', 'X', 'C') +#define TRUST_ANCHOR_CACHE_MAX 64 + +typedef struct { + UINT8 *CertDer; // Allocated cert DER copy. + UINTN CertDerSize; + BOOLEAN TbsHashValid[HASH_TABLE_COUNT]; + UINT8 TbsHash[HASH_TABLE_COUNT][SHA512_DIGEST_SIZE]; +} TRUST_ANCHOR_CACHE_ENTRY; + +typedef struct { + UINT32 Signature; + UINTN Count; + TRUST_ANCHOR_CACHE_ENTRY Entries[TRUST_ANCHOR_CACHE_MAX]; +} TRUST_ANCHOR_CACHE; + +/** + Map a digest size to an index in mHashTable. + + @param[in] HashSize Digest length in bytes. + + @retval Index into mHashTable on match. + @retval (UINTN)-1 if the size is not supported. +**/ +STATIC +UINTN +HashSizeToIndex ( + IN UINTN HashSize + ) +{ + UINTN Index; + + for (Index = 0; Index < HASH_TABLE_COUNT; Index++) { + if (mHashTable[Index].DigestSize == HashSize) { + return Index; + } + } + + return (UINTN)-1; +} + +/** + Decode an ASN.1 DER length field starting at *Cursor. On success, the + decoded length is written to *Length and *Cursor is advanced past the + length octets. + + Only the definite form is accepted. Lengths > the remaining input are + rejected. + + @param[in,out] Cursor Pointer to the cursor pointer; advanced on + success. + @param[in] End One past the last valid input byte. + @param[out] Length Receives the decoded content length. + + @retval EFI_SUCCESS Length parsed. + @retval EFI_INVALID_PARAMETER Malformed encoding or out of bounds. +**/ +STATIC +EFI_STATUS +Asn1DecodeLength ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + OUT UINTN *Length + ) +{ + CONST UINT8 *P; + UINTN Result; + UINTN NumOctets; + UINTN Index; + + P = *Cursor; + if (P >= End) { + return EFI_INVALID_PARAMETER; + } + + if ((*P & 0x80) == 0) { + Result = (UINTN)*P; + P++; + } else { + NumOctets = (UINTN)(*P & 0x7F); + P++; + // + // Reject indefinite (0x80) and lengths longer than UINTN. + // + if ((NumOctets == 0) || (NumOctets > sizeof (UINTN))) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)(End - P) < NumOctets) { + return EFI_INVALID_PARAMETER; + } + + Result = 0; + for (Index = 0; Index < NumOctets; Index++) { + Result = (Result << 8) | P[Index]; + } + + P += NumOctets; + } + + if ((UINTN)(End - P) < Result) { + return EFI_INVALID_PARAMETER; + } + + *Cursor = P; + *Length = Result; + return EFI_SUCCESS; +} + +/** + Parse an ASN.1 DER TLV at *Cursor and require Tag. On success, *Body + points to the value bytes, *BodyLen is the value length, and *Cursor + is advanced past the entire TLV. + + @param[in,out] Cursor Cursor pointer. + @param[in] End One past the last valid input byte. + @param[in] Tag Required tag byte. + @param[out] Body Receives a pointer to the value bytes. + @param[out] BodyLen Receives the value length. + + @retval EFI_SUCCESS TLV parsed. + @retval EFI_INVALID_PARAMETER Wrong tag or malformed encoding. +**/ +STATIC +EFI_STATUS +Asn1ExpectTagged ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + IN UINT8 Tag, + OUT CONST UINT8 **Body, + OUT UINTN *BodyLen + ) +{ + CONST UINT8 *P; + EFI_STATUS Status; + UINTN Length; + + P = *Cursor; + if ((P >= End) || (*P != Tag)) { + return EFI_INVALID_PARAMETER; + } + + P++; + Status = Asn1DecodeLength (&P, End, &Length); + if (EFI_ERROR (Status)) { + return Status; + } + + *Body = P; + *BodyLen = Length; + *Cursor = P + Length; + return EFI_SUCCESS; +} + +/** + Compute the TBSCertificate digest of an X.509 certificate. + + @param[in] CertDer Pointer to the DER-encoded Certificate. + @param[in] CertDerSize Size of the certificate in bytes. + @param[in] HashIndex Algorithm index in mHashTable. + @param[out] Digest Caller-allocated buffer for the digest. Must + be at least mHashTable[HashIndex].DigestSize + bytes. + + @retval EFI_SUCCESS Digest computed. + @retval EFI_INVALID_PARAMETER CertDer is not a well-formed Certificate. + @retval EFI_OUT_OF_RESOURCES Could not allocate hash context. + @retval EFI_DEVICE_ERROR Hash primitive failed. +**/ +STATIC +EFI_STATUS +HashTbsCertificate ( + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize, + IN UINTN HashIndex, + OUT UINT8 *Digest + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *CertBody; + UINTN CertBodyLen; + CONST UINT8 *TbsStart; + CONST UINT8 *TbsBody; + UINTN TbsBodyLen; + EFI_STATUS Status; + + Cursor = CertDer; + End = CertDer + CertDerSize; + + // + // Certificate ::= SEQUENCE + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &CertBody, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // First element of Certificate is TBSCertificate ::= SEQUENCE. + // Hash that whole TLV (tag + length + value) per RFC 5280 4.1.2. + // + TbsStart = CertBody; + Cursor = CertBody; + Status = Asn1ExpectTagged (&Cursor, CertBody + CertBodyLen, ASN1_TAG_SEQUENCE, &TbsBody, &TbsBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + if (!mHashTable[HashIndex].HashAll (TbsStart, (UINTN)(Cursor - TbsStart), Digest)) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} + +/** + Look up a certificate in the cache by DER bytes. If found, return the + entry; otherwise append a new entry with a copy of the cert bytes if + the cache has room. + + @param[in] Cache Cache handle. + @param[in] CertDer Pointer to certificate DER bytes. + @param[in] CertDerSize Size of certificate in bytes. + + @retval Pointer to the matching or newly inserted entry on success. + @retval NULL when the cache is full or memory allocation failed. +**/ +STATIC +TRUST_ANCHOR_CACHE_ENTRY * +CacheLookupOrInsert ( + IN TRUST_ANCHOR_CACHE *Cache, + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize + ) +{ + UINTN Index; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + + for (Index = 0; Index < Cache->Count; Index++) { + Entry = &Cache->Entries[Index]; + if ((Entry->CertDerSize == CertDerSize) && + (CompareMem (Entry->CertDer, CertDer, CertDerSize) == 0)) + { + return Entry; + } + } + + if (Cache->Count >= TRUST_ANCHOR_CACHE_MAX) { + return NULL; + } + + Entry = &Cache->Entries[Cache->Count]; + Entry->CertDer = AllocateCopyPool (CertDerSize, CertDer); + if (Entry->CertDer == NULL) { + return NULL; + } + + Entry->CertDerSize = CertDerSize; + ZeroMem (Entry->TbsHashValid, sizeof (Entry->TbsHashValid)); + Cache->Count++; + return Entry; +} + +/** + Compare a certificate's TBS digest at HashIndex against the supplied + TbsCertHash, using the cache when available to avoid re-hashing. + + @param[in] Cache Cache or NULL. + @param[in] CertDer Pointer to certificate DER bytes. + @param[in] CertDerSize Size of certificate in bytes. + @param[in] HashIndex Algorithm index in mHashTable. + @param[in] TbsCertHash Target digest bytes. + @param[out] IsMatch TRUE on match; FALSE otherwise. + + @retval EFI_SUCCESS IsMatch was set. + @retval other A hashing or allocation error occurred. +**/ +STATIC +EFI_STATUS +CertMatchesTbsHash ( + IN TRUST_ANCHOR_CACHE *Cache OPTIONAL, + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize, + IN UINTN HashIndex, + IN CONST UINT8 *TbsCertHash, + OUT BOOLEAN *IsMatch + ) +{ + EFI_STATUS Status; + UINTN DigestSize; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + UINT8 LocalDigest[SHA512_DIGEST_SIZE]; + UINT8 *DigestBuf; + + *IsMatch = FALSE; + DigestSize = mHashTable[HashIndex].DigestSize; + Entry = (Cache != NULL) ? CacheLookupOrInsert (Cache, CertDer, CertDerSize) : NULL; + + // + // Hash directly into the cache slot when available; fall back to a + // stack buffer when the cache is full or absent. + // + if ((Entry != NULL) && Entry->TbsHashValid[HashIndex]) { + DigestBuf = Entry->TbsHash[HashIndex]; + } else { + DigestBuf = (Entry != NULL) ? Entry->TbsHash[HashIndex] : LocalDigest; + Status = HashTbsCertificate (CertDer, CertDerSize, HashIndex, DigestBuf); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Entry != NULL) { + Entry->TbsHashValid[HashIndex] = TRUE; + } + } + + if (CompareMem (DigestBuf, TbsCertHash, DigestSize) == 0) { + *IsMatch = TRUE; + } + + return EFI_SUCCESS; +} + +/** + Walk a SignedData certificates field and search for a certificate + whose TBSCertificate digest equals TbsCertHash. + + @param[in] CertSetBody Pointer to the contents of the [0] + IMPLICIT certificates field. + @param[in] CertSetBodyLen Length of CertSetBody. + @param[in] HashIndex Algorithm index. + @param[in] TbsCertHash Target digest bytes. + @param[in] Cache Optional cache. + @param[out] MatchCert On success, points within CertSetBody to + the matching certificate's first byte. + @param[out] MatchCertSize On success, size of the matching cert. + + @retval EFI_SUCCESS Match found. + @retval EFI_NOT_FOUND No certificate matched. + @retval other Parse or hashing error. +**/ +STATIC +EFI_STATUS +SearchCertificateSet ( + IN CONST UINT8 *CertSetBody, + IN UINTN CertSetBodyLen, + IN UINTN HashIndex, + IN CONST UINT8 *TbsCertHash, + IN TRUST_ANCHOR_CACHE *Cache OPTIONAL, + OUT CONST UINT8 **MatchCert, + OUT UINTN *MatchCertSize + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *CertStart; + CONST UINT8 *CertBody; + UINTN CertBodyLen; + EFI_STATUS Status; + BOOLEAN Match; + + Cursor = CertSetBody; + End = CertSetBody + CertSetBodyLen; + + while (Cursor < End) { + if (*Cursor != ASN1_TAG_SEQUENCE) { + // + // The certificates field may also contain extendedCertificate or + // other choices in older PKCS#7. Skip non-SEQUENCE TLVs. + // + CertStart = Cursor + 1; + Status = Asn1DecodeLength (&CertStart, End, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = CertStart + CertBodyLen; + continue; + } + + CertStart = Cursor; + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &CertBody, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = CertMatchesTbsHash ( + Cache, + CertStart, + (UINTN)(Cursor - CertStart), + HashIndex, + TbsCertHash, + &Match + ); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Match) { + *MatchCert = CertStart; + *MatchCertSize = (UINTN)(Cursor - CertStart); + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/** + Parse a PKCS#7 SignedData blob and return the contents of the + certificates [0] IMPLICIT field. + + Accepts both the bare SignedData encoding and a ContentInfo wrapper. + + @param[in] AuthData Pointer to PKCS#7 / SignedData bytes. + @param[in] AuthDataSize Size in bytes. + @param[out] CertSetBody Receives a pointer into AuthData where the + certificates contents start. + @param[out] CertSetBodyLen Receives the length of the certificates + contents. + + @retval EFI_SUCCESS Parsed and certificates field located. + @retval EFI_INVALID_PARAMETER Malformed input. + @retval EFI_NOT_FOUND The optional certificates field is + absent. +**/ +STATIC +EFI_STATUS +LocateCertificatesField ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT CONST UINT8 **CertSetBody, + OUT UINTN *CertSetBodyLen + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *Body; + UINTN BodyLen; + CONST UINT8 *OidBody; + UINTN OidLen; + EFI_STATUS Status; + + Cursor = AuthData; + End = AuthData + AuthDataSize; + + // + // Outer SEQUENCE. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // Detect ContentInfo: optional contentType OID followed by [0] EXPLICIT + // SignedData. If we see an OID first, peel the wrapper. + // + if ((Cursor < End) && (*Cursor == ASN1_TAG_OID)) { + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return Status; + } + + if ((OidLen != sizeof (mPkcs7SignedDataOid)) || + (CompareMem (OidBody, mPkcs7SignedDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // [0] EXPLICIT SignedData wrapper. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // Inner SignedData SEQUENCE. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + } + + // + // Now Cursor..End contains the SignedData contents. + // version INTEGER + // digestAlgorithms SET + // encapContentInfo SEQUENCE + // certificates [0] IMPLICIT (optional) <-- the field we want + // crls [1] IMPLICIT (optional) + // signerInfos SET + // + // Skip version + digestAlgorithms + encapContentInfo by parsing each + // TLV and discarding its body. + // + // version (INTEGER, tag 0x02) + // + if (Cursor >= End) { + return EFI_INVALID_PARAMETER; + } + + Status = Asn1ExpectTagged (&Cursor, End, *Cursor, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // digestAlgorithms (SET) + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SET, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // encapContentInfo (SEQUENCE) + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Optional certificates [0] IMPLICIT. + // + if ((Cursor < End) && (*Cursor == ASN1_TAG_CTX_CONS_0)) { + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + *CertSetBody = Body; + *CertSetBodyLen = BodyLen; + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + +/** + Locate, in a PKCS#7 SignedData blob, the X.509 certificate whose + TBSCertificate digest matches a caller-supplied hash, and return + that certificate as a newly allocated DER-encoded buffer. + + See BaseCryptLib.h for the full contract. +**/ +EFI_STATUS +EFIAPI +GetTrustAnchorX509FromAuthData ( + IN OUT VOID **CacheHandle OPTIONAL, + IN CONST UINT8 *TbsCertHash, + IN UINTN TbsCertHashSize, + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT UINT8 **TrustAnchorX509, + OUT UINTN *TrustAnchorX509Size + ) +{ + EFI_STATUS Status; + UINTN HashIndex; + CONST UINT8 *CertSetBody; + UINTN CertSetBodyLen; + CONST UINT8 *MatchCert; + UINTN MatchCertSize; + TRUST_ANCHOR_CACHE *Cache; + UINT8 *Output; + + if ((TbsCertHash == NULL) || (TbsCertHashSize == 0) || + (AuthData == NULL) || (AuthDataSize == 0) || + (TrustAnchorX509 == NULL) || (TrustAnchorX509Size == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashIndex = HashSizeToIndex (TbsCertHashSize); + if (HashIndex == (UINTN)-1) { + return EFI_INVALID_PARAMETER; + } + + // + // Optional cache. *CacheHandle == NULL means "allocate me one"; the + // caller releases it later via FreeTrustAnchorX509Cache(). + // + Cache = NULL; + if (CacheHandle != NULL) { + if (*CacheHandle == NULL) { + Cache = AllocateZeroPool (sizeof (TRUST_ANCHOR_CACHE)); + if (Cache == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Cache->Signature = TRUST_ANCHOR_CACHE_SIGNATURE; + *CacheHandle = Cache; + } else { + Cache = (TRUST_ANCHOR_CACHE *)*CacheHandle; + if (Cache->Signature != TRUST_ANCHOR_CACHE_SIGNATURE) { + return EFI_INVALID_PARAMETER; + } + } + } + + Status = LocateCertificatesField (AuthData, AuthDataSize, &CertSetBody, &CertSetBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SearchCertificateSet ( + CertSetBody, + CertSetBodyLen, + HashIndex, + TbsCertHash, + Cache, + &MatchCert, + &MatchCertSize + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Output = AllocateCopyPool (MatchCertSize, MatchCert); + if (Output == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + *TrustAnchorX509 = Output; + *TrustAnchorX509Size = MatchCertSize; + return EFI_SUCCESS; +} + +/** + Release a trust-anchor cache previously allocated by + GetTrustAnchorX509FromAuthData(). + + @param[in] CacheHandle Cache handle, or NULL. +**/ +VOID +EFIAPI +FreeTrustAnchorX509Cache ( + IN VOID *CacheHandle OPTIONAL + ) +{ + TRUST_ANCHOR_CACHE *Cache; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + UINTN Index; + + if (CacheHandle == NULL) { + return; + } + + Cache = (TRUST_ANCHOR_CACHE *)CacheHandle; + if (Cache->Signature != TRUST_ANCHOR_CACHE_SIGNATURE) { + ASSERT (FALSE); + return; + } + + for (Index = 0; Index < Cache->Count; Index++) { + Entry = &Cache->Entries[Index]; + if (Entry->CertDer != NULL) { + FreePool (Entry->CertDer); + } + } + + ZeroMem (Cache, sizeof (TRUST_ANCHOR_CACHE)); + FreePool (Cache); +} diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c new file mode 100644 index 000000000..2b121479b --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c @@ -0,0 +1,45 @@ +/** @file + GetTrustAnchorX509FromAuthData() / FreeTrustAnchorX509Cache() Null + implementation. + + Returns EFI_UNSUPPORTED to indicate this interface is not provided by + the current library instance (e.g., PEI / Runtime / SEC / SMM phases). + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +/** + Null stub for GetTrustAnchorX509FromAuthData(). + + @retval EFI_UNSUPPORTED This interface is not supported. +**/ +EFI_STATUS +EFIAPI +GetTrustAnchorX509FromAuthData ( + IN OUT VOID **CacheHandle OPTIONAL, + IN CONST UINT8 *TbsCertHash, + IN UINTN TbsCertHashSize, + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT UINT8 **TrustAnchorX509, + OUT UINTN *TrustAnchorX509Size + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + Null stub for FreeTrustAnchorX509Cache(). No-op. +**/ +VOID +EFIAPI +FreeTrustAnchorX509Cache ( + IN VOID *CacheHandle OPTIONAL + ) +{ + ASSERT (FALSE); +} diff --git a/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf index cd728a359..2a3fe1c9f 100644 --- a/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -58,6 +58,7 @@ Pk/CryptX509.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPssNull.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf index b2bd97e91..224d84b33 100644 --- a/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -50,6 +50,7 @@ Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pem/CryptPemNull.c Rand/CryptRandNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf index 9b36f20ba..08a17d24f 100644 --- a/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -59,6 +59,7 @@ Pk/CryptX509.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf index 017727062..7ccc75a0d 100644 --- a/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -46,6 +46,7 @@ Pk/CryptX509.c Pk/CryptAuthenticode.c Pk/CryptAuthenticodeHash.c # MU_CHANGE + Pk/CryptTrustAnchor.c # MU_CHANGE Pk/CryptTs.c Pem/CryptPem.c Pk/CryptRsaPss.c From b4b898b5487dd52627c7f99208018675fb4db251 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:54:09 -0700 Subject: [PATCH 5/9] MbedTlsPkg: Implement GetTrustAnchorX509FromAuthData Add the OneCrypto v1.1 GetTrustAnchorX509FromAuthData() and FreeTrustAnchorX509Cache() implementation in the MbedTlsPkg BaseCryptLib instance. The implementation walks a PKCS#7 SignedData blob, hashes each embedded X.509 certificate's TBSCertificate, and returns the certificate whose digest matches the caller-supplied hash. The source is shared with OpensslPkg: the parser uses only ASN.1 DER primitives and the BaseCryptLib hash API, so it does not depend on the underlying provider library. This is required because the MbedTlsPkg PKCS#7 helper Pkcs7GetCertificatesList() is currently ASSERT(FALSE)-stubbed and cannot be used for certificate enumeration. A CryptTrustAnchorNull.c stub is added for the PEI / Runtime / SEC / SMM phase library instances. Caution: AuthData is treated as untrusted input. All ASN.1 length fields are bounds-checked against the remaining input before the parser advances. Signed-off-by: Doug Flick --- .../Library/BaseCryptLib/BaseCryptLib.inf | 1 + .../Library/BaseCryptLib/PeiCryptLib.inf | 1 + .../BaseCryptLib/Pk/CryptTrustAnchor.c | 752 ++++++++++++++++++ .../BaseCryptLib/Pk/CryptTrustAnchorNull.c | 45 ++ .../Library/BaseCryptLib/RuntimeCryptLib.inf | 1 + .../Library/BaseCryptLib/SecCryptLib.inf | 1 + .../Library/BaseCryptLib/SmmCryptLib.inf | 1 + .../Library/BaseCryptLib/TestBaseCryptLib.inf | 1 + .../BaseCryptLib/UnitTestHostBaseCryptLib.inf | 1 + 9 files changed, 804 insertions(+) create mode 100644 MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c create mode 100644 MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf index 44f429330..ee6b7ffdc 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -44,6 +44,7 @@ Pk/CryptX509.c Pk/CryptAuthenticode.c Pk/CryptAuthenticodeHash.c # MU_CHANGE + Pk/CryptTrustAnchor.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf index 489ac9c92..c0076ec49 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -57,6 +57,7 @@ Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c new file mode 100644 index 000000000..f743e98d3 --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchor.c @@ -0,0 +1,752 @@ +/** @file + GetTrustAnchorX509FromAuthData() and the matching cache lifecycle. + + Walks a PKCS#7 SignedData blob, hashes each embedded X.509 certificate's + TBSCertificate, and returns the certificate whose digest matches a + caller-supplied hash. Optional cache memoizes (cert -> TBS-hash[s]) + across calls so callers that ask repeatedly do not re-hash the same + certificates. + + Caution: AuthData is treated as untrusted input. Each ASN.1 length is + bounds-checked against the remaining input before the parser advances. + + Reference: + RFC 2315 / RFC 5652 (PKCS#7 / CMS SignedData) + RFC 5280 (X.509 Certificate) + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +// +// ASN.1 DER constants used during the walk. +// +#define ASN1_TAG_SEQUENCE 0x30 +#define ASN1_TAG_SET 0x31 +#define ASN1_TAG_OID 0x06 +#define ASN1_TAG_CTX_CONS_0 0xA0 // [0] EXPLICIT / IMPLICIT constructed + +// +// signedData OID 1.2.840.113549.1.7.2 = 06 09 2A 86 48 86 F7 0D 01 07 02 +// +STATIC CONST UINT8 mPkcs7SignedDataOid[] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 +}; + +// +// Hash algorithm dispatch table; selected by digest length. The one-shot +// ShaXxxHashAll() entry points handle context allocation, init, update, +// and finalization internally, so the table only needs the digest size +// and the function pointer. +// +typedef +BOOLEAN +(EFIAPI *AUTH_HASH_ALL)( + IN CONST VOID *Data, + IN UINTN DataSize, + OUT UINT8 *HashValue + ); + +typedef struct { + UINTN DigestSize; + AUTH_HASH_ALL HashAll; +} TRUST_ANCHOR_HASH_INFO; + +STATIC CONST TRUST_ANCHOR_HASH_INFO mHashTable[] = { + { SHA1_DIGEST_SIZE, Sha1HashAll }, + { SHA256_DIGEST_SIZE, Sha256HashAll }, + { SHA384_DIGEST_SIZE, Sha384HashAll }, + { SHA512_DIGEST_SIZE, Sha512HashAll }, +}; + +#define HASH_TABLE_COUNT (sizeof (mHashTable) / sizeof (mHashTable[0])) + +// +// Cache structures. The cache stores up to TRUST_ANCHOR_CACHE_MAX entries; +// each entry is a copy of the certificate DER bytes plus a per-algorithm +// TBSCertificate digest computed lazily on first use. The digests are +// inlined (not heap-allocated) — at four 64-byte slots per entry the +// cost is bounded and the cache lifecycle has no inner allocations to +// fail or leak. +// +#define TRUST_ANCHOR_CACHE_SIGNATURE SIGNATURE_32 ('T', 'A', 'X', 'C') +#define TRUST_ANCHOR_CACHE_MAX 64 + +typedef struct { + UINT8 *CertDer; // Allocated cert DER copy. + UINTN CertDerSize; + BOOLEAN TbsHashValid[HASH_TABLE_COUNT]; + UINT8 TbsHash[HASH_TABLE_COUNT][SHA512_DIGEST_SIZE]; +} TRUST_ANCHOR_CACHE_ENTRY; + +typedef struct { + UINT32 Signature; + UINTN Count; + TRUST_ANCHOR_CACHE_ENTRY Entries[TRUST_ANCHOR_CACHE_MAX]; +} TRUST_ANCHOR_CACHE; + +/** + Map a digest size to an index in mHashTable. + + @param[in] HashSize Digest length in bytes. + + @retval Index into mHashTable on match. + @retval (UINTN)-1 if the size is not supported. +**/ +STATIC +UINTN +HashSizeToIndex ( + IN UINTN HashSize + ) +{ + UINTN Index; + + for (Index = 0; Index < HASH_TABLE_COUNT; Index++) { + if (mHashTable[Index].DigestSize == HashSize) { + return Index; + } + } + + return (UINTN)-1; +} + +/** + Decode an ASN.1 DER length field starting at *Cursor. On success, the + decoded length is written to *Length and *Cursor is advanced past the + length octets. + + Only the definite form is accepted. Lengths > the remaining input are + rejected. + + @param[in,out] Cursor Pointer to the cursor pointer; advanced on + success. + @param[in] End One past the last valid input byte. + @param[out] Length Receives the decoded content length. + + @retval EFI_SUCCESS Length parsed. + @retval EFI_INVALID_PARAMETER Malformed encoding or out of bounds. +**/ +STATIC +EFI_STATUS +Asn1DecodeLength ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + OUT UINTN *Length + ) +{ + CONST UINT8 *P; + UINTN Result; + UINTN NumOctets; + UINTN Index; + + P = *Cursor; + if (P >= End) { + return EFI_INVALID_PARAMETER; + } + + if ((*P & 0x80) == 0) { + Result = (UINTN)*P; + P++; + } else { + NumOctets = (UINTN)(*P & 0x7F); + P++; + // + // Reject indefinite (0x80) and lengths longer than UINTN. + // + if ((NumOctets == 0) || (NumOctets > sizeof (UINTN))) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)(End - P) < NumOctets) { + return EFI_INVALID_PARAMETER; + } + + Result = 0; + for (Index = 0; Index < NumOctets; Index++) { + Result = (Result << 8) | P[Index]; + } + + P += NumOctets; + } + + if ((UINTN)(End - P) < Result) { + return EFI_INVALID_PARAMETER; + } + + *Cursor = P; + *Length = Result; + return EFI_SUCCESS; +} + +/** + Parse an ASN.1 DER TLV at *Cursor and require Tag. On success, *Body + points to the value bytes, *BodyLen is the value length, and *Cursor + is advanced past the entire TLV. + + @param[in,out] Cursor Cursor pointer. + @param[in] End One past the last valid input byte. + @param[in] Tag Required tag byte. + @param[out] Body Receives a pointer to the value bytes. + @param[out] BodyLen Receives the value length. + + @retval EFI_SUCCESS TLV parsed. + @retval EFI_INVALID_PARAMETER Wrong tag or malformed encoding. +**/ +STATIC +EFI_STATUS +Asn1ExpectTagged ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + IN UINT8 Tag, + OUT CONST UINT8 **Body, + OUT UINTN *BodyLen + ) +{ + CONST UINT8 *P; + EFI_STATUS Status; + UINTN Length; + + P = *Cursor; + if ((P >= End) || (*P != Tag)) { + return EFI_INVALID_PARAMETER; + } + + P++; + Status = Asn1DecodeLength (&P, End, &Length); + if (EFI_ERROR (Status)) { + return Status; + } + + *Body = P; + *BodyLen = Length; + *Cursor = P + Length; + return EFI_SUCCESS; +} + +/** + Compute the TBSCertificate digest of an X.509 certificate. + + @param[in] CertDer Pointer to the DER-encoded Certificate. + @param[in] CertDerSize Size of the certificate in bytes. + @param[in] HashIndex Algorithm index in mHashTable. + @param[out] Digest Caller-allocated buffer for the digest. Must + be at least mHashTable[HashIndex].DigestSize + bytes. + + @retval EFI_SUCCESS Digest computed. + @retval EFI_INVALID_PARAMETER CertDer is not a well-formed Certificate. + @retval EFI_OUT_OF_RESOURCES Could not allocate hash context. + @retval EFI_DEVICE_ERROR Hash primitive failed. +**/ +STATIC +EFI_STATUS +HashTbsCertificate ( + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize, + IN UINTN HashIndex, + OUT UINT8 *Digest + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *CertBody; + UINTN CertBodyLen; + CONST UINT8 *TbsStart; + CONST UINT8 *TbsBody; + UINTN TbsBodyLen; + EFI_STATUS Status; + + Cursor = CertDer; + End = CertDer + CertDerSize; + + // + // Certificate ::= SEQUENCE + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &CertBody, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // First element of Certificate is TBSCertificate ::= SEQUENCE. + // Hash that whole TLV (tag + length + value) per RFC 5280 4.1.2. + // + TbsStart = CertBody; + Cursor = CertBody; + Status = Asn1ExpectTagged (&Cursor, CertBody + CertBodyLen, ASN1_TAG_SEQUENCE, &TbsBody, &TbsBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + if (!mHashTable[HashIndex].HashAll (TbsStart, (UINTN)(Cursor - TbsStart), Digest)) { + return EFI_DEVICE_ERROR; + } + + return EFI_SUCCESS; +} + +/** + Look up a certificate in the cache by DER bytes. If found, return the + entry; otherwise append a new entry with a copy of the cert bytes if + the cache has room. + + @param[in] Cache Cache handle. + @param[in] CertDer Pointer to certificate DER bytes. + @param[in] CertDerSize Size of certificate in bytes. + + @retval Pointer to the matching or newly inserted entry on success. + @retval NULL when the cache is full or memory allocation failed. +**/ +STATIC +TRUST_ANCHOR_CACHE_ENTRY * +CacheLookupOrInsert ( + IN TRUST_ANCHOR_CACHE *Cache, + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize + ) +{ + UINTN Index; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + + for (Index = 0; Index < Cache->Count; Index++) { + Entry = &Cache->Entries[Index]; + if ((Entry->CertDerSize == CertDerSize) && + (CompareMem (Entry->CertDer, CertDer, CertDerSize) == 0)) + { + return Entry; + } + } + + if (Cache->Count >= TRUST_ANCHOR_CACHE_MAX) { + return NULL; + } + + Entry = &Cache->Entries[Cache->Count]; + Entry->CertDer = AllocateCopyPool (CertDerSize, CertDer); + if (Entry->CertDer == NULL) { + return NULL; + } + + Entry->CertDerSize = CertDerSize; + ZeroMem (Entry->TbsHashValid, sizeof (Entry->TbsHashValid)); + Cache->Count++; + return Entry; +} + +/** + Compare a certificate's TBS digest at HashIndex against the supplied + TbsCertHash, using the cache when available to avoid re-hashing. + + @param[in] Cache Cache or NULL. + @param[in] CertDer Pointer to certificate DER bytes. + @param[in] CertDerSize Size of certificate in bytes. + @param[in] HashIndex Algorithm index in mHashTable. + @param[in] TbsCertHash Target digest bytes. + @param[out] IsMatch TRUE on match; FALSE otherwise. + + @retval EFI_SUCCESS IsMatch was set. + @retval other A hashing or allocation error occurred. +**/ +STATIC +EFI_STATUS +CertMatchesTbsHash ( + IN TRUST_ANCHOR_CACHE *Cache OPTIONAL, + IN CONST UINT8 *CertDer, + IN UINTN CertDerSize, + IN UINTN HashIndex, + IN CONST UINT8 *TbsCertHash, + OUT BOOLEAN *IsMatch + ) +{ + EFI_STATUS Status; + UINTN DigestSize; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + UINT8 LocalDigest[SHA512_DIGEST_SIZE]; + UINT8 *DigestBuf; + + *IsMatch = FALSE; + DigestSize = mHashTable[HashIndex].DigestSize; + Entry = (Cache != NULL) ? CacheLookupOrInsert (Cache, CertDer, CertDerSize) : NULL; + + // + // Hash directly into the cache slot when available; fall back to a + // stack buffer when the cache is full or absent. + // + if ((Entry != NULL) && Entry->TbsHashValid[HashIndex]) { + DigestBuf = Entry->TbsHash[HashIndex]; + } else { + DigestBuf = (Entry != NULL) ? Entry->TbsHash[HashIndex] : LocalDigest; + Status = HashTbsCertificate (CertDer, CertDerSize, HashIndex, DigestBuf); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Entry != NULL) { + Entry->TbsHashValid[HashIndex] = TRUE; + } + } + + if (CompareMem (DigestBuf, TbsCertHash, DigestSize) == 0) { + *IsMatch = TRUE; + } + + return EFI_SUCCESS; +} + +/** + Walk a SignedData certificates field and search for a certificate + whose TBSCertificate digest equals TbsCertHash. + + @param[in] CertSetBody Pointer to the contents of the [0] + IMPLICIT certificates field. + @param[in] CertSetBodyLen Length of CertSetBody. + @param[in] HashIndex Algorithm index. + @param[in] TbsCertHash Target digest bytes. + @param[in] Cache Optional cache. + @param[out] MatchCert On success, points within CertSetBody to + the matching certificate's first byte. + @param[out] MatchCertSize On success, size of the matching cert. + + @retval EFI_SUCCESS Match found. + @retval EFI_NOT_FOUND No certificate matched. + @retval other Parse or hashing error. +**/ +STATIC +EFI_STATUS +SearchCertificateSet ( + IN CONST UINT8 *CertSetBody, + IN UINTN CertSetBodyLen, + IN UINTN HashIndex, + IN CONST UINT8 *TbsCertHash, + IN TRUST_ANCHOR_CACHE *Cache OPTIONAL, + OUT CONST UINT8 **MatchCert, + OUT UINTN *MatchCertSize + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *CertStart; + CONST UINT8 *CertBody; + UINTN CertBodyLen; + EFI_STATUS Status; + BOOLEAN Match; + + Cursor = CertSetBody; + End = CertSetBody + CertSetBodyLen; + + while (Cursor < End) { + if (*Cursor != ASN1_TAG_SEQUENCE) { + // + // The certificates field may also contain extendedCertificate or + // other choices in older PKCS#7. Skip non-SEQUENCE TLVs. + // + CertStart = Cursor + 1; + Status = Asn1DecodeLength (&CertStart, End, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = CertStart + CertBodyLen; + continue; + } + + CertStart = Cursor; + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &CertBody, &CertBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = CertMatchesTbsHash ( + Cache, + CertStart, + (UINTN)(Cursor - CertStart), + HashIndex, + TbsCertHash, + &Match + ); + if (EFI_ERROR (Status)) { + return Status; + } + + if (Match) { + *MatchCert = CertStart; + *MatchCertSize = (UINTN)(Cursor - CertStart); + return EFI_SUCCESS; + } + } + + return EFI_NOT_FOUND; +} + +/** + Parse a PKCS#7 SignedData blob and return the contents of the + certificates [0] IMPLICIT field. + + Accepts both the bare SignedData encoding and a ContentInfo wrapper. + + @param[in] AuthData Pointer to PKCS#7 / SignedData bytes. + @param[in] AuthDataSize Size in bytes. + @param[out] CertSetBody Receives a pointer into AuthData where the + certificates contents start. + @param[out] CertSetBodyLen Receives the length of the certificates + contents. + + @retval EFI_SUCCESS Parsed and certificates field located. + @retval EFI_INVALID_PARAMETER Malformed input. + @retval EFI_NOT_FOUND The optional certificates field is + absent. +**/ +STATIC +EFI_STATUS +LocateCertificatesField ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT CONST UINT8 **CertSetBody, + OUT UINTN *CertSetBodyLen + ) +{ + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *Body; + UINTN BodyLen; + CONST UINT8 *OidBody; + UINTN OidLen; + EFI_STATUS Status; + + Cursor = AuthData; + End = AuthData + AuthDataSize; + + // + // Outer SEQUENCE. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // Detect ContentInfo: optional contentType OID followed by [0] EXPLICIT + // SignedData. If we see an OID first, peel the wrapper. + // + if ((Cursor < End) && (*Cursor == ASN1_TAG_OID)) { + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return Status; + } + + if ((OidLen != sizeof (mPkcs7SignedDataOid)) || + (CompareMem (OidBody, mPkcs7SignedDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // [0] EXPLICIT SignedData wrapper. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // Inner SignedData SEQUENCE. + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Cursor = Body; + End = Body + BodyLen; + } + + // + // Now Cursor..End contains the SignedData contents. + // version INTEGER + // digestAlgorithms SET + // encapContentInfo SEQUENCE + // certificates [0] IMPLICIT (optional) <-- the field we want + // crls [1] IMPLICIT (optional) + // signerInfos SET + // + // Skip version + digestAlgorithms + encapContentInfo by parsing each + // TLV and discarding its body. + // + // version (INTEGER, tag 0x02) + // + if (Cursor >= End) { + return EFI_INVALID_PARAMETER; + } + + Status = Asn1ExpectTagged (&Cursor, End, *Cursor, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // digestAlgorithms (SET) + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SET, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // encapContentInfo (SEQUENCE) + // + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + // + // Optional certificates [0] IMPLICIT. + // + if ((Cursor < End) && (*Cursor == ASN1_TAG_CTX_CONS_0)) { + Status = Asn1ExpectTagged (&Cursor, End, ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + *CertSetBody = Body; + *CertSetBodyLen = BodyLen; + return EFI_SUCCESS; + } + + return EFI_NOT_FOUND; +} + +/** + Locate, in a PKCS#7 SignedData blob, the X.509 certificate whose + TBSCertificate digest matches a caller-supplied hash, and return + that certificate as a newly allocated DER-encoded buffer. + + See BaseCryptLib.h for the full contract. +**/ +EFI_STATUS +EFIAPI +GetTrustAnchorX509FromAuthData ( + IN OUT VOID **CacheHandle OPTIONAL, + IN CONST UINT8 *TbsCertHash, + IN UINTN TbsCertHashSize, + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT UINT8 **TrustAnchorX509, + OUT UINTN *TrustAnchorX509Size + ) +{ + EFI_STATUS Status; + UINTN HashIndex; + CONST UINT8 *CertSetBody; + UINTN CertSetBodyLen; + CONST UINT8 *MatchCert; + UINTN MatchCertSize; + TRUST_ANCHOR_CACHE *Cache; + UINT8 *Output; + + if ((TbsCertHash == NULL) || (TbsCertHashSize == 0) || + (AuthData == NULL) || (AuthDataSize == 0) || + (TrustAnchorX509 == NULL) || (TrustAnchorX509Size == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashIndex = HashSizeToIndex (TbsCertHashSize); + if (HashIndex == (UINTN)-1) { + return EFI_INVALID_PARAMETER; + } + + // + // Optional cache. *CacheHandle == NULL means "allocate me one"; the + // caller releases it later via FreeTrustAnchorX509Cache(). + // + Cache = NULL; + if (CacheHandle != NULL) { + if (*CacheHandle == NULL) { + Cache = AllocateZeroPool (sizeof (TRUST_ANCHOR_CACHE)); + if (Cache == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Cache->Signature = TRUST_ANCHOR_CACHE_SIGNATURE; + *CacheHandle = Cache; + } else { + Cache = (TRUST_ANCHOR_CACHE *)*CacheHandle; + if (Cache->Signature != TRUST_ANCHOR_CACHE_SIGNATURE) { + return EFI_INVALID_PARAMETER; + } + } + } + + Status = LocateCertificatesField (AuthData, AuthDataSize, &CertSetBody, &CertSetBodyLen); + if (EFI_ERROR (Status)) { + return Status; + } + + Status = SearchCertificateSet ( + CertSetBody, + CertSetBodyLen, + HashIndex, + TbsCertHash, + Cache, + &MatchCert, + &MatchCertSize + ); + if (EFI_ERROR (Status)) { + return Status; + } + + Output = AllocateCopyPool (MatchCertSize, MatchCert); + if (Output == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + *TrustAnchorX509 = Output; + *TrustAnchorX509Size = MatchCertSize; + return EFI_SUCCESS; +} + +/** + Release a trust-anchor cache previously allocated by + GetTrustAnchorX509FromAuthData(). + + @param[in] CacheHandle Cache handle, or NULL. +**/ +VOID +EFIAPI +FreeTrustAnchorX509Cache ( + IN VOID *CacheHandle OPTIONAL + ) +{ + TRUST_ANCHOR_CACHE *Cache; + TRUST_ANCHOR_CACHE_ENTRY *Entry; + UINTN Index; + + if (CacheHandle == NULL) { + return; + } + + Cache = (TRUST_ANCHOR_CACHE *)CacheHandle; + if (Cache->Signature != TRUST_ANCHOR_CACHE_SIGNATURE) { + ASSERT (FALSE); + return; + } + + for (Index = 0; Index < Cache->Count; Index++) { + Entry = &Cache->Entries[Index]; + if (Entry->CertDer != NULL) { + FreePool (Entry->CertDer); + } + } + + ZeroMem (Cache, sizeof (TRUST_ANCHOR_CACHE)); + FreePool (Cache); +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c new file mode 100644 index 000000000..2b121479b --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptTrustAnchorNull.c @@ -0,0 +1,45 @@ +/** @file + GetTrustAnchorX509FromAuthData() / FreeTrustAnchorX509Cache() Null + implementation. + + Returns EFI_UNSUPPORTED to indicate this interface is not provided by + the current library instance (e.g., PEI / Runtime / SEC / SMM phases). + +Copyright (C) Microsoft Corporation. All rights reserved.
+SPDX-License-Identifier: BSD-2-Clause-Patent +**/ + +#include "InternalCryptLib.h" + +/** + Null stub for GetTrustAnchorX509FromAuthData(). + + @retval EFI_UNSUPPORTED This interface is not supported. +**/ +EFI_STATUS +EFIAPI +GetTrustAnchorX509FromAuthData ( + IN OUT VOID **CacheHandle OPTIONAL, + IN CONST UINT8 *TbsCertHash, + IN UINTN TbsCertHashSize, + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT UINT8 **TrustAnchorX509, + OUT UINTN *TrustAnchorX509Size + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} + +/** + Null stub for FreeTrustAnchorX509Cache(). No-op. +**/ +VOID +EFIAPI +FreeTrustAnchorX509Cache ( + IN VOID *CacheHandle OPTIONAL + ) +{ + ASSERT (FALSE); +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf index f061fe442..8f94318c2 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -50,6 +50,7 @@ Pk/CryptX509.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPssNull.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf index ba597416b..c11de30bb 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -52,6 +52,7 @@ Pk/CryptX509Null.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Rand/CryptRandNull.c SysCall/CrtWrapper.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf index 715b19725..3637dbba2 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -49,6 +49,7 @@ Pk/CryptX509.c Pk/CryptAuthenticodeNull.c Pk/CryptAuthenticodeHashNull.c # MU_CHANGE + Pk/CryptTrustAnchorNull.c # MU_CHANGE Pk/CryptTsNull.c Pk/CryptRsaPss.c Pk/CryptRsaPssSignNull.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf index a408b3305..54323547d 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf @@ -43,6 +43,7 @@ Pk/CryptX509.c Pk/CryptAuthenticode.c Pk/CryptAuthenticodeHash.c # MU_CHANGE + Pk/CryptTrustAnchor.c # MU_CHANGE Pk/CryptTs.c Pem/CryptPem.c Pk/CryptRsaPss.c diff --git a/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf index 21056ce13..30402b807 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -49,6 +49,7 @@ Pk/CryptX509.c Pk/CryptAuthenticode.c Pk/CryptAuthenticodeHash.c # MU_CHANGE + Pk/CryptTrustAnchor.c # MU_CHANGE Pk/CryptTs.c Pk/CryptRsaPss.c Pk/CryptRsaPssSign.c From 33da9f3feac3dc2cd43003770029887ddda99746 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Wed, 10 Jun 2026 14:54:18 -0700 Subject: [PATCH 6/9] OneCryptoPkg: Publish GetTrustAnchorX509FromAuthData in v1.1 protocol Wire the OneCrypto v1.1 GetTrustAnchorX509FromAuthData and FreeTrustAnchorX509Cache slots in the OneCryptoBin protocol producer. The slots dispatch to the BaseCryptLib implementation provided by either OpensslPkg or MbedTlsPkg depending on which binary is linked. Signed-off-by: Doug Flick --- OneCryptoPkg/OneCryptoBin/OneCryptoBin.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c index d01b349f2..e898745e3 100644 --- a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c +++ b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c @@ -338,7 +338,9 @@ CryptoInit ( // v1.1 functions // ======================================================================================================== - CryptoProtocol->GetAuthenticodeHash = GetAuthenticodeHash; + CryptoProtocol->GetAuthenticodeHash = GetAuthenticodeHash; + CryptoProtocol->GetTrustAnchorX509FromAuthData = GetTrustAnchorX509FromAuthData; + CryptoProtocol->FreeTrustAnchorX509Cache = FreeTrustAnchorX509Cache; } /** From 212b6adc07e6584506400ace528071bb90a7cc48 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Thu, 11 Jun 2026 09:47:35 -0700 Subject: [PATCH 7/9] OpensslPkg,MbedTlsPkg,OneCryptoPkg: Add GetAuthenticodeHashAlgorithm Add the GetAuthenticodeHashAlgorithm implementation and its Null variant to the OpensslPkg and MbedTlsPkg BaseCryptLib instances, and wire it into the OneCryptoBin protocol dispatch table. The byte-identical provider copies parse the Authenticode SpcIndirectDataContent with bounds-checked ASN.1 decoding to recover the digest-algorithm GUID from untrusted signature data, mapping the digestAlgorithm OID to the matching signature-type GUID. Signed-off-by: Doug Flick --- .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 392 ++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 24 ++ OneCryptoPkg/OneCryptoBin/OneCryptoBin.c | 1 + .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 392 ++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 24 ++ 5 files changed, 833 insertions(+) diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c index 0b0fbafa0..43c05daf7 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -445,3 +445,395 @@ GetAuthenticodeHash ( return Status; } + +// +// =========================================================================== +// Authenticode hash-algorithm discovery (SpcIndirectDataContent parsing) +// =========================================================================== +// +// The functions below walk the PKCS#7 SignedData ASN.1 structure to recover +// the digest algorithm recorded by the signer, without depending on any +// particular crypto provider. AuthData is untrusted, so every length field +// is decoded with bounds checking. +// + +// +// ASN.1 DER tag bytes used while walking the PKCS#7 SignedData structure. +// +#define AUTH_ASN1_TAG_INTEGER 0x02 +#define AUTH_ASN1_TAG_OID 0x06 +#define AUTH_ASN1_TAG_SEQUENCE 0x30 +#define AUTH_ASN1_TAG_SET 0x31 +#define AUTH_ASN1_TAG_CTX_CONS_0 0xA0 // [0] EXPLICIT, constructed + +// +// OID 1.2.840.113549.1.7.2 (PKCS#7 signedData) - DER value bytes. +// +STATIC CONST UINT8 mAuthPkcs7SignedDataOid[] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 +}; + +// +// OID 1.3.6.1.4.1.311.2.1.4 (SPC_INDIRECT_DATA_OBJID) - DER value bytes. +// +STATIC CONST UINT8 mAuthSpcIndirectDataOid[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04 +}; + +// +// Digest-algorithm OIDs (DER value bytes) recognized by Authenticode. +// +STATIC CONST UINT8 mAuthOidSha1[] = { + 0x2B, 0x0E, 0x03, 0x02, 0x1A // 1.3.14.3.2.26 +}; +STATIC CONST UINT8 mAuthOidSha256[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 // 2.16.840.1.101.3.4.2.1 +}; +STATIC CONST UINT8 mAuthOidSha384[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 // 2.16.840.1.101.3.4.2.2 +}; +STATIC CONST UINT8 mAuthOidSha512[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 // 2.16.840.1.101.3.4.2.3 +}; + +// +// Mapping of digest-algorithm OID to the signature-type GUID consumed by +// GetAuthenticodeHash(). +// +typedef struct { + CONST UINT8 *Oid; + UINTN OidSize; + CONST EFI_GUID *HashGuid; +} AUTH_DIGEST_OID_INFO; + +STATIC CONST AUTH_DIGEST_OID_INFO mAuthDigestOidInfo[] = { + { mAuthOidSha1, sizeof (mAuthOidSha1), &gEfiCertSha1Guid }, + { mAuthOidSha256, sizeof (mAuthOidSha256), &gEfiCertSha256Guid }, + { mAuthOidSha384, sizeof (mAuthOidSha384), &gEfiCertSha384Guid }, + { mAuthOidSha512, sizeof (mAuthOidSha512), &gEfiCertSha512Guid }, +}; + +#define AUTH_DIGEST_OID_INFO_COUNT (sizeof (mAuthDigestOidInfo) / sizeof (mAuthDigestOidInfo[0])) + +/** + Decode an ASN.1 DER length field starting at *Cursor. On success, the + decoded length is written to *Length and *Cursor is advanced past the + length octets. + + Only the definite form is accepted. Lengths > the remaining input are + rejected. + + @param[in,out] Cursor Pointer to the cursor pointer; advanced on + success. + @param[in] End One past the last valid input byte. + @param[out] Length Receives the decoded content length. + + @retval EFI_SUCCESS Length parsed. + @retval EFI_INVALID_PARAMETER Malformed encoding or out of bounds. +**/ +STATIC +EFI_STATUS +Asn1DecodeLength ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + OUT UINTN *Length + ) +{ + CONST UINT8 *P; + UINTN Result; + UINTN NumOctets; + UINTN Index; + + P = *Cursor; + if (P >= End) { + return EFI_INVALID_PARAMETER; + } + + if ((*P & 0x80) == 0) { + Result = (UINTN)*P; + P++; + } else { + NumOctets = (UINTN)(*P & 0x7F); + P++; + // + // Reject indefinite (0x80) and lengths longer than UINTN. + // + if ((NumOctets == 0) || (NumOctets > sizeof (UINTN))) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)(End - P) < NumOctets) { + return EFI_INVALID_PARAMETER; + } + + Result = 0; + for (Index = 0; Index < NumOctets; Index++) { + Result = (Result << 8) | P[Index]; + } + + P += NumOctets; + } + + if ((UINTN)(End - P) < Result) { + return EFI_INVALID_PARAMETER; + } + + *Cursor = P; + *Length = Result; + return EFI_SUCCESS; +} + +/** + Parse an ASN.1 DER TLV at *Cursor and require Tag. On success, *Body + points to the value bytes, *BodyLen is the value length, and *Cursor + is advanced past the entire TLV. + + @param[in,out] Cursor Cursor pointer. + @param[in] End One past the last valid input byte. + @param[in] Tag Required tag byte. + @param[out] Body Receives a pointer to the value bytes. + @param[out] BodyLen Receives the value length. + + @retval EFI_SUCCESS TLV parsed. + @retval EFI_INVALID_PARAMETER Wrong tag or malformed encoding. +**/ +STATIC +EFI_STATUS +Asn1ExpectTagged ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + IN UINT8 Tag, + OUT CONST UINT8 **Body, + OUT UINTN *BodyLen + ) +{ + CONST UINT8 *P; + EFI_STATUS Status; + UINTN Length; + + P = *Cursor; + if ((P >= End) || (*P != Tag)) { + return EFI_INVALID_PARAMETER; + } + + P++; + Status = Asn1DecodeLength (&P, End, &Length); + if (EFI_ERROR (Status)) { + return Status; + } + + *Body = P; + *BodyLen = Length; + *Cursor = P + Length; + return EFI_SUCCESS; +} + +/** + Determine the image-hash algorithm used by an Authenticode signature. + + Parses the PKCS#7 SignedData blob's SpcIndirectDataContent + (OID 1.3.6.1.4.1.311.2.1.4) and reads the digestAlgorithm of its + embedded messageDigest DigestInfo, mapping it to the corresponding + signature-type GUID. The recovered GUID can be passed directly to + GetAuthenticodeHash() as its HashType. + + Caution: AuthData is untrusted. The ASN.1 DER is parsed with + bounds-checked length decoding to avoid out-of-bounds reads. + + @param[in] AuthData Pointer to the PKCS#7 SignedData blob + (DER-encoded Authenticode signature). + @param[in] AuthDataSize Size of AuthData in bytes. + @param[out] HashType On success, receives the signature-type + GUID identifying the digest algorithm. + + @retval EFI_SUCCESS The hash algorithm was identified. + @retval EFI_INVALID_PARAMETER A required pointer is NULL, + AuthDataSize is 0, or AuthData is not a + well-formed Authenticode SignedData + blob. + @retval EFI_UNSUPPORTED The digest algorithm is not a + recognized image hash algorithm. +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHashAlgorithm ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT EFI_GUID *HashType + ) +{ + EFI_STATUS Status; + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *Body; + UINTN BodyLen; + CONST UINT8 *OidBody; + UINTN OidLen; + UINTN Index; + + if ((AuthData == NULL) || (AuthDataSize == 0) || (HashType == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = AuthData; + End = AuthData + AuthDataSize; + + // + // ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT } + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // contentType OID == pkcs7-signedData (1.2.840.113549.1.7.2). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + if ((OidLen != sizeof (mAuthPkcs7SignedDataOid)) || + (CompareMem (OidBody, mAuthPkcs7SignedDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // content [0] EXPLICIT -> SignedData. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // SignedData ::= SEQUENCE { version, digestAlgorithms, encapContentInfo, ... } + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // version INTEGER (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_INTEGER, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // digestAlgorithms SET (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SET, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // encapContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // contentType OID == SPC_INDIRECT_DATA (1.3.6.1.4.1.311.2.1.4). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + if ((OidLen != sizeof (mAuthSpcIndirectDataOid)) || + (CompareMem (OidBody, mAuthSpcIndirectDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // content [0] EXPLICIT -> SpcIndirectDataContent. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // SpcIndirectDataContent ::= SEQUENCE { data, messageDigest }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // data SpcAttributeTypeAndOptionalValue SEQUENCE (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // messageDigest DigestInfo ::= SEQUENCE { digestAlgorithm, digest }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // digestAlgorithm AlgorithmIdentifier ::= SEQUENCE { algorithm OID, ... }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // algorithm OID. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + for (Index = 0; Index < AUTH_DIGEST_OID_INFO_COUNT; Index++) { + if ((OidLen == mAuthDigestOidInfo[Index].OidSize) && + (CompareMem (OidBody, mAuthDigestOidInfo[Index].Oid, OidLen) == 0)) + { + CopyGuid (HashType, mAuthDigestOidInfo[Index].HashGuid); + return EFI_SUCCESS; + } + } + + return EFI_UNSUPPORTED; +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c index 5ea092e4c..85267c826 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -41,3 +41,27 @@ GetAuthenticodeHash ( ASSERT (FALSE); return EFI_UNSUPPORTED; } + +/** + Determine the image-hash algorithm used by an Authenticode signature. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] AuthData Pointer to the PKCS#7 SignedData blob. + @param[in] AuthDataSize Size of AuthData in bytes. + @param[out] HashType Receives the signature-type GUID. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHashAlgorithm ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT EFI_GUID *HashType + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} diff --git a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c index e898745e3..68e86e90c 100644 --- a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c +++ b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c @@ -341,6 +341,7 @@ CryptoInit ( CryptoProtocol->GetAuthenticodeHash = GetAuthenticodeHash; CryptoProtocol->GetTrustAnchorX509FromAuthData = GetTrustAnchorX509FromAuthData; CryptoProtocol->FreeTrustAnchorX509Cache = FreeTrustAnchorX509Cache; + CryptoProtocol->GetAuthenticodeHashAlgorithm = GetAuthenticodeHashAlgorithm; } /** diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c index 0b0fbafa0..43c05daf7 100644 --- a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -445,3 +445,395 @@ GetAuthenticodeHash ( return Status; } + +// +// =========================================================================== +// Authenticode hash-algorithm discovery (SpcIndirectDataContent parsing) +// =========================================================================== +// +// The functions below walk the PKCS#7 SignedData ASN.1 structure to recover +// the digest algorithm recorded by the signer, without depending on any +// particular crypto provider. AuthData is untrusted, so every length field +// is decoded with bounds checking. +// + +// +// ASN.1 DER tag bytes used while walking the PKCS#7 SignedData structure. +// +#define AUTH_ASN1_TAG_INTEGER 0x02 +#define AUTH_ASN1_TAG_OID 0x06 +#define AUTH_ASN1_TAG_SEQUENCE 0x30 +#define AUTH_ASN1_TAG_SET 0x31 +#define AUTH_ASN1_TAG_CTX_CONS_0 0xA0 // [0] EXPLICIT, constructed + +// +// OID 1.2.840.113549.1.7.2 (PKCS#7 signedData) - DER value bytes. +// +STATIC CONST UINT8 mAuthPkcs7SignedDataOid[] = { + 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x02 +}; + +// +// OID 1.3.6.1.4.1.311.2.1.4 (SPC_INDIRECT_DATA_OBJID) - DER value bytes. +// +STATIC CONST UINT8 mAuthSpcIndirectDataOid[] = { + 0x2B, 0x06, 0x01, 0x04, 0x01, 0x82, 0x37, 0x02, 0x01, 0x04 +}; + +// +// Digest-algorithm OIDs (DER value bytes) recognized by Authenticode. +// +STATIC CONST UINT8 mAuthOidSha1[] = { + 0x2B, 0x0E, 0x03, 0x02, 0x1A // 1.3.14.3.2.26 +}; +STATIC CONST UINT8 mAuthOidSha256[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01 // 2.16.840.1.101.3.4.2.1 +}; +STATIC CONST UINT8 mAuthOidSha384[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02 // 2.16.840.1.101.3.4.2.2 +}; +STATIC CONST UINT8 mAuthOidSha512[] = { + 0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03 // 2.16.840.1.101.3.4.2.3 +}; + +// +// Mapping of digest-algorithm OID to the signature-type GUID consumed by +// GetAuthenticodeHash(). +// +typedef struct { + CONST UINT8 *Oid; + UINTN OidSize; + CONST EFI_GUID *HashGuid; +} AUTH_DIGEST_OID_INFO; + +STATIC CONST AUTH_DIGEST_OID_INFO mAuthDigestOidInfo[] = { + { mAuthOidSha1, sizeof (mAuthOidSha1), &gEfiCertSha1Guid }, + { mAuthOidSha256, sizeof (mAuthOidSha256), &gEfiCertSha256Guid }, + { mAuthOidSha384, sizeof (mAuthOidSha384), &gEfiCertSha384Guid }, + { mAuthOidSha512, sizeof (mAuthOidSha512), &gEfiCertSha512Guid }, +}; + +#define AUTH_DIGEST_OID_INFO_COUNT (sizeof (mAuthDigestOidInfo) / sizeof (mAuthDigestOidInfo[0])) + +/** + Decode an ASN.1 DER length field starting at *Cursor. On success, the + decoded length is written to *Length and *Cursor is advanced past the + length octets. + + Only the definite form is accepted. Lengths > the remaining input are + rejected. + + @param[in,out] Cursor Pointer to the cursor pointer; advanced on + success. + @param[in] End One past the last valid input byte. + @param[out] Length Receives the decoded content length. + + @retval EFI_SUCCESS Length parsed. + @retval EFI_INVALID_PARAMETER Malformed encoding or out of bounds. +**/ +STATIC +EFI_STATUS +Asn1DecodeLength ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + OUT UINTN *Length + ) +{ + CONST UINT8 *P; + UINTN Result; + UINTN NumOctets; + UINTN Index; + + P = *Cursor; + if (P >= End) { + return EFI_INVALID_PARAMETER; + } + + if ((*P & 0x80) == 0) { + Result = (UINTN)*P; + P++; + } else { + NumOctets = (UINTN)(*P & 0x7F); + P++; + // + // Reject indefinite (0x80) and lengths longer than UINTN. + // + if ((NumOctets == 0) || (NumOctets > sizeof (UINTN))) { + return EFI_INVALID_PARAMETER; + } + + if ((UINTN)(End - P) < NumOctets) { + return EFI_INVALID_PARAMETER; + } + + Result = 0; + for (Index = 0; Index < NumOctets; Index++) { + Result = (Result << 8) | P[Index]; + } + + P += NumOctets; + } + + if ((UINTN)(End - P) < Result) { + return EFI_INVALID_PARAMETER; + } + + *Cursor = P; + *Length = Result; + return EFI_SUCCESS; +} + +/** + Parse an ASN.1 DER TLV at *Cursor and require Tag. On success, *Body + points to the value bytes, *BodyLen is the value length, and *Cursor + is advanced past the entire TLV. + + @param[in,out] Cursor Cursor pointer. + @param[in] End One past the last valid input byte. + @param[in] Tag Required tag byte. + @param[out] Body Receives a pointer to the value bytes. + @param[out] BodyLen Receives the value length. + + @retval EFI_SUCCESS TLV parsed. + @retval EFI_INVALID_PARAMETER Wrong tag or malformed encoding. +**/ +STATIC +EFI_STATUS +Asn1ExpectTagged ( + IN OUT CONST UINT8 **Cursor, + IN CONST UINT8 *End, + IN UINT8 Tag, + OUT CONST UINT8 **Body, + OUT UINTN *BodyLen + ) +{ + CONST UINT8 *P; + EFI_STATUS Status; + UINTN Length; + + P = *Cursor; + if ((P >= End) || (*P != Tag)) { + return EFI_INVALID_PARAMETER; + } + + P++; + Status = Asn1DecodeLength (&P, End, &Length); + if (EFI_ERROR (Status)) { + return Status; + } + + *Body = P; + *BodyLen = Length; + *Cursor = P + Length; + return EFI_SUCCESS; +} + +/** + Determine the image-hash algorithm used by an Authenticode signature. + + Parses the PKCS#7 SignedData blob's SpcIndirectDataContent + (OID 1.3.6.1.4.1.311.2.1.4) and reads the digestAlgorithm of its + embedded messageDigest DigestInfo, mapping it to the corresponding + signature-type GUID. The recovered GUID can be passed directly to + GetAuthenticodeHash() as its HashType. + + Caution: AuthData is untrusted. The ASN.1 DER is parsed with + bounds-checked length decoding to avoid out-of-bounds reads. + + @param[in] AuthData Pointer to the PKCS#7 SignedData blob + (DER-encoded Authenticode signature). + @param[in] AuthDataSize Size of AuthData in bytes. + @param[out] HashType On success, receives the signature-type + GUID identifying the digest algorithm. + + @retval EFI_SUCCESS The hash algorithm was identified. + @retval EFI_INVALID_PARAMETER A required pointer is NULL, + AuthDataSize is 0, or AuthData is not a + well-formed Authenticode SignedData + blob. + @retval EFI_UNSUPPORTED The digest algorithm is not a + recognized image hash algorithm. +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHashAlgorithm ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT EFI_GUID *HashType + ) +{ + EFI_STATUS Status; + CONST UINT8 *Cursor; + CONST UINT8 *End; + CONST UINT8 *Body; + UINTN BodyLen; + CONST UINT8 *OidBody; + UINTN OidLen; + UINTN Index; + + if ((AuthData == NULL) || (AuthDataSize == 0) || (HashType == NULL)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = AuthData; + End = AuthData + AuthDataSize; + + // + // ContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT } + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // contentType OID == pkcs7-signedData (1.2.840.113549.1.7.2). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + if ((OidLen != sizeof (mAuthPkcs7SignedDataOid)) || + (CompareMem (OidBody, mAuthPkcs7SignedDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // content [0] EXPLICIT -> SignedData. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // SignedData ::= SEQUENCE { version, digestAlgorithms, encapContentInfo, ... } + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // version INTEGER (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_INTEGER, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // digestAlgorithms SET (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SET, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // encapContentInfo ::= SEQUENCE { contentType OID, content [0] EXPLICIT }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // contentType OID == SPC_INDIRECT_DATA (1.3.6.1.4.1.311.2.1.4). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + if ((OidLen != sizeof (mAuthSpcIndirectDataOid)) || + (CompareMem (OidBody, mAuthSpcIndirectDataOid, OidLen) != 0)) + { + return EFI_INVALID_PARAMETER; + } + + // + // content [0] EXPLICIT -> SpcIndirectDataContent. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_CTX_CONS_0, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // SpcIndirectDataContent ::= SEQUENCE { data, messageDigest }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // data SpcAttributeTypeAndOptionalValue SEQUENCE (skip). + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + // + // messageDigest DigestInfo ::= SEQUENCE { digestAlgorithm, digest }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // digestAlgorithm AlgorithmIdentifier ::= SEQUENCE { algorithm OID, ... }. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_SEQUENCE, &Body, &BodyLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + Cursor = Body; + End = Body + BodyLen; + + // + // algorithm OID. + // + Status = Asn1ExpectTagged (&Cursor, End, AUTH_ASN1_TAG_OID, &OidBody, &OidLen); + if (EFI_ERROR (Status)) { + return EFI_INVALID_PARAMETER; + } + + for (Index = 0; Index < AUTH_DIGEST_OID_INFO_COUNT; Index++) { + if ((OidLen == mAuthDigestOidInfo[Index].OidSize) && + (CompareMem (OidBody, mAuthDigestOidInfo[Index].Oid, OidLen) == 0)) + { + CopyGuid (HashType, mAuthDigestOidInfo[Index].HashGuid); + return EFI_SUCCESS; + } + } + + return EFI_UNSUPPORTED; +} diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c index 5ea092e4c..85267c826 100644 --- a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -41,3 +41,27 @@ GetAuthenticodeHash ( ASSERT (FALSE); return EFI_UNSUPPORTED; } + +/** + Determine the image-hash algorithm used by an Authenticode signature. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] AuthData Pointer to the PKCS#7 SignedData blob. + @param[in] AuthDataSize Size of AuthData in bytes. + @param[out] HashType Receives the signature-type GUID. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +GetAuthenticodeHashAlgorithm ( + IN CONST UINT8 *AuthData, + IN UINTN AuthDataSize, + OUT EFI_GUID *HashType + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} From 3c18155b052e6c291d68064f1b84ef6d896204c8 Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Thu, 11 Jun 2026 10:30:24 -0700 Subject: [PATCH 8/9] OpensslPkg,MbedTlsPkg,OneCryptoPkg: Add X509GetTbsCertHash Implement X509GetTbsCertHash() for the OpenSSL and Mbed TLS BaseCryptLib instances. The implementation reuses the existing X509GetTBSCert() to extract the TBSCertificate byte range and the provider-independent hash dispatch table to digest it under the caller-selected algorithm. Add the matching BaseCryptLib null stub and wire the new service into the OneCrypto protocol binary. The OpenSSL and Mbed TLS sources remain byte-identical. Signed-off-by: Doug Flick --- .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 95 +++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 31 ++++++ OneCryptoPkg/OneCryptoBin/OneCryptoBin.c | 1 + .../BaseCryptLib/Pk/CryptAuthenticodeHash.c | 95 +++++++++++++++++++ .../Pk/CryptAuthenticodeHashNull.c | 31 ++++++ 5 files changed, 253 insertions(+) diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c index 43c05daf7..b45e0d765 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -837,3 +837,98 @@ GetAuthenticodeHashAlgorithm ( return EFI_UNSUPPORTED; } + +/** + Compute the digest of the TBSCertificate of an X.509 certificate. + + Extracts the TBSCertificate (the to-be-signed portion) of the given + DER-encoded certificate and hashes it with the algorithm selected by + HashType. The TBSCertificate is the exact byte range a certificate + authority signs, so its digest uniquely identifies the certificate + independent of the issuer signature. + + The caller selects the digest algorithm by HashType (e.g. + gEfiCertSha256Guid, gEfiCertSha384Guid). The digest is written to + Digest, which must be large enough to hold the largest supported + digest (at least SHA512_DIGEST_SIZE bytes). + + @param[in] Cert Pointer to the DER-encoded X.509 certificate. + @param[in] CertSize Size of Cert in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed TBSCertificate digest. Must be at + least SHA512_DIGEST_SIZE bytes. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_SUCCESS Digest was computed successfully. + @retval EFI_INVALID_PARAMETER A required pointer is NULL, CertSize is + 0, or Cert is not a well-formed X.509 + certificate. + @retval EFI_UNSUPPORTED HashType is not a recognized image + hash algorithm. + @retval EFI_OUT_OF_RESOURCES Could not allocate the hash context. + @retval EFI_DEVICE_ERROR A hash primitive failed. +**/ +EFI_STATUS +EFIAPI +X509GetTbsCertHash ( + IN VOID *Cert, + IN UINTN CertSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + EFI_STATUS Status; + CONST AUTH_HASH_INFO *HashInfo; + UINT8 *TbsCert; + UINTN TbsCertSize; + VOID *HashCtx; + UINTN CtxSize; + + if ((Cert == NULL) || (CertSize == 0) || (HashType == NULL) || + (Digest == NULL) || (DigestSize == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashInfo = LookupAuthHashInfo (HashType); + if (HashInfo == NULL) { + return EFI_UNSUPPORTED; + } + + // + // Extract the TBSCertificate byte range. X509GetTBSCert() returns a + // pointer into Cert (it does not allocate), so TbsCert must not be + // freed here. + // + TbsCert = NULL; + TbsCertSize = 0; + if (!X509GetTBSCert ((CONST UINT8 *)Cert, CertSize, &TbsCert, &TbsCertSize)) { + return EFI_INVALID_PARAMETER; + } + + // + // Allocate and initialize the hash context, then hash the + // TBSCertificate bytes. + // + CtxSize = HashInfo->GetContextSize (); + HashCtx = AllocatePool (CtxSize); + if (HashCtx == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_DEVICE_ERROR; + if (HashInfo->Init (HashCtx) && + HashInfo->Update (HashCtx, TbsCert, TbsCertSize) && + HashInfo->Final (HashCtx, Digest)) + { + *DigestSize = HashInfo->DigestSize; + Status = EFI_SUCCESS; + } + + FreePool (HashCtx); + return Status; +} diff --git a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c index 85267c826..f51b95e78 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -65,3 +65,34 @@ GetAuthenticodeHashAlgorithm ( ASSERT (FALSE); return EFI_UNSUPPORTED; } + +/** + Compute the digest of the TBSCertificate of an X.509 certificate. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] Cert Pointer to the DER-encoded X.509 certificate. + @param[in] CertSize Size of Cert in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +X509GetTbsCertHash ( + IN VOID *Cert, + IN UINTN CertSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} diff --git a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c index 68e86e90c..6549a64a3 100644 --- a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c +++ b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c @@ -342,6 +342,7 @@ CryptoInit ( CryptoProtocol->GetTrustAnchorX509FromAuthData = GetTrustAnchorX509FromAuthData; CryptoProtocol->FreeTrustAnchorX509Cache = FreeTrustAnchorX509Cache; CryptoProtocol->GetAuthenticodeHashAlgorithm = GetAuthenticodeHashAlgorithm; + CryptoProtocol->X509GetTbsCertHash = X509GetTbsCertHash; } /** diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c index 43c05daf7..b45e0d765 100644 --- a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -837,3 +837,98 @@ GetAuthenticodeHashAlgorithm ( return EFI_UNSUPPORTED; } + +/** + Compute the digest of the TBSCertificate of an X.509 certificate. + + Extracts the TBSCertificate (the to-be-signed portion) of the given + DER-encoded certificate and hashes it with the algorithm selected by + HashType. The TBSCertificate is the exact byte range a certificate + authority signs, so its digest uniquely identifies the certificate + independent of the issuer signature. + + The caller selects the digest algorithm by HashType (e.g. + gEfiCertSha256Guid, gEfiCertSha384Guid). The digest is written to + Digest, which must be large enough to hold the largest supported + digest (at least SHA512_DIGEST_SIZE bytes). + + @param[in] Cert Pointer to the DER-encoded X.509 certificate. + @param[in] CertSize Size of Cert in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed TBSCertificate digest. Must be at + least SHA512_DIGEST_SIZE bytes. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_SUCCESS Digest was computed successfully. + @retval EFI_INVALID_PARAMETER A required pointer is NULL, CertSize is + 0, or Cert is not a well-formed X.509 + certificate. + @retval EFI_UNSUPPORTED HashType is not a recognized image + hash algorithm. + @retval EFI_OUT_OF_RESOURCES Could not allocate the hash context. + @retval EFI_DEVICE_ERROR A hash primitive failed. +**/ +EFI_STATUS +EFIAPI +X509GetTbsCertHash ( + IN VOID *Cert, + IN UINTN CertSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + EFI_STATUS Status; + CONST AUTH_HASH_INFO *HashInfo; + UINT8 *TbsCert; + UINTN TbsCertSize; + VOID *HashCtx; + UINTN CtxSize; + + if ((Cert == NULL) || (CertSize == 0) || (HashType == NULL) || + (Digest == NULL) || (DigestSize == NULL)) + { + return EFI_INVALID_PARAMETER; + } + + HashInfo = LookupAuthHashInfo (HashType); + if (HashInfo == NULL) { + return EFI_UNSUPPORTED; + } + + // + // Extract the TBSCertificate byte range. X509GetTBSCert() returns a + // pointer into Cert (it does not allocate), so TbsCert must not be + // freed here. + // + TbsCert = NULL; + TbsCertSize = 0; + if (!X509GetTBSCert ((CONST UINT8 *)Cert, CertSize, &TbsCert, &TbsCertSize)) { + return EFI_INVALID_PARAMETER; + } + + // + // Allocate and initialize the hash context, then hash the + // TBSCertificate bytes. + // + CtxSize = HashInfo->GetContextSize (); + HashCtx = AllocatePool (CtxSize); + if (HashCtx == NULL) { + return EFI_OUT_OF_RESOURCES; + } + + Status = EFI_DEVICE_ERROR; + if (HashInfo->Init (HashCtx) && + HashInfo->Update (HashCtx, TbsCert, TbsCertSize) && + HashInfo->Final (HashCtx, Digest)) + { + *DigestSize = HashInfo->DigestSize; + Status = EFI_SUCCESS; + } + + FreePool (HashCtx); + return Status; +} diff --git a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c index 85267c826..f51b95e78 100644 --- a/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -65,3 +65,34 @@ GetAuthenticodeHashAlgorithm ( ASSERT (FALSE); return EFI_UNSUPPORTED; } + +/** + Compute the digest of the TBSCertificate of an X.509 certificate. + + Return EFI_UNSUPPORTED to indicate this interface is not supported. + + @param[in] Cert Pointer to the DER-encoded X.509 certificate. + @param[in] CertSize Size of Cert in bytes. + @param[in] HashType Signature-type GUID identifying the hash + algorithm to use. + @param[out] Digest Caller-provided buffer that receives the + computed digest. + @param[out] DigestSize On success, receives the digest length in + bytes. + + @retval EFI_UNSUPPORTED This interface is not supported. + +**/ +EFI_STATUS +EFIAPI +X509GetTbsCertHash ( + IN VOID *Cert, + IN UINTN CertSize, + IN CONST EFI_GUID *HashType, + OUT UINT8 *Digest, + OUT UINTN *DigestSize + ) +{ + ASSERT (FALSE); + return EFI_UNSUPPORTED; +} From c975dd20df96fb612ba3fd76e9cbb939ec9e93cf Mon Sep 17 00:00:00 2001 From: Doug Flick Date: Thu, 11 Jun 2026 11:27:28 -0700 Subject: [PATCH 9/9] Build: Temporarily pin MU_BASECORE to fork commit Point the MU_BASECORE GetDependencies entry in CISettings.py and OneCryptoPkg/DriverBuild.py at the flickdm/mu_basecore fork commit 709ab9b016, which carries the new BaseCryptLib and OneCrypto protocol APIs (GetAuthenticodeHash, GetTrustAnchorX509FromAuthData, FreeTrustAnchorX509Cache, GetAuthenticodeHashAlgorithm, X509GetTbsCertHash). The previously pinned microsoft/mu_basecore commit predates these members, so OneCryptoBin.c failed to build. This pin is temporary and must be reverted to https://github.com/microsoft/mu_basecore.git once the dependency changes merge. Signed-off-by: Doug Flick --- .pytool/CISettings.py | 9 +++++++-- OneCryptoPkg/DriverBuild.py | 9 +++++++-- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/.pytool/CISettings.py b/.pytool/CISettings.py index 269998ecd..003ce3083 100644 --- a/.pytool/CISettings.py +++ b/.pytool/CISettings.py @@ -167,8 +167,13 @@ def GetDependencies(self): return [ { "Path": "MU_BASECORE", - "Url": "https://github.com/microsoft/mu_basecore.git", - "Commit": "922377cb580e03c45628ddb4e0a0871ccf2f6f2d" + # MU_CHANGE [TEMP] - pin to fork commit carrying the new + # BaseCryptLib/OneCrypto APIs (GetAuthenticodeHash, + # GetTrustAnchorX509FromAuthData, FreeTrustAnchorX509Cache, + # GetAuthenticodeHashAlgorithm, X509GetTbsCertHash). Revert to + # https://github.com/microsoft/mu_basecore.git once merged. + "Url": "https://github.com/flickdm/mu_basecore.git", + "Commit": "709ab9b016ecf0555142ba5bc0da55227f34a7cd" }, { "Path": "Features/MM_SUPV", diff --git a/OneCryptoPkg/DriverBuild.py b/OneCryptoPkg/DriverBuild.py index 90a60102a..b2aec1592 100644 --- a/OneCryptoPkg/DriverBuild.py +++ b/OneCryptoPkg/DriverBuild.py @@ -65,8 +65,13 @@ def GetDependencies(): return [ { "Path": "MU_BASECORE", - "Url": "https://github.com/microsoft/mu_basecore.git", - "Commit": "922377cb580e03c45628ddb4e0a0871ccf2f6f2d" + # MU_CHANGE [TEMP] - pin to fork commit carrying the new + # BaseCryptLib/OneCrypto APIs (GetAuthenticodeHash, + # GetTrustAnchorX509FromAuthData, FreeTrustAnchorX509Cache, + # GetAuthenticodeHashAlgorithm, X509GetTbsCertHash). Revert to + # https://github.com/microsoft/mu_basecore.git once merged. + "Url": "https://github.com/flickdm/mu_basecore.git", + "Commit": "709ab9b016ecf0555142ba5bc0da55227f34a7cd" }, { "Path": "Features/MM_SUPV",