mirror of
https://github.com/CloverHackyColor/CloverBootloader.git
synced 2024-12-04 13:23:26 +01:00
476 lines
16 KiB
C
476 lines
16 KiB
C
|
/** @file
|
|||
|
Implements APIs to verify the Authenticode Signature of PE/COFF Images.
|
|||
|
|
|||
|
Copyright (c) 2020 - 2021, Marvin Häuser. All rights reserved.<BR>
|
|||
|
Copyright (c) 2020, Vitaly Cheptsov. All rights reserved.<BR>
|
|||
|
Copyright (c) 2020, ISP RAS. All rights reserved.<BR>
|
|||
|
Portions copyright (c) 2006 - 2019, Intel Corporation. All rights reserved.<BR>
|
|||
|
Portions copyright (c) 2016, Hewlett Packard Enterprise Development LP. All rights reserved.<BR>
|
|||
|
|
|||
|
SPDX-License-Identifier: BSD-3-Clause
|
|||
|
**/
|
|||
|
|
|||
|
#include <Base.h>
|
|||
|
|
|||
|
#include <IndustryStandard/PeImage2.h>
|
|||
|
|
|||
|
#include <Guid/WinCertificate.h>
|
|||
|
|
|||
|
#include <Library/BaseOverflowLib.h>
|
|||
|
#include <Library/DebugLib.h>
|
|||
|
#include <Library/MemoryAllocationLib.h>
|
|||
|
#include <Library/PcdLib.h>
|
|||
|
#include <Library/PeCoffLib2.h>
|
|||
|
|
|||
|
#include "BasePeCoffLib2Internals.h"
|
|||
|
|
|||
|
/**
|
|||
|
Hashes the Image section data in ascending order of raw file appearance.
|
|||
|
|
|||
|
@param[in] Context The context describing the Image. Must have been
|
|||
|
initialised by PeCoffInitializeContext().
|
|||
|
@param[in] HashUpdate The data hashing function.
|
|||
|
@param[in,out] HashContext The context of the current hash.
|
|||
|
|
|||
|
@returns Whether hashing has been successful.
|
|||
|
**/
|
|||
|
STATIC
|
|||
|
BOOLEAN
|
|||
|
InternalHashSections (
|
|||
|
IN CONST PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|||
|
IN OUT VOID *HashContext,
|
|||
|
IN PE_COFF_LOADER_HASH_UPDATE HashUpdate,
|
|||
|
IN OUT UINT32 *SumBytesHashed
|
|||
|
)
|
|||
|
{
|
|||
|
BOOLEAN Result;
|
|||
|
BOOLEAN Overflow;
|
|||
|
|
|||
|
CONST EFI_IMAGE_SECTION_HEADER *Sections;
|
|||
|
CONST EFI_IMAGE_SECTION_HEADER **SortedSections;
|
|||
|
UINT16 SectionIndex;
|
|||
|
UINT16 SectionPos;
|
|||
|
UINT32 SectionTop;
|
|||
|
UINT32 CurHashSize;
|
|||
|
//
|
|||
|
// 9. Build a temporary table of pointers to all of the section headers in the
|
|||
|
// image. The NumberOfSections field of COFF File Header indicates how big
|
|||
|
// the table should be. Do not include any section headers in the table
|
|||
|
// whose SizeOfRawData field is zero.
|
|||
|
//
|
|||
|
SortedSections = AllocatePool (
|
|||
|
(UINT32) Context->NumberOfSections * sizeof (*SortedSections)
|
|||
|
);
|
|||
|
if (SortedSections == NULL) {
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
|
|||
|
Sections = (CONST EFI_IMAGE_SECTION_HEADER *) (CONST VOID *) (
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + Context->SectionsOffset
|
|||
|
);
|
|||
|
//
|
|||
|
// 10. Using the PointerToRawData field (offset 20) in the referenced
|
|||
|
// SectionHeader structure as a key, arrange the table's elements in
|
|||
|
// ascending order. In other words, sort the section headers in ascending
|
|||
|
// order according to the disk-file offset of the sections.
|
|||
|
//
|
|||
|
SortedSections[0] = Sections;
|
|||
|
//
|
|||
|
// Perform Insertion Sort to order the Sections by their raw file offset.
|
|||
|
//
|
|||
|
for (SectionIndex = 1; SectionIndex < Context->NumberOfSections; ++SectionIndex) {
|
|||
|
for (SectionPos = SectionIndex;
|
|||
|
0 < SectionPos
|
|||
|
&& SortedSections[SectionPos - 1]->PointerToRawData > Sections[SectionIndex].PointerToRawData;
|
|||
|
--SectionPos) {
|
|||
|
SortedSections[SectionPos] = SortedSections[SectionPos - 1];
|
|||
|
}
|
|||
|
|
|||
|
SortedSections[SectionPos] = Sections + SectionIndex;
|
|||
|
}
|
|||
|
|
|||
|
Result = TRUE;
|
|||
|
SectionTop = 0;
|
|||
|
CurHashSize = 0;
|
|||
|
//
|
|||
|
// 13. Repeat steps 11 and 12 for all of the sections in the sorted table.
|
|||
|
//
|
|||
|
for (SectionIndex = 0; SectionIndex < Context->NumberOfSections; ++SectionIndex) {
|
|||
|
//
|
|||
|
// Verify the Image section does not overlap with the previous one if the
|
|||
|
// policy demands it. Overlapping Sections could dramatically increase the
|
|||
|
// hashing time.
|
|||
|
// FIXME: Move to init, along with a trailing data policy.
|
|||
|
//
|
|||
|
if (PcdGetBool (PcdImageLoaderHashProhibitOverlap)) {
|
|||
|
if (SectionTop > SortedSections[SectionIndex]->PointerToRawData) {
|
|||
|
Result = FALSE;
|
|||
|
break;
|
|||
|
}
|
|||
|
|
|||
|
SectionTop = SortedSections[SectionIndex]->PointerToRawData + SortedSections[SectionIndex]->SizeOfRawData;
|
|||
|
}
|
|||
|
//
|
|||
|
// Skip Sections that contain no data.
|
|||
|
//
|
|||
|
if (SortedSections[SectionIndex]->SizeOfRawData > 0) {
|
|||
|
//
|
|||
|
// 11. Walk through the sorted table, load the corresponding section into
|
|||
|
// memory, and hash the entire section. Use the SizeOfRawData field in the
|
|||
|
// SectionHeader structure to determine the amount of data to hash.
|
|||
|
//
|
|||
|
Result = HashUpdate (
|
|||
|
HashContext,
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + SortedSections[SectionIndex]->PointerToRawData,
|
|||
|
SortedSections[SectionIndex]->SizeOfRawData
|
|||
|
);
|
|||
|
if (!Result) {
|
|||
|
break;
|
|||
|
}
|
|||
|
//
|
|||
|
// 12. Add the section’s SizeOfRawData value to SUM_OF_BYTES_HASHED.
|
|||
|
//
|
|||
|
// If and only if the Sections do not overlap, we know their sizes are at
|
|||
|
// most MAX_UINT32 in sum because the file size is at most MAX_UINT32.
|
|||
|
//
|
|||
|
if (PcdGetBool (PcdImageLoaderHashProhibitOverlap)) {
|
|||
|
CurHashSize += SortedSections[SectionIndex]->SizeOfRawData;
|
|||
|
} else {
|
|||
|
//
|
|||
|
// Verify the hash size does not overflow.
|
|||
|
//
|
|||
|
Overflow = BaseOverflowAddU32 (
|
|||
|
CurHashSize,
|
|||
|
SortedSections[SectionIndex]->SizeOfRawData,
|
|||
|
&CurHashSize
|
|||
|
);
|
|||
|
if (Overflow) {
|
|||
|
Result = FALSE;
|
|||
|
break;
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
*SumBytesHashed = CurHashSize;
|
|||
|
FreePool ((VOID *) SortedSections);
|
|||
|
|
|||
|
return Result;
|
|||
|
}
|
|||
|
|
|||
|
BOOLEAN
|
|||
|
PeCoffHashImageAuthenticode (
|
|||
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|||
|
IN OUT VOID *HashContext,
|
|||
|
IN PE_COFF_LOADER_HASH_UPDATE HashUpdate
|
|||
|
)
|
|||
|
{
|
|||
|
BOOLEAN Result;
|
|||
|
BOOLEAN Overflow;
|
|||
|
UINT32 NumberOfRvaAndSizes;
|
|||
|
UINT32 ChecksumOffset;
|
|||
|
UINT32 SecurityDirOffset;
|
|||
|
UINT32 SecurityDirSize;
|
|||
|
UINT32 CurrentOffset;
|
|||
|
UINT32 HashSize;
|
|||
|
CONST EFI_IMAGE_NT_HEADERS32 *Pe32;
|
|||
|
CONST EFI_IMAGE_NT_HEADERS64 *Pe32Plus;
|
|||
|
UINT32 SumBytesHashed;
|
|||
|
UINT32 FileSize;
|
|||
|
|
|||
|
//
|
|||
|
// These conditions must be met by the caller prior to calling this function.
|
|||
|
//
|
|||
|
// 1. Load the image header into memory.
|
|||
|
// 2. Initialize a hash algorithm context.
|
|||
|
//
|
|||
|
|
|||
|
//
|
|||
|
// This step can be moved here because steps 1 to 5 do not modify the Image.
|
|||
|
//
|
|||
|
// 6. Get the Attribute Certificate Table address and size from the
|
|||
|
// Certificate Table entry. For details, see section 5.7 of the PE/COFF
|
|||
|
// specification.
|
|||
|
//
|
|||
|
// Additionally, retrieve important offsets for later steps.
|
|||
|
//
|
|||
|
switch (Context->ImageType) {
|
|||
|
case PeCoffLoaderTypePe32:
|
|||
|
Pe32 = (CONST EFI_IMAGE_NT_HEADERS32 *) (CONST VOID *) (
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + Context->ExeHdrOffset
|
|||
|
);
|
|||
|
ChecksumOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS32, CheckSum);
|
|||
|
SecurityDirOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS32, DataDirectory) + (UINT32) (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY * sizeof (EFI_IMAGE_DATA_DIRECTORY));
|
|||
|
NumberOfRvaAndSizes = Pe32->NumberOfRvaAndSizes;
|
|||
|
//
|
|||
|
// Retrieve the Security Directory size depending on existence.
|
|||
|
//
|
|||
|
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
|
|||
|
SecurityDirSize = Pe32->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size;
|
|||
|
} else {
|
|||
|
SecurityDirSize = 0;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
case PeCoffLoaderTypePe32Plus:
|
|||
|
Pe32Plus = (CONST EFI_IMAGE_NT_HEADERS64 *) (CONST VOID *) (
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + Context->ExeHdrOffset
|
|||
|
);
|
|||
|
ChecksumOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS64, CheckSum);
|
|||
|
SecurityDirOffset = Context->ExeHdrOffset + (UINT32) OFFSET_OF (EFI_IMAGE_NT_HEADERS64, DataDirectory) + (UINT32) (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY * sizeof (EFI_IMAGE_DATA_DIRECTORY));
|
|||
|
NumberOfRvaAndSizes = Pe32Plus->NumberOfRvaAndSizes;
|
|||
|
//
|
|||
|
// Retrieve the Security Directory size depending on existence.
|
|||
|
//
|
|||
|
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
|
|||
|
SecurityDirSize = Pe32Plus->DataDirectory[EFI_IMAGE_DIRECTORY_ENTRY_SECURITY].Size;
|
|||
|
} else {
|
|||
|
SecurityDirSize = 0;
|
|||
|
}
|
|||
|
|
|||
|
break;
|
|||
|
|
|||
|
default:
|
|||
|
ASSERT (FALSE);
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// 3. Hash the image header from its base to immediately before the start of
|
|||
|
// the checksum address, as specified in Optional Header Windows-Specific
|
|||
|
// Fields.
|
|||
|
//
|
|||
|
Result = HashUpdate (HashContext, Context->FileBuffer, ChecksumOffset);
|
|||
|
if (!Result) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// 4. Skip over the checksum, which is a 4-byte field.
|
|||
|
//
|
|||
|
CurrentOffset = ChecksumOffset + sizeof (UINT32);
|
|||
|
//
|
|||
|
// 5. Hash everything from the end of the checksum field to immediately before
|
|||
|
// the start of the Certificate Table entry, as specified in Optional
|
|||
|
// Header Data Directories.
|
|||
|
//
|
|||
|
if (EFI_IMAGE_DIRECTORY_ENTRY_SECURITY < NumberOfRvaAndSizes) {
|
|||
|
HashSize = SecurityDirOffset - CurrentOffset;
|
|||
|
Result = HashUpdate (
|
|||
|
HashContext,
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + CurrentOffset,
|
|||
|
HashSize
|
|||
|
);
|
|||
|
if (!Result) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// Skip over the Security Directory.
|
|||
|
//
|
|||
|
CurrentOffset = SecurityDirOffset + sizeof (EFI_IMAGE_DATA_DIRECTORY);
|
|||
|
}
|
|||
|
//
|
|||
|
// 7. Exclude the Certificate Table entry from the calculation and hash
|
|||
|
// everything from the end of the Certificate Table entry to the end of
|
|||
|
// image header, including Section Table (headers).The Certificate Table
|
|||
|
// entry is 8 Bytes long, as specified in Optional Header Data Directories.
|
|||
|
//
|
|||
|
HashSize = Context->SizeOfHeaders - CurrentOffset;
|
|||
|
Result = HashUpdate (
|
|||
|
HashContext,
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + CurrentOffset,
|
|||
|
HashSize
|
|||
|
);
|
|||
|
if (!Result) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// Perform the Section-related steps of the algorithm.
|
|||
|
//
|
|||
|
Result = InternalHashSections (
|
|||
|
Context,
|
|||
|
HashContext,
|
|||
|
HashUpdate,
|
|||
|
&SumBytesHashed
|
|||
|
);
|
|||
|
if (!Result) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// 8. Create a counter called SUM_OF_BYTES_HASHED, which is not part of the
|
|||
|
// signature. Set this counter to the SizeOfHeaders field, as specified in
|
|||
|
// Optional Header Windows-Specific Field.
|
|||
|
//
|
|||
|
Overflow = BaseOverflowAddU32 (
|
|||
|
SumBytesHashed,
|
|||
|
Context->SizeOfHeaders,
|
|||
|
&SumBytesHashed
|
|||
|
);
|
|||
|
if (Overflow) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return FALSE;
|
|||
|
}
|
|||
|
//
|
|||
|
// 14. Create a value called FILE_SIZE, which is not part of the signature.
|
|||
|
// Set this value to the image’s file size, acquired from the underlying
|
|||
|
// file system. If FILE_SIZE is greater than SUM_OF_BYTES_HASHED, the file
|
|||
|
// contains extra data that must be added to the hash. This data begins at
|
|||
|
// the SUM_OF_BYTES_HASHED file offset, and its length is:
|
|||
|
// (File Size) - ((Size of AttributeCertificateTable) + SUM_OF_BYTES_HASHED)
|
|||
|
//
|
|||
|
// Note: The size of Attribute Certificate Table is specified in the
|
|||
|
// second ULONG value in the Certificate Table entry (32 bit: offset 132,
|
|||
|
// 64 bit: offset 148) in Optional Header Data Directories.
|
|||
|
//
|
|||
|
FileSize = Context->FileSize - SecurityDirSize;
|
|||
|
if (SumBytesHashed < FileSize) {
|
|||
|
Result = HashUpdate (
|
|||
|
HashContext,
|
|||
|
(CONST CHAR8 *) Context->FileBuffer + SumBytesHashed,
|
|||
|
FileSize - SumBytesHashed
|
|||
|
);
|
|||
|
}
|
|||
|
//
|
|||
|
// This step must be performed by the caller after this function succeeded.
|
|||
|
//
|
|||
|
// 15. Finalize the hash algorithm context.
|
|||
|
//
|
|||
|
return Result;
|
|||
|
}
|
|||
|
|
|||
|
RETURN_STATUS
|
|||
|
PeCoffGetFirstCertificate (
|
|||
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|||
|
OUT CONST WIN_CERTIFICATE **Certificate
|
|||
|
)
|
|||
|
{
|
|||
|
CONST WIN_CERTIFICATE *WinCertificate;
|
|||
|
//
|
|||
|
// These conditions are verified by PeCoffInitializeContext().
|
|||
|
//
|
|||
|
ASSERT (Context->SecDirOffset % ALIGNOF (WIN_CERTIFICATE));
|
|||
|
ASSERT (Context->SecDirSize == 0 || sizeof (WIN_CERTIFICATE) <= Context->SecDirSize);
|
|||
|
//
|
|||
|
// Verify the Security Directory is not empty.
|
|||
|
//
|
|||
|
if (Context->SecDirSize == 0) {
|
|||
|
return RETURN_NOT_FOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the certificate size is well-formed and that it is in bounds of the
|
|||
|
// Security Directory.
|
|||
|
//
|
|||
|
WinCertificate = (CONST WIN_CERTIFICATE *) (CONST VOID *) (
|
|||
|
(CONST UINT8 *) Context->FileBuffer + Context->SecDirOffset
|
|||
|
);
|
|||
|
if (WinCertificate->dwLength < sizeof (WIN_CERTIFICATE)
|
|||
|
|| WinCertificate->dwLength > Context->SecDirSize) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the certificate size is sufficiently aligned, if the policy demands
|
|||
|
// it. This has been observed to not be the case with images signed with
|
|||
|
// pesign, such as GRUB.
|
|||
|
//
|
|||
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
|
|||
|
if (!IS_ALIGNED (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN)) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
}
|
|||
|
|
|||
|
*Certificate = WinCertificate;
|
|||
|
|
|||
|
return RETURN_SUCCESS;
|
|||
|
}
|
|||
|
|
|||
|
RETURN_STATUS
|
|||
|
PeCoffGetNextCertificate (
|
|||
|
IN OUT PE_COFF_LOADER_IMAGE_CONTEXT *Context,
|
|||
|
IN OUT CONST WIN_CERTIFICATE **Certificate
|
|||
|
)
|
|||
|
{
|
|||
|
BOOLEAN Overflow;
|
|||
|
UINT32 CertOffset;
|
|||
|
UINT32 CertSize;
|
|||
|
UINT32 CertEnd;
|
|||
|
CONST WIN_CERTIFICATE *WinCertificate;
|
|||
|
//
|
|||
|
// This condition is verified by PeCoffInitializeContext().
|
|||
|
//
|
|||
|
ASSERT (IS_ALIGNED (Context->SecDirSize, IMAGE_CERTIFICATE_ALIGN));
|
|||
|
//
|
|||
|
// Retrieve the current certificate.
|
|||
|
//
|
|||
|
WinCertificate = *Certificate;
|
|||
|
CertOffset = (UINT32) ((UINTN) WinCertificate - ((UINTN) Context->FileBuffer + Context->SecDirOffset));
|
|||
|
//
|
|||
|
// Retrieve the offset of the next certificate.
|
|||
|
//
|
|||
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
|
|||
|
CertSize = WinCertificate->dwLength;
|
|||
|
} else {
|
|||
|
CertSize = ALIGN_VALUE (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN);
|
|||
|
}
|
|||
|
|
|||
|
CertOffset += CertSize;
|
|||
|
//
|
|||
|
// This invariant is ensured by the certificate iteration functions.
|
|||
|
//
|
|||
|
ASSERT (CertOffset <= Context->SecDirSize);
|
|||
|
//
|
|||
|
// If the next offset is the end of the Directory, signal it's the end of
|
|||
|
// the certificate list.
|
|||
|
//
|
|||
|
if (CertOffset == Context->SecDirSize) {
|
|||
|
return RETURN_NOT_FOUND;
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the Directory fits another certificate.
|
|||
|
//
|
|||
|
if (Context->SecDirSize - CertOffset < sizeof (WIN_CERTIFICATE)) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the certificate has a well-formed size.
|
|||
|
//
|
|||
|
WinCertificate = (CONST WIN_CERTIFICATE *) (CONST VOID *) (
|
|||
|
(CONST UINT8 *) Context->FileBuffer + Context->SecDirOffset + CertOffset
|
|||
|
);
|
|||
|
if (WinCertificate->dwLength < sizeof (WIN_CERTIFICATE)) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the certificate size is sufficiently aligned, if the policy demands
|
|||
|
// it.
|
|||
|
//
|
|||
|
if ((PcdGet32 (PcdImageLoaderAlignmentPolicy) & PCD_ALIGNMENT_POLICY_CERTIFICATE_SIZES) == 0) {
|
|||
|
if (!IS_ALIGNED (WinCertificate->dwLength, IMAGE_CERTIFICATE_ALIGN)) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
}
|
|||
|
//
|
|||
|
// Verify the certificate is in bounds of the Security Directory.
|
|||
|
//
|
|||
|
Overflow = BaseOverflowAddU32 (
|
|||
|
CertOffset,
|
|||
|
WinCertificate->dwLength,
|
|||
|
&CertEnd
|
|||
|
);
|
|||
|
if (Overflow || CertEnd > Context->SecDirSize) {
|
|||
|
DEBUG_RAISE ();
|
|||
|
return RETURN_VOLUME_CORRUPTED;
|
|||
|
}
|
|||
|
|
|||
|
*Certificate = WinCertificate;
|
|||
|
|
|||
|
return RETURN_SUCCESS;
|
|||
|
}
|