/* fitparser.cpp
 
 Copyright (c) 2022, Nikolaj Schlej. All rights reserved.
 This program and the accompanying materials
 are licensed and made available under the terms and conditions of the BSD License
 which accompanies this distribution.  The full text of the license may be found at
 http://opensource.org/licenses/bsd-license.php
 
 THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
 WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.
 
 */
#include "fitparser.h"

#ifdef U_ENABLE_FIT_PARSING_SUPPORT

#include "intel_fit.h"
#include "ffs.h"
#include "parsingdata.h"
#include "types.h"
#include "utility.h"
#include "digest/sha2.h"

#include "umemstream.h"
#include "kaitai/kaitaistream.h"
#include "generated/intel_acbp_v1.h"
#include "generated/intel_acbp_v2.h"
#include "generated/intel_keym_v1.h"
#include "generated/intel_keym_v2.h"
#include "generated/intel_acm.h"

USTATUS FitParser::parseFit(const UModelIndex & index)
{
    // Reset parser state
    fitTable.clear();
    securityInfo = "";
    bgAcmFound = false;
    bgKeyManifestFound = false;
    bgBootPolicyFound = false;
    bgKmHash = UByteArray();
    bgBpHashSha256 = UByteArray();
    bgBpHashSha384 = UByteArray();
    
    // Check sanity
    if (!index.isValid()) {
        return U_INVALID_PARAMETER;
    }
    
    // Search for FIT
    UModelIndex fitIndex;
    UINT32 fitOffset;
    findFitRecursive(index, fitIndex, fitOffset);
    
    // FIT not found
    if (!fitIndex.isValid()) {
        // Nothing to parse further
        return U_SUCCESS;
    }
    // Explicitly set the item containing FIT as fixed
    model->setFixed(fitIndex, true);
    
    // Special case of FIT header
    UByteArray fitBody = model->body(fitIndex);
    // This is safe, as we checked the size in findFitRecursive already
    const INTEL_FIT_ENTRY* fitHeader = (const INTEL_FIT_ENTRY*)(fitBody.constData() + fitOffset);
    
    // Sanity check
    UINT32 fitSize = fitHeader->Size * sizeof(INTEL_FIT_ENTRY);
    if ((UINT32)fitBody.size() - fitOffset < fitSize) {
        msg(usprintf("%s: not enough space to contain the whole FIT table", __FUNCTION__), fitIndex);
        return U_INVALID_FIT;
    }
    
    // Check FIT checksum, if present
    if (fitHeader->ChecksumValid) {
        // Calculate FIT entry checksum
        UByteArray tempFIT = model->body(fitIndex).mid(fitOffset, fitSize);
        INTEL_FIT_ENTRY* tempFitHeader = (INTEL_FIT_ENTRY*)tempFIT.data();
        tempFitHeader->Checksum = 0;
        UINT8 calculated = calculateChecksum8((const UINT8*)tempFitHeader, fitSize);
        if (calculated != fitHeader->Checksum) {
            msg(usprintf("%s: invalid FIT table checksum %02Xh, should be %02Xh", __FUNCTION__, fitHeader->Checksum, calculated), fitIndex);
        }
    }
    
    // Check fit header type
    if (fitHeader->Type != INTEL_FIT_TYPE_HEADER) {
        msg(usprintf("%s: invalid FIT header type", __FUNCTION__), fitIndex);
        return U_INVALID_FIT;
    }
    
    // Add FIT header
    std::vector<UString> currentStrings;
    currentStrings.push_back(UString("_FIT_            "));
    currentStrings.push_back(usprintf("%08Xh", fitSize));
    currentStrings.push_back(usprintf("%04Xh", fitHeader->Version));
    currentStrings.push_back(usprintf("%02Xh", fitHeader->Checksum));
    currentStrings.push_back(fitEntryTypeToUString(fitHeader->Type));
    currentStrings.push_back(UString()); // Empty info for FIT header
    fitTable.push_back(std::pair<std::vector<UString>, UModelIndex>(currentStrings, fitIndex));
    
    // Process all other entries
    UModelIndex acmIndex;
    UModelIndex kmIndex;
    UModelIndex bpIndex;
    for (UINT32 i = 1; i < fitHeader->Size; i++) {
        currentStrings.clear();
        UString info;
        UModelIndex itemIndex;
        const INTEL_FIT_ENTRY* currentEntry = fitHeader + i;
        UINT32 currentEntrySize = currentEntry->Size;
        
        // Check sanity
        if (currentEntry->Type == INTEL_FIT_TYPE_HEADER) {
            msg(usprintf("%s: second FIT header found, the table is damaged", __FUNCTION__), fitIndex);
            return U_INVALID_FIT;
        }
        
        // Special case of version 0 entries for TXT and TPM policies
        if ((currentEntry->Type == INTEL_FIT_TYPE_TXT_POLICY || currentEntry->Type == INTEL_FIT_TYPE_TPM_POLICY)
            && currentEntry->Version == 0) {
            const INTEL_FIT_INDEX_IO_ADDRESS* policy = (const INTEL_FIT_INDEX_IO_ADDRESS*)currentEntry;
            info += usprintf("Index: %04Xh, BitPosition: %02Xh, AccessWidth: %02Xh, DataRegAddr: %04Xh, IndexRegAddr: %04Xh",
                             policy->Index,
                             policy->BitPosition,
                             policy->AccessWidthInBytes,
                             policy->DataRegisterAddress,
                             policy->IndexRegisterAddress);
        }
        else if (currentEntry->Address > ffsParser->addressDiff && currentEntry->Address < 0xFFFFFFFFUL) { // Only elements in the image need to be parsed
            UINT32 currentEntryBase = (UINT32)(currentEntry->Address - ffsParser->addressDiff);
            itemIndex = model->findByBase(currentEntryBase);
            if (itemIndex.isValid()) {
                UByteArray item = model->header(itemIndex) + model->body(itemIndex) + model->tail(itemIndex);
                UINT32 localOffset = currentEntryBase - model->base(itemIndex);
                
                switch (currentEntry->Type) {
                    case INTEL_FIT_TYPE_MICROCODE:
                        (void)parseFitEntryMicrocode(item, localOffset, itemIndex, info, currentEntrySize);
                        break;
                        
                    case INTEL_FIT_TYPE_STARTUP_AC_MODULE:
                        (void)parseFitEntryAcm(item, localOffset, itemIndex, info, currentEntrySize);
                        acmIndex = itemIndex;
                        break;
                        
                    case INTEL_FIT_TYPE_BOOT_GUARD_KEY_MANIFEST:
                        (void)parseFitEntryBootGuardKeyManifest(item, localOffset, itemIndex, info, currentEntrySize);
                        kmIndex = itemIndex;
                        break;
                        
                    case INTEL_FIT_TYPE_BOOT_GUARD_BOOT_POLICY:
                        (void)parseFitEntryBootGuardBootPolicy(item, localOffset, itemIndex, info, currentEntrySize);
                        bpIndex = itemIndex;
                        break;
                        
                    default:
                        // Do nothing
                        break;
                }
            }
            else {
                msg(usprintf("%s: FIT entry #%u not found in the image", __FUNCTION__, i), fitIndex);
            }
        }
        
        // Explicitly set the item referenced by FIT as fixed
        if (itemIndex.isValid()) {
            model->setFixed(itemIndex, true);
        }
        
        // Add entry to fitTable
        currentStrings.push_back(usprintf("%016" PRIX64 "h", currentEntry->Address));
        currentStrings.push_back(usprintf("%08Xh", currentEntrySize));
        currentStrings.push_back(usprintf("%04Xh", currentEntry->Version));
        currentStrings.push_back(usprintf("%02Xh", currentEntry->Checksum));
        currentStrings.push_back(fitEntryTypeToUString(currentEntry->Type));
        currentStrings.push_back(info);
        fitTable.push_back(std::pair<std::vector<UString>, UModelIndex>(currentStrings, itemIndex));
    }
    
    // Perform validation of BootGuard components
    if (bgAcmFound) {
        if (!bgKeyManifestFound) {
            msg(usprintf("%s: startup ACM found, but KeyManifest is not", __FUNCTION__), acmIndex);
        }
        else if (!bgBootPolicyFound) {
            msg(usprintf("%s: startup ACM and Key Manifest found, Boot Policy is not", __FUNCTION__), kmIndex);
        }
        else {
            // Check key hashes
            if (!bgKmHash.isEmpty()
                && !(bgKmHash == bgBpHashSha256 || bgKmHash == bgBpHashSha384)) {
                msg(usprintf("%s: Boot Policy key hash stored in Key Manifest differs from the hash of the public key stored in Boot Policy", __FUNCTION__), bpIndex);
                return U_SUCCESS;
            }
        }
    }

    return U_SUCCESS;
}

void FitParser::findFitRecursive(const UModelIndex & index, UModelIndex & found, UINT32 & fitOffset)
{
    // Sanity check
    if (!index.isValid()) {
        return;
    }
    
    // Process child items
    for (int i = 0; i < model->rowCount(index); i++) {
        findFitRecursive(index.model()->index(i, 0, index), found, fitOffset);
        
        if (found.isValid()) {
            // Found it, no need to process further
            return;
        }
    }
    
    // Check for all FIT signatures in item body
    UByteArray lastVtfBody = model->body(ffsParser->lastVtf);
    UINT64 fitSignatureValue = INTEL_FIT_SIGNATURE;
    UByteArray fitSignature((const char*)&fitSignatureValue, sizeof(fitSignatureValue));
    UINT32 storedFitAddress = *(const UINT32*)(lastVtfBody.constData() + lastVtfBody.size() - INTEL_FIT_POINTER_OFFSET);
    for (INT32 offset = (INT32)model->body(index).indexOf(fitSignature);
         offset >= 0;
         offset = (INT32)model->body(index).indexOf(fitSignature, offset + 1)) {
        // FIT candidate found, calculate its physical address
        UINT32 fitAddress = (UINT32)(model->base(index) + (UINT32)ffsParser->addressDiff + model->header(index).size() + (UINT32)offset);
        
        // Check FIT address to be stored in the last VTF
        if (fitAddress == storedFitAddress) {
            // Valid FIT table must have at least two entries
            if ((UINT32)model->body(index).size() < offset + 2*sizeof(INTEL_FIT_ENTRY)) {
                msg(usprintf("%s: FIT table candidate found, too small to contain real FIT", __FUNCTION__), index);
            }
            else {
                // Real FIT found
                found = index;
                fitOffset = offset;
                msg(usprintf("%s: real FIT table found at physical address %08Xh", __FUNCTION__, fitAddress), found);
                break;
            }
        }
        else if (model->rowCount(index) == 0) { // Show messages only to leaf items
            msg(usprintf("%s: FIT table candidate found, but not referenced from the last VTF", __FUNCTION__), index);
        }
    }
}

USTATUS FitParser::parseFitEntryMicrocode(const UByteArray & microcode, const UINT32 localOffset, const UModelIndex & parent, UString & info, UINT32 &realSize)
{
    U_UNUSED_PARAMETER(parent);
    if ((UINT32)microcode.size() - localOffset < sizeof(INTEL_MICROCODE_HEADER)) {
        return U_INVALID_MICROCODE;
    }
    
    const INTEL_MICROCODE_HEADER* ucodeHeader = (const INTEL_MICROCODE_HEADER*)(microcode.constData() + localOffset);
    if (!ffsParser->microcodeHeaderValid(ucodeHeader)) {
        return U_INVALID_MICROCODE;
    }
    
    if ((UINT32)microcode.size() - localOffset < ucodeHeader->TotalSize) {
        return U_INVALID_MICROCODE;
    }
    
    // Valid microcode found
    info = usprintf("CpuSignature: %08Xh, Revision: %08Xh, Date: %02X.%02X.%04X",
                    ucodeHeader->ProcessorSignature,
                    ucodeHeader->UpdateRevision,
                    ucodeHeader->DateDay,
                    ucodeHeader->DateMonth,
                    ucodeHeader->DateYear);
    realSize = ucodeHeader->TotalSize;
    
    return U_SUCCESS;
}

USTATUS FitParser::parseFitEntryAcm(const UByteArray & acm, const UINT32 localOffset, const UModelIndex & parent, UString & info, UINT32 &realSize)
{
    try {
        umemstream is(acm.constData(), acm.size());
        is.seekg(localOffset, is.beg);
        kaitai::kstream ks(&is);
        intel_acm_t parsed(&ks);
        intel_acm_t::header_t* header = parsed.header();
        
        realSize = header->module_size();
        
        // Check header version to be of a known value
        if (header->header_version() != intel_acm_t::KNOWN_HEADER_VERSION_V0_0
            && header->header_version() != intel_acm_t::KNOWN_HEADER_VERSION_V3_0) {
            msg(usprintf("%s: Intel ACM with unknown header version %08Xh found", __FUNCTION__, header->header_version()), parent);
        }
        
        // Valid ACM found
        info = usprintf("LocalOffset: %08Xh, EntryPoint: %08Xh, ACM SVN: %04Xh, Date: %02X.%02X.%04X",
                        localOffset,
                        header->entry_point(),
                        header->acm_svn(),
                        header->date_day(),
                        header->date_month(),
                        header->date_year());
        
        // Populate ACM info
        UString acmInfo;
        if (header->module_subtype() == intel_acm_t::MODULE_SUBTYPE_TXT) {
            acmInfo = "TXT ACM ";
        }
        else if(header->module_subtype() == intel_acm_t::MODULE_SUBTYPE_STARTUP) {
            acmInfo = "Startup ACM ";
        }
        else if (header->module_subtype() == intel_acm_t::MODULE_SUBTYPE_BOOT_GUARD) {
            acmInfo = "BootGuard ACM ";
        }
        else {
            acmInfo = usprintf("Unknown ACM (%04Xh)", header->module_subtype());
            msg(usprintf("%s: Intel ACM with unknown subtype %04Xh found", __FUNCTION__, header->module_subtype()), parent);
        }
        
        acmInfo += usprintf("found at base %Xh\n"
                            "ModuleType: %04Xh\n"
                            "ModuleSubtype: %04Xh\n"
                            "HeaderSize: %08Xh\n"
                            "HeaderVersion: %08Xh\n"
                            "ChipsetId: %04Xh\n"
                            "Flags: %04Xh\n"
                            "ModuleVendor: %04Xh\n"
                            "Date: %02X.%02X.%04X\n"
                            "ModuleSize: %08Xh\n"
                            "AcmSvn: %04Xh\n"
                            "SeSvn: %04Xh\n"
                            "CodeControlFlags: %08Xh\n"
                            "ErrorEntryPoint: %08Xh\n"
                            "GdtMax: %08Xh\n"
                            "GdtBase: %08Xh\n"
                            "SegmentSel: %08Xh\n"
                            "EntryPoint: %08Xh\n"
                            "KeySize: %08Xh\n"
                            "ScratchSpaceSize: %08Xh\n",
                            model->base(parent) + localOffset,
                            header->module_type(),
                            header->module_subtype(),
                            header->header_size() * (UINT32)sizeof(UINT32),
                            header->header_version(),
                            header->chipset_id(),
                            header->flags(),
                            header->module_vendor(),
                            header->date_day(), header->date_month(), header->date_year(),
                            header->module_size() * (UINT32)sizeof(UINT32),
                            header->acm_svn(),
                            header->se_svn(),
                            header->code_control_flags(),
                            header->error_entry_point(),
                            header->gdt_max(),
                            header->gdt_base(),
                            header->segment_sel(),
                            header->entry_point(),
                            header->key_size() * (UINT32)sizeof(UINT32),
                            header->scratch_space_size() * (UINT32)sizeof(UINT32));
        
        // Add RsaPublicKey
        if (header->_is_null_rsa_exponent() == false) {
            acmInfo += usprintf("ACM RSA Public Key Exponent: %Xh\n", header->rsa_exponent());
        }
        else {
            acmInfo += usprintf("ACM RSA Public Key Exponent: %Xh\n", INTEL_ACM_HARDCODED_RSA_EXPONENT);
        }
        acmInfo += usprintf("ACM RSA Public Key:");
        for (UINT32 i = 0; i < header->rsa_public_key().size(); i++) {
            if (i % 32 == 0) acmInfo += "\n";
            acmInfo += usprintf("%02X", (UINT8)header->rsa_public_key().at(i));
        }
        acmInfo += "\n";
        
        // Add RsaSignature
        acmInfo += UString("ACM RSA Signature:");
        for (UINT32 i = 0; i < header->rsa_signature().size(); i++) {
            if (i % 32 == 0) acmInfo +="\n";
            acmInfo += usprintf("%02X", (UINT8)header->rsa_signature().at(i));
        }
        acmInfo += "\n";
        
        securityInfo += acmInfo + "\n";
        bgAcmFound = true;
        return U_SUCCESS;
    }
    catch (...) {
        msg(usprintf("%s: unable to parse ACM", __FUNCTION__), parent);
        return U_INVALID_ACM;
    }
}

USTATUS FitParser::parseFitEntryBootGuardKeyManifest(const UByteArray & keyManifest, const UINT32 localOffset, const UModelIndex & parent, UString & info, UINT32 &realSize)
{
    U_UNUSED_PARAMETER(realSize);
    
    // v1
    try {
        umemstream is(keyManifest.constData(), keyManifest.size());
        is.seekg(localOffset, is.beg);
        kaitai::kstream ks(&is);
        intel_keym_v1_t parsed(&ks);
        
        // Valid KM found
        info = usprintf("LocalOffset: %08Xh, Version: %02Xh, KM Version: %02Xh, KM SVN: %02Xh",
                        localOffset,
                        parsed.version(),
                        parsed.km_version(),
                        parsed.km_svn());
        
        // Populate KM info
        UString kmInfo
        = usprintf("Intel BootGuard Key manifest found at base %Xh\n"
                   "Tag: '__KEYM__'\n"
                   "Version: %02Xh\n"
                   "KmVersion: %02Xh\n"
                   "KmSvn: %02Xh\n"
                   "KmId: %02Xh\n",
                   model->base(parent) + localOffset,
                   parsed.version(),
                   parsed.km_version(),
                   parsed.km_svn(),
                   parsed.km_id());
        
        // Add KM hash
        kmInfo += UString("KM Hash (") + hashTypeToUString(parsed.km_hash()->hash_algorithm_id()) + "): ";
        for (UINT16 j = 0; j < parsed.km_hash()->len_hash(); j++) {
            kmInfo += usprintf("%02X", (UINT8) parsed.km_hash()->hash().data()[j]);
        }
        kmInfo += "\n";
        
        // Add Key Signature
        const intel_keym_v1_t::key_signature_t* key_signature = parsed.key_signature();
        kmInfo += usprintf("Key Manifest Key Signature:\n"
                           "Version: %02Xh\n"
                           "KeyId: %04Xh\n"
                           "SigScheme: %04Xh\n",
                           key_signature->version(),
                           key_signature->key_id(),
                           key_signature->sig_scheme());
                           
        // Add PubKey
        kmInfo += usprintf("Key Manifest Public Key Exponent: %Xh\n", key_signature->public_key()->exponent());
        kmInfo += usprintf("Key Manifest Public Key:");
        for (UINT16 i = 0; i < (UINT16)key_signature->public_key()->modulus().length(); i++) {
            if (i % 32 == 0) kmInfo += UString("\n");
            kmInfo += usprintf("%02X", (UINT8)key_signature->public_key()->modulus().at(i));
        }
        kmInfo += "\n";
        
        // One of those hashes is what's getting written into Field Programmable Fuses
        // Calculate the hashes of public key modulus only
        UINT8 hash[SHA384_HASH_SIZE] = {};
        sha256(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus Only, SHA256): ");
        for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        sha384(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus Only, SHA384): ");
        for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        // Calculate the hashes of public key modulus + exponent
        UByteArray dataToHash;
        dataToHash += UByteArray(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length());
        UINT32 exponent = key_signature->public_key()->exponent();
        dataToHash += UByteArray((const char*)&exponent, sizeof(exponent));
        sha256(dataToHash.constData(), dataToHash.size(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus+Exponent, SHA256): ");
        for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        sha384(dataToHash.constData(), dataToHash.size(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus+Exponent, SHA384): ");
        for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        
        // Add Signature
        kmInfo += UString("Key Manifest Signature: ");
        for (UINT16 i = 0; i < (UINT16)key_signature->signature()->signature().length(); i++) {
            if (i % 32 == 0) kmInfo += UString("\n");
            kmInfo += usprintf("%02X", (UINT8)key_signature->signature()->signature().at(i));
        }
        kmInfo += "\n";
                        
        securityInfo += kmInfo + "\n";
        bgKeyManifestFound = true;
        return U_SUCCESS;
    }
    catch (...) {
        // Do nothing here, will try parsing as v2 next
    }
    
    // v2
    try {
        umemstream is(keyManifest.constData(), keyManifest.size());
        is.seekg(localOffset, is.beg);
        kaitai::kstream ks(&is);
        intel_keym_v2_t parsed(&ks);
        intel_keym_v2_t::header_t* header = parsed.header();
        
        // Valid KM found
        info = usprintf("LocalOffset: %08Xh, Version: %02Xh, KM Version: %02Xh, KM SVN: %02Xh",
                        localOffset,
                        header->version(),
                        parsed.km_version(),
                        parsed.km_svn());
        
        // Populate KM info
        UString kmInfo
        = usprintf("Intel BootGuard Key manifest found at base %Xh\n"
                   "Tag: '__KEYM__'\n"
                   "Version: %02Xh\n"
                   "KmVersion: %02Xh\n"
                   "KmSvn: %02Xh\n"
                   "KmId: %02Xh\n"
                   "KeySignatureOffset: %04Xh\n"
                   "FPFHashAlgorithmId: %04Xh\n"
                   "HashCount: %04Xh\n",
                   model->base(parent) + localOffset,
                   header->version(),
                   parsed.km_version(),
                   parsed.km_svn(),
                   parsed.km_id(),
                   parsed.key_signature_offset(),
                   parsed.fpf_hash_algorithm_id(),
                   parsed.num_km_hashes());
        
        // Add KM hashes
        if (parsed.num_km_hashes() == 0) {
            kmInfo += UString("KM Hashes: N/A\n");
            msg(usprintf("%s: Key Manifest without KM hashes", __FUNCTION__), parent);
        }
        else {
            kmInfo += UString("KM Hashes:\n");
            for (UINT16 i = 0; i < parsed.num_km_hashes(); i++) {
                const auto & current_km_hash = parsed.km_hashes()->at(i);
                
                // Add KM hash
                kmInfo += usprintf("UsageFlags: %016" PRIX64 "h, ", current_km_hash->usage_flags()) + hashTypeToUString(current_km_hash->hash_algorithm_id()) + ": ";
                for (UINT16 j = 0; j < current_km_hash->len_hash(); j++) {
                    kmInfo += usprintf("%02X", (UINT8)current_km_hash->hash().data()[j]);
                }
                kmInfo += "\n";
                
                if (current_km_hash->usage_flags() == intel_keym_v2_t::KM_USAGE_FLAGS_BOOT_POLICY_MANIFEST) {
                    bgKmHash = UByteArray((const char*)current_km_hash->hash().data(), current_km_hash->hash().size());
                }
            }
        }
        
        // Add Key Signature
        const intel_keym_v2_t::key_signature_t* key_signature = parsed.key_signature();
        kmInfo += usprintf("Key Manifest Key Signature:\n"
                           "Version: %02Xh\n"
                           "KeyId: %04Xh\n"
                           "SigScheme: %04Xh\n",
                           key_signature->version(),
                           key_signature->key_id(),
                           key_signature->sig_scheme());
                           
        // Add PubKey
        kmInfo += usprintf("Key Manifest Public Key Exponent: %Xh\n", key_signature->public_key()->exponent());
        kmInfo += usprintf("Key Manifest Public Key:");
        for (UINT16 i = 0; i < (UINT16)key_signature->public_key()->modulus().length(); i++) {
            if (i % 32 == 0) kmInfo += UString("\n");
            kmInfo += usprintf("%02X", (UINT8)key_signature->public_key()->modulus().at(i));
        }
        kmInfo += "\n";
        
        // One of those hashes is what's getting written into Field Programmable Fuses
        // Calculate the hashes of public key modulus only
        UINT8 hash[SHA384_HASH_SIZE] = {};
        sha256(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus Only, SHA256): ");
        for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        sha384(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus Only, SHA384): ");
        for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        // Calculate the hashes of public key modulus + exponent
        UByteArray dataToHash;
        dataToHash += UByteArray(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length());
        UINT32 exponent = key_signature->public_key()->exponent();
        dataToHash += UByteArray((const char*)&exponent, sizeof(exponent));
        sha256(dataToHash.constData(), dataToHash.size(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus+Exponent, SHA256): ");
        for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        sha384(dataToHash.constData(), dataToHash.size(), hash);
        kmInfo += usprintf("Key Manifest Public Key Hash (Modulus+Exponent, SHA384): ");
        for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
            kmInfo += usprintf("%02X", hash[i]);
        }
        kmInfo += "\n";
        
        // Add Signature
        kmInfo += UString("Key Manifest Signature: ");
        for (UINT16 i = 0; i < (UINT16)key_signature->signature()->signature().length(); i++) {
            if (i % 32 == 0) kmInfo += UString("\n");
            kmInfo += usprintf("%02X", (UINT8)key_signature->signature()->signature().at(i));
        }
        kmInfo += "\n";
                
        securityInfo += kmInfo + "\n";
        bgKeyManifestFound = true;
        return U_SUCCESS;
    }
    catch (...) {
        msg(usprintf("%s: unable to parse Key Manifest", __FUNCTION__), parent);
        return U_INVALID_BOOT_GUARD_KEY_MANIFEST;
    }
}

USTATUS FitParser::parseFitEntryBootGuardBootPolicy(const UByteArray & bootPolicy, const UINT32 localOffset, const UModelIndex & parent, UString & info, UINT32 &realSize)
{
    U_UNUSED_PARAMETER(realSize);
    
    // v1
    try {
        umemstream is(bootPolicy.constData(), bootPolicy.size());
        is.seekg(localOffset, is.beg);
        kaitai::kstream ks(&is);
        intel_acbp_v1_t parsed(&ks);
        
        // Valid BPM found
        info = usprintf("LocalOffset: %08Xh, Version: %02Xh, BP SVN: %02Xh, ACM SVN: %02Xh",
                        localOffset,
                        parsed.version(),
                        parsed.bp_svn(),
                        parsed.acm_svn());
        
        UString bpInfo
        = usprintf("Intel BootGuard Boot Policy Manifest found at base %Xh\n"
                   "StructureId: '__ACBP__'\n"
                   "Version: %02Xh\n"
                   "BPMRevision: %02Xh\n"
                   "BPSVN: %02Xh\n"
                   "ACMSVN: %02Xh\n"
                   "NEMDataSize: %04Xh\n",
                   model->base(parent) + localOffset,
                   parsed.version(),
                   parsed.bpm_revision(),
                   parsed.bp_svn(),
                   parsed.acm_svn(),
                   parsed.nem_data_size());
        
        bpInfo += UString("Boot Policy Elements:\n");
        for (const auto & element : *parsed.elements()) {
            const auto & element_header = element->header();
            
            UINT64 structure_id = (UINT64) element_header->structure_id();
            const char* structure_id_bytes = (const char*)&structure_id;
            
            bpInfo += usprintf("StructureId: '%c%c%c%c%c%c%c%c'\n"
                               "Version: %02Xh\n",
                               structure_id_bytes[0],
                               structure_id_bytes[1],
                               structure_id_bytes[2],
                               structure_id_bytes[3],
                               structure_id_bytes[4],
                               structure_id_bytes[5],
                               structure_id_bytes[6],
                               structure_id_bytes[7],
                               element_header->version());
            
            // IBBS
            if (element->_is_null_ibbs_body() == false) {
                const intel_acbp_v1_t::ibbs_body_t* ibbs_body = element->ibbs_body();
                
                // Valid IBBS element found
                bpInfo += usprintf("Flags: %08Xh\n"
                                   "MchBar: %016" PRIX64 "h\n"
                                   "VtdBar: %016" PRIX64 "h\n"
                                   "DmaProtectionBase0: %08Xh\n"
                                   "DmaProtectionLimit0: %08Xh\n"
                                   "DmaProtectionBase1: %016" PRIX64 "h\n"
                                   "DmaProtectionLimit1: %016" PRIX64 "h\n"
                                   "IbbEntryPoint: %08Xh\n"
                                   "IbbSegmentsCount: %02Xh\n",
                                   ibbs_body->flags(),
                                   ibbs_body->mch_bar(),
                                   ibbs_body->vtd_bar(),
                                   ibbs_body->dma_protection_base0(),
                                   ibbs_body->dma_protection_limit0(),
                                   ibbs_body->dma_protection_base1(),
                                   ibbs_body->dma_protection_limit1(),
                                   ibbs_body->ibb_entry_point(),
                                   ibbs_body->num_ibb_segments());
                
                // Check for non-empty PostIbbHash
                if (ibbs_body->post_ibb_hash()->len_hash() == 0) {
                    bpInfo += UString("PostIBB Hash: N/A\n");
                }
                else {
                    // Add postIbbHash protected range
                    UByteArray postIbbHash(ibbs_body->post_ibb_hash()->hash().data(), ibbs_body->post_ibb_hash()->len_hash());
                    if (postIbbHash.count('\x00') != postIbbHash.size()
                        && postIbbHash.count('\xFF') != postIbbHash.size()) {
                        PROTECTED_RANGE range = {};
                        range.Type = PROTECTED_RANGE_INTEL_BOOT_GUARD_POST_IBB;
                        range.AlgorithmId = ibbs_body->post_ibb_hash()->hash_algorithm_id();
                        range.Hash = postIbbHash;
                        ffsParser->protectedRanges.push_back(range);
                    }
                    
                    // Add PostIbbHash
                    bpInfo += UString("PostIBB Hash (") + hashTypeToUString(ibbs_body->post_ibb_hash()->hash_algorithm_id()) + "): ";
                    for (UINT16 i = 0; i < ibbs_body->post_ibb_hash()->len_hash(); i++) {
                        bpInfo += usprintf("%02X", (UINT8)ibbs_body->post_ibb_hash()->hash().data()[i]);
                    }
                    bpInfo += "\n";
                }
                
                // Add IbbHash
                bpInfo += UString("IBB Hash (") + hashTypeToUString(ibbs_body->ibb_hash()->hash_algorithm_id()) + "): ";
                for (UINT16 j = 0; j < ibbs_body->ibb_hash()->len_hash(); j++) {
                    bpInfo += usprintf("%02X", (UINT8)ibbs_body->ibb_hash()->hash().data()[j]);
                }
                bpInfo += "\n";
                
                // Check for non-empty IbbSegments
                if (ibbs_body->num_ibb_segments() == 0) {
                    bpInfo += UString("IBB Segments: N/A\n");
                    msg(usprintf("%s: Boot Policy without IBB segments", __FUNCTION__), parent);
                }
                else {
                    bpInfo += UString("IBB Segments:\n");
                    for (UINT8 i = 0; i < ibbs_body->num_ibb_segments(); i++) {
                        const auto & current_segment = ibbs_body->ibb_segments()->at(i);
                        
                        bpInfo += usprintf("Flags: %04Xh, Address: %08Xh, Size: %08Xh\n",
                                           current_segment->flags(),
                                           current_segment->base(),
                                           current_segment->size());
                        
                        if (current_segment->flags() == intel_acbp_v1_t::IBB_SEGMENT_TYPE_IBB
                            && current_segment->base() != 0xFFFFFFFF && current_segment->size() != 0 && current_segment->size() != 0xFFFFFFFF) {
                            PROTECTED_RANGE range = {};
                            range.Offset = current_segment->base();
                            range.Size = current_segment->size();
                            range.AlgorithmId = TCG_HASH_ALGORITHM_ID_SHA256;
                            range.Type = PROTECTED_RANGE_INTEL_BOOT_GUARD_IBB;
                            ffsParser->protectedRanges.push_back(range);
                        }
                    }
                }
            }
            // PMDA
            else if (element->_is_null_pmda_body() == false) {
                intel_acbp_v1_t::pmda_body_t* pmda_body = element->pmda_body();
                
                // Valid Microsoft PMDA element found
                bpInfo += usprintf("TotalSize: %04Xh\n"
                                   "Version: %08Xh\n"
                                   "NumEntries: %08Xh\n",
                                   pmda_body->total_size(),
                                   pmda_body->version(),
                                   pmda_body->num_entries());
                if (pmda_body->num_entries() == 0) {
                    bpInfo += UString("PMDA Entries: N/A\n");
                }
                else {
                    bpInfo += UString("PMDA Entries:\n");
                    // v1 entries
                    if (pmda_body->_is_null_entries_v1() == false) {
                        for (UINT32 i = 0; i < pmda_body->num_entries(); i++) {
                            const auto & current_element = pmda_body->entries_v1()->at(i);
                            
                            // Add element
                            bpInfo += usprintf("Address: %08Xh, Size: %08Xh\n",
                                               current_element->base(),
                                               current_element->size());
                            
                            // Add hash
                            bpInfo += "SHA256: ";
                            for (UINT16 j = 0; j < (UINT16)current_element->hash().size(); j++) {
                                bpInfo += usprintf("%02X", (UINT8)current_element->hash().data()[j]);
                            }
                            bpInfo += "\n";
                            
                            // Add protected range
                            if (current_element->base() != 0xFFFFFFFF && current_element->size() != 0 && current_element->size() != 0xFFFFFFFF) {
                                PROTECTED_RANGE range = {};
                                range.Offset = current_element->base();
                                range.Size = current_element->size();
                                range.Type = PROTECTED_RANGE_VENDOR_HASH_MICROSOFT_PMDA;
                                range.AlgorithmId = TCG_HASH_ALGORITHM_ID_SHA256;
                                range.Hash = UByteArray(current_element->hash().data(), current_element->hash().size());
                                ffsParser->protectedRanges.push_back(range);
                            }
                        }
                    }
                    // v2 entries
                    else if (pmda_body->_is_null_entries_v2() == false) {
                        for (UINT32 i = 0; i < pmda_body->num_entries(); i++) {
                            const auto & current_element = pmda_body->entries_v2()->at(i);
                            
                            // Add element
                            bpInfo += usprintf("Address: %08Xh, Size: %08Xh\n",
                                               current_element->base(),
                                               current_element->size());
                            
                            // Add hash
                            bpInfo += hashTypeToUString(current_element->hash()->hash_algorithm_id()) + ": ";
                            for (UINT16 j = 0; j < (UINT16)current_element->hash()->hash().size(); j++) {
                                bpInfo += usprintf("%02X", (UINT8)current_element->hash()->hash().data()[j]);
                            }
                            bpInfo += "\n";
                            
                            // Add protected range
                            if (current_element->base() != 0xFFFFFFFF && current_element->size() != 0 && current_element->size() != 0xFFFFFFFF) {
                                PROTECTED_RANGE range = {};
                                range.Offset = current_element->base();
                                range.Size = current_element->size();
                                range.Type = PROTECTED_RANGE_VENDOR_HASH_MICROSOFT_PMDA;
                                range.AlgorithmId = current_element->hash()->hash_algorithm_id();
                                range.Hash = UByteArray(current_element->hash()->hash().data(), current_element->hash()->hash().size());
                                ffsParser->protectedRanges.push_back(range);
                            }
                        }
                    }
                }
            }
            // PMSG
            else if (element->_is_null_pmsg_body() == false) {
                const intel_acbp_v1_t::pmsg_body_t* key_signature = element->pmsg_body();
                bpInfo += usprintf("Boot Policy Key Signature:\n"
                                   "Version: %02Xh\n"
                                   "KeyId: %04Xh\n"
                                   "SigScheme: %04Xh\n",
                                   key_signature->version(),
                                   key_signature->key_id(),
                                   key_signature->sig_scheme());
                                   
                // Add PubKey
                bpInfo += usprintf("Boot Policy Public Key Exponent: %Xh\n", key_signature->public_key()->exponent());
                bpInfo += usprintf("Boot Policy Public Key:");
                for (UINT16 i = 0; i < (UINT16)key_signature->public_key()->modulus().length(); i++) {
                    if (i % 32 == 0) bpInfo += UString("\n");
                    bpInfo += usprintf("%02X", (UINT8)key_signature->public_key()->modulus().at(i));
                }
                bpInfo += "\n";
                
                // Calculate and add PubKey hashes
                UINT8 hash[SHA384_HASH_SIZE];
                // SHA256
                sha256(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length() , hash);
                bpInfo += UString("Boot Policy Public Key Hash (SHA256): ");
                for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
                    bpInfo += usprintf("%02X", hash[i]);
                }
                bpInfo += "\n";
                bgBpHashSha256 = UByteArray((const char*)hash, SHA256_HASH_SIZE);
                // SHA384
                sha384(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length() , hash);
                bpInfo += UString("Boot Policy Public Key Hash (SHA384): ");
                for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
                    bpInfo += usprintf("%02X", hash[i]);
                }
                bpInfo += "\n";
                bgBpHashSha384 = UByteArray((const char*)hash, SHA384_HASH_SIZE);
                
                // Add Signature
                bpInfo += UString("Boot Policy Signature: ");
                for (UINT16 i = 0; i < (UINT16)key_signature->signature()->signature().length(); i++) {
                    if (i % 32 == 0) bpInfo += UString("\n");
                    bpInfo += usprintf("%02X", (UINT8)key_signature->signature()->signature().at(i));
                }
                bpInfo += "\n";
            }
        }
        
        securityInfo += bpInfo + "\n";
        bgBootPolicyFound = true;
        return U_SUCCESS;
    }
    catch (...) {
        // Do nothing here, will try parsing as v2 next
    }
    
    // v2
    try {
        umemstream is(bootPolicy.constData(), bootPolicy.size());
        is.seekg(localOffset, is.beg);
        kaitai::kstream ks(&is);
        intel_acbp_v2_t parsed(&ks); // This already verified the version to be >= 0x20
        // Valid BPM found
        info = usprintf("LocalOffset: %08Xh, Version: %02Xh, BP SVN: %02Xh, ACM SVN: %02Xh",
                        localOffset,
                        parsed.version(),
                        parsed.bp_svn(),
                        parsed.acm_svn());
        
        // Add BP header and body info
        UString bpInfo
        = usprintf("Intel BootGuard Boot Policy Manifest found at base %Xh\n"
                   "StructureId: '__ACBP__'\n"
                   "Version: %02Xh\n"
                   "HeaderSpecific: %02Xh\n"
                   "TotalSize: %04Xh\n"
                   "KeySignatureOffset: %04Xh\n"
                   "BPMRevision: %02Xh\n"
                   "BPSVN: %02Xh\n"
                   "ACMSVN: %02Xh\n"
                   "NEMDataSize: %04Xh\n",
                   model->base(parent) + localOffset,
                   parsed.version(),
                   parsed.header_specific(),
                   parsed.total_size(),
                   parsed.key_signature_offset(),
                   parsed.bpm_revision(),
                   parsed.bp_svn(),
                   parsed.acm_svn(),
                   parsed.nem_data_size());
        
        bpInfo += UString("Boot Policy Elements:\n");
        for (const auto & element : *parsed.elements()) {
            const intel_acbp_v2_t::header_t* element_header = element->header();
            
            UINT64 structure_id = element_header->structure_id();
            const char* structure_id_bytes = (const char*)&structure_id;
            
            bpInfo += usprintf("StructureId: '%c%c%c%c%c%c%c%c'\n"
                               "Version: %02Xh\n"
                               "HeaderSpecific: %02Xh\n"
                               "TotalSize: %04Xh\n",
                               structure_id_bytes[0],
                               structure_id_bytes[1],
                               structure_id_bytes[2],
                               structure_id_bytes[3],
                               structure_id_bytes[4],
                               structure_id_bytes[5],
                               structure_id_bytes[6],
                               structure_id_bytes[7],
                               element_header->version(),
                               element_header->header_specific(),
                               element_header->total_size());
            
            // IBBS
            if (element->_is_null_ibbs_body() == false) {
                const intel_acbp_v2_t::ibbs_body_t* ibbs_body = element->ibbs_body();
                
                // Valid IBBS element found
                bpInfo += usprintf("SetNumber: %02Xh\n"
                                   "PBETValue: %02Xh\n"
                                   "Flags: %08Xh\n"
                                   "MchBar: %016" PRIX64 "h\n"
                                   "VtdBar: %016" PRIX64 "h\n"
                                   "DmaProtectionBase0: %08Xh\n"
                                   "DmaProtectionLimit0: %08Xh\n"
                                   "DmaProtectionBase1: %016" PRIX64 "h\n"
                                   "DmaProtectionLimit1: %016" PRIX64 "h\n"
                                   "IbbEntryPoint: %08Xh\n"
                                   "IbbDigestsSize: %02Xh\n"
                                   "IbbDigestsCount: %02Xh\n"
                                   "IbbSegmentsCount: %02Xh\n",
                                   ibbs_body->set_number(),
                                   ibbs_body->pbet_value(),
                                   ibbs_body->flags(),
                                   ibbs_body->mch_bar(),
                                   ibbs_body->vtd_bar(),
                                   ibbs_body->dma_protection_base0(),
                                   ibbs_body->dma_protection_limit0(),
                                   ibbs_body->dma_protection_base1(),
                                   ibbs_body->dma_protection_limit1(),
                                   ibbs_body->ibb_entry_point(),
                                   ibbs_body->ibb_digests_size(),
                                   ibbs_body->num_ibb_digests(),
                                   ibbs_body->num_ibb_segments());
                
                // Check for non-empty PostIbbHash
                if (ibbs_body->post_ibb_digest()->len_hash() == 0) {
                    bpInfo += UString("PostIBB Hash: N/A\n");
                }
                else {
                    // Add postIbbHash protected range
                    UByteArray postIbbHash(ibbs_body->post_ibb_digest()->hash().data(), ibbs_body->post_ibb_digest()->len_hash());
                    if (postIbbHash.count('\x00') != postIbbHash.size()
                        && postIbbHash.count('\xFF') != postIbbHash.size()) {
                        PROTECTED_RANGE range = {};
                        range.Type = PROTECTED_RANGE_INTEL_BOOT_GUARD_POST_IBB;
                        range.AlgorithmId = ibbs_body->post_ibb_digest()->hash_algorithm_id();
                        range.Hash = postIbbHash;
                        ffsParser->protectedRanges.push_back(range);
                    }
                    
                    // Add PostIbbDigest
                    bpInfo += UString("PostIBB Hash (") + hashTypeToUString(ibbs_body->post_ibb_digest()->hash_algorithm_id()) + "): ";
                    for (UINT16 i = 0; i < ibbs_body->post_ibb_digest()->len_hash(); i++) {
                        bpInfo += usprintf("%02X", (UINT8)ibbs_body->post_ibb_digest()->hash().data()[i]);
                    }
                    bpInfo += "\n";
                }
                
                // Check for non-empty ObbHash
                if (ibbs_body->obb_digest() == 0) {
                    bpInfo += UString("OBB Hash: N/A\n");
                }
                else {
                    // Add ObbHash
                    bpInfo += UString("OBB Hash (") + hashTypeToUString(ibbs_body->obb_digest()->hash_algorithm_id()) + "): ";
                    for (UINT16 i = 0; i < ibbs_body->obb_digest()->len_hash(); i++) {
                        bpInfo += usprintf("%02X", (UINT8)ibbs_body->obb_digest()->hash().data()[i]);
                    }
                    bpInfo += "\n";
                    
                    // Add ObbHash protected range
                    UByteArray obbHash(ibbs_body->obb_digest()->hash().data(), ibbs_body->obb_digest()->len_hash());
                    if (obbHash.count('\x00') != obbHash.size()
                        && obbHash.count('\xFF') != obbHash.size()) {
                        PROTECTED_RANGE range = {};
                        range.Type = PROTECTED_RANGE_INTEL_BOOT_GUARD_OBB;
                        range.AlgorithmId = ibbs_body->obb_digest()->hash_algorithm_id();
                        range.Hash = obbHash;
                        ffsParser->protectedRanges.push_back(range);
                    }
                }
                
                // Check for non-empty IbbDigests
                if (ibbs_body->num_ibb_digests() == 0) {
                    bpInfo += UString("IBB Hashes: N/A\n");
                    msg(usprintf("%s: Boot Policy without IBB digests", __FUNCTION__), parent);
                }
                else {
                    bpInfo += UString("IBB Hashes:\n");
                    for (UINT16 i = 0; i < ibbs_body->num_ibb_digests(); i++) {
                        const auto & current_hash = ibbs_body->ibb_digests()->at(i);
                        bpInfo += hashTypeToUString(current_hash->hash_algorithm_id()) + ": ";
                        for (UINT16 j = 0; j < current_hash->len_hash(); j++) {
                            bpInfo += usprintf("%02X", (UINT8)current_hash->hash().data()[j]);
                        }
                        bpInfo += "\n";
                    }
                }
                
                // Check for non-empty IbbSegments
                if (ibbs_body->num_ibb_segments() == 0) {
                    bpInfo += UString("IBB Segments: N/A\n");
                    msg(usprintf("%s: Boot Policy without IBB segments", __FUNCTION__), parent);
                }
                else {
                    bpInfo += UString("IBB Segments:\n");
                    for (UINT8 i = 0; i < ibbs_body->num_ibb_segments(); i++) {
                        const auto & current_segment = ibbs_body->ibb_segments()->at(i);
                        
                        bpInfo += usprintf("Flags: %04Xh, Address: %08Xh, Size: %08Xh\n",
                                           current_segment->flags(),
                                           current_segment->base(),
                                           current_segment->size());
                        
                        if (current_segment->flags() == intel_acbp_v2_t::IBB_SEGMENT_TYPE_IBB
                            && current_segment->base() != 0xFFFFFFFF && current_segment->size() != 0 && current_segment->size() != 0xFFFFFFFF) {
                            PROTECTED_RANGE range = {};
                            range.Offset = current_segment->base();
                            range.Size =current_segment->size();
                            range.Type = PROTECTED_RANGE_INTEL_BOOT_GUARD_IBB;
                            range.AlgorithmId = TCG_HASH_ALGORITHM_ID_SHA256;
                            ffsParser->protectedRanges.push_back(range);
                        }
                    }
                }
            }
            // PMDA
            else if (element->_is_null_pmda_body() == false) {
                const intel_acbp_v2_t::pmda_body_t* pmda_body = element->pmda_body();
                
                // Valid Microsoft PMDA element found
                bpInfo += usprintf("TotalSize: %04Xh\n"
                                   "Version: %08Xh\n"
                                   "NumEntries: %08Xh\n",
                                   pmda_body->total_size(),
                                   pmda_body->version(),
                                   pmda_body->num_entries());
                
                if (pmda_body->num_entries() == 0) {
                    bpInfo += UString("PMDA Entries: N/A\n");
                }
                else {
                    bpInfo += UString("PMDA Entries:\n");
                    for (UINT32 i = 0; i < pmda_body->num_entries(); i++) {
                        const auto & current_entry = pmda_body->entries()->at(i);
                        
                        UINT64 entry_id = current_entry->entry_id();
                        const char* entry_id_bytes = (const char*)&entry_id;
                        
                        // Add element
                        bpInfo += usprintf("EntryId: '%c%c%c%c', Version: %04Xh, Address: %08Xh, Size: %08Xh\n",
                                           entry_id_bytes[0],
                                           entry_id_bytes[1],
                                           entry_id_bytes[2],
                                           entry_id_bytes[3],
                                           current_entry->version(),
                                           current_entry->base(),
                                           current_entry->size());
                        
                        // Add hash
                        bpInfo += hashTypeToUString(current_entry->hash()->hash_algorithm_id()) + ": ";
                        for (UINT16 j = 0; j < current_entry->hash()->len_hash(); j++) {
                            bpInfo += usprintf("%02X", (UINT8)current_entry->hash()->hash().data()[j]);
                        }
                        bpInfo += "\n";
                        
                        // Add protected range
                        if (current_entry->base() != 0xFFFFFFFF && current_entry->size() != 0 && current_entry->size() != 0xFFFFFFFF) {
                            PROTECTED_RANGE range = {};
                            range.Offset = current_entry->base();
                            range.Size = current_entry->size();
                            range.Type = PROTECTED_RANGE_VENDOR_HASH_MICROSOFT_PMDA;
                            range.AlgorithmId = current_entry->hash()->hash_algorithm_id();
                            range.Hash = UByteArray(current_entry->hash()->hash().data(), current_entry->hash()->hash().size());
                            ffsParser->protectedRanges.push_back(range);
                        }
                    }
                }
            }
            bpInfo += "\n";
        }
        
        // Add Key Signature
        const intel_acbp_v2_t::key_signature_t* key_signature = parsed.key_signature();
        bpInfo += usprintf("Boot Policy Key Signature:\n"
                           "Version: %02Xh\n"
                           "KeyId: %04Xh\n"
                           "SigScheme: %04Xh\n",
                           key_signature->version(),
                           key_signature->key_id(),
                           key_signature->sig_scheme());
                           
        // Add PubKey
        bpInfo += usprintf("Boot Policy Public Key Exponent: %Xh\n", key_signature->public_key()->exponent());
        bpInfo += usprintf("Boot Policy Public Key:");
        for (UINT16 i = 0; i < (UINT16)key_signature->public_key()->modulus().length(); i++) {
            if (i % 32 == 0) bpInfo += UString("\n");
            bpInfo += usprintf("%02X", (UINT8)key_signature->public_key()->modulus().at(i));
        }
        bpInfo += "\n";
        
        // Calculate and add PubKey hashes
        UINT8 hash[SHA384_HASH_SIZE];
        // SHA256
        sha256(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length() , hash);
        bpInfo += UString("Boot Policy Public Key Hash (SHA256): ");
        for (UINT8 i = 0; i < SHA256_HASH_SIZE; i++) {
            bpInfo += usprintf("%02X", hash[i]);
        }
        bpInfo += "\n";
        bgBpHashSha256 = UByteArray((const char*)hash, SHA256_HASH_SIZE);
        // SHA384
        sha384(key_signature->public_key()->modulus().data(), key_signature->public_key()->modulus().length() , hash);
        bpInfo += UString("Boot Policy Public Key Hash (SHA384): ");
        for (UINT8 i = 0; i < SHA384_HASH_SIZE; i++) {
            bpInfo += usprintf("%02X", hash[i]);
        }
        bpInfo += "\n";
        bgBpHashSha384 = UByteArray((const char*)hash, SHA384_HASH_SIZE);
        
        // Add Signature
        bpInfo += UString("Boot Policy Signature: ");
        for (UINT16 i = 0; i < (UINT16)key_signature->signature()->signature().length(); i++) {
            if (i % 32 == 0) bpInfo += UString("\n");
            bpInfo += usprintf("%02X", (UINT8)key_signature->signature()->signature().at(i));
        }
        bpInfo += "\n";
        
        securityInfo += bpInfo + "\n";
        bgBootPolicyFound = true;
        return U_SUCCESS;
    }
    catch (...) {
        msg(usprintf("%s: unable to parse Boot Policy", __FUNCTION__), parent);
        return U_INVALID_BOOT_GUARD_BOOT_POLICY;
    }
}
#endif // U_ENABLE_ME_PARSING_SUPPORT