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/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf index 6161df737..ee6b7ffdc 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -43,6 +43,8 @@ Pk/CryptDhNull.c 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 @@ -78,6 +80,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..c0076ec49 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -56,6 +56,8 @@ Pk/CryptDhNull.c 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/CryptAuthenticodeHash.c b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c new file mode 100644 index 000000000..b45e0d765 --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -0,0 +1,934 @@ +/** @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; +} + +// +// =========================================================================== +// 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; +} + +/** + 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 new file mode 100644 index 000000000..f51b95e78 --- /dev/null +++ b/MbedTlsPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -0,0 +1,98 @@ +/** @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; +} + +/** + 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; +} + +/** + 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/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 f3bbefb6a..8f94318c2 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -49,6 +49,8 @@ Pk/CryptDhNull.c 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 40b31e03b..c11de30bb 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -51,6 +51,8 @@ Pk/CryptPkcs7VerifyEkuNull.c 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 a1948cec6..3637dbba2 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -48,6 +48,8 @@ Pk/CryptDhNull.c 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 b96088ec3..54323547d 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/TestBaseCryptLib.inf @@ -42,6 +42,8 @@ Pk/CryptDhNull.c 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 @@ -68,6 +70,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..30402b807 100644 --- a/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/MbedTlsPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -48,6 +48,8 @@ Pk/CryptDhNull.c 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 @@ -74,6 +76,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/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", diff --git a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c index 051672e4d..6549a64a3 100644 --- a/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c +++ b/OneCryptoPkg/OneCryptoBin/OneCryptoBin.c @@ -333,6 +333,16 @@ CryptoInit ( // ======================================================================================================== CryptoProtocol->GetCryptoProviderVersionString = GetCryptoProviderVersionString; + + // ======================================================================================================== + // v1.1 functions + // ======================================================================================================== + + CryptoProtocol->GetAuthenticodeHash = GetAuthenticodeHash; + CryptoProtocol->GetTrustAnchorX509FromAuthData = GetTrustAnchorX509FromAuthData; + CryptoProtocol->FreeTrustAnchorX509Cache = FreeTrustAnchorX509Cache; + CryptoProtocol->GetAuthenticodeHashAlgorithm = GetAuthenticodeHashAlgorithm; + CryptoProtocol->X509GetTbsCertHash = X509GetTbsCertHash; } /** diff --git a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf index 2f4e10383..6ffd829a4 100644 --- a/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/BaseCryptLib.inf @@ -58,6 +58,8 @@ Pk/CryptDh.c 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 @@ -108,6 +110,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..6a8acdc08 100644 --- a/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/PeiCryptLib.inf @@ -54,6 +54,8 @@ Pk/CryptDhNull.c 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/CryptAuthenticodeHash.c b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c new file mode 100644 index 000000000..b45e0d765 --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHash.c @@ -0,0 +1,934 @@ +/** @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; +} + +// +// =========================================================================== +// 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; +} + +/** + 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 new file mode 100644 index 000000000..f51b95e78 --- /dev/null +++ b/OpensslPkg/Library/BaseCryptLib/Pk/CryptAuthenticodeHashNull.c @@ -0,0 +1,98 @@ +/** @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; +} + +/** + 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; +} + +/** + 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/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 1bee26764..2a3fe1c9f 100644 --- a/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/RuntimeCryptLib.inf @@ -57,6 +57,8 @@ Pk/CryptDhNull.c 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 100686d6c..224d84b33 100644 --- a/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SecCryptLib.inf @@ -49,6 +49,8 @@ Pk/CryptDhNull.c 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 ae075b49d..08a17d24f 100644 --- a/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/SmmCryptLib.inf @@ -58,6 +58,8 @@ Pk/CryptDhNull.c 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 23bc87e92..7ccc75a0d 100644 --- a/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf +++ b/OpensslPkg/Library/BaseCryptLib/UnitTestHostBaseCryptLib.inf @@ -45,6 +45,8 @@ Pk/CryptDh.c 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 @@ -73,6 +75,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 #