#!/usr/bin/env python3
# tpm2_listpersistent -- show persistent objects stored in TPM 2.0

import argparse
import os
from pprint import pprint
import yaml
import subprocess

def yrun(*cmd):
    res = subprocess.run(cmd, stdout=subprocess.PIPE,
                              check=True)
    buf = res.stdout.replace(b'value: ""!!', b'value: "!!')
    return yaml.safe_load(buf)

def findrange(ranges, key):
    for (start, end, *rest) in ranges:
        if start <= key <= end:
            return rest

# https://trustedcomputinggroup.org/wp-content/uploads/RegistryOfReservedTPM2HandlesAndLocalities_v1p1_pub.pdf
handle_ranges = [
    (0x01000000, 0x013FFFFF, "Free use: TPM OEM"),
    (0x01400000, 0x017FFFFF, "Free use: platform"),
    (0x01800000, 0x01BFFFFF, "Free use: owner"),

    (0x01C07F00, 0x01C07FFF, "TCG: EK policies"),
    (0x01C00000, 0x01C07FFF, "TCG: Endorsement certificates"),

    (0x01C08000, 0x01C0FFFF, "TCG: Platform certificates"),

    (0x01C10100, 0x01C1013F, "TCG: component OEM - Intel"),
    (0x01C10140, 0x01C1017F, "TCG: component OEM - Cisco"),
    (0x01C10180, 0x01C101BF, "TCG: component OEM - IBM"),
    (0x01C101C0, 0x01C101FF, "TCG: component OEM - Microsoft"),
    (0x01C10200, 0x01C1023F, "TCG: component OEM - AMD"),
    (0x01C10240, 0x01C1027F, "TCG: component OEM - HP"),
    (0x01C10280, 0x01C102BF, "TCG: component OEM - HPE"),
    (0x01C10000, 0x01C1FFFF, "TCG: component OEM"),

    (0x01C20000, 0x01C2007F, "TCG: TPM OEM - Infineon"),
    (0x01C20000, 0x01C2FFFF, "TCG: TPM OEM"),

    (0x01C30100, 0x01C3013F, "TCG: platform OEM - Intel"),
    (0x01C30140, 0x01C3017F, "TCG: platform OEM - Cisco"),
    (0x01C30180, 0x01C301BF, "TCG: platform OEM - HPE"),
    (0x01C301C0, 0x01C301FF, "TCG: platform OEM - IBM"),
    (0x01C30200, 0x01C3023F, "TCG: platform OEM - Juniper"),
    (0x01C30240, 0x01C3027F, "TCG: platform OEM - GE"),
    (0x01C30280, 0x01C302BF, "TCG: platform OEM - Raytheon"),
    (0x01C302C0, 0x01C302FF, "TCG: platform OEM - HP"),
    (0x01C30300, 0x01C3033F, "TCG: platform OEM - AMD"),
    (0x01C30340, 0x01C3037F, "TCG: platform OEM - Qualcomm"),
    (0x01C30000, 0x01C3FFFF, "TCG: platform OEM"),

    (0x01C40000, 0x01C4000F, "TCG: PC-Client: GPIO"),
    (0x01C40100, 0x01C401FF, "TCG: PC-Client: EPI"),
    (0x01C40000, 0x01C4FFFF, "TCG: PC-Client workgroup"),
    (0x01C50000, 0x01C5FFFF, "TCG: Server workgroup"),
    (0x01C60000, 0x01C6FFFF, "TCG: Virtualized workgroup"),
    (0x01C70000, 0x01C7FFFF, "TCG: MP workgroup"),
    (0x01C80000, 0x01C8FFFF, "TCG: Embedded workgroup"),
    (0x01C90000, 0x01C9FFFF, "TCG: Infrastructure workgroup"),
    (0x01D00000, 0x01D0FFFF, "TCG: TPM workgroup"),
    (0x01D10000, 0x01D100FF, "External: FIDO"),
    (0x01D10000, 0x01D1FFFF, "External standards"),
    (0x01C40000, 0x01FFFFFF, "TCG: other specific usages"),

    (0x81000000, 0x810000FF, "Storage Primary Key"),
    (0x81000000, 0x8100FFFF, "Storage hierarchy"),
    (0x81010000, 0x810100FF, "Endorsement Primary Key"),
    (0x81020000, 0x810200FF, "Endorsement Infrastructure WG"),
    (0x81010000, 0x8103FFFF, "Endorsement hierarchy"),
    (0x81800000, 0x818000FF, "Platform Primary Key"),
    (0x81800000, 0x81FFFFFF, "Platform hierarchy"),
]

# Various well-known handles
# [fishilico]: https://github.com/fishilico/shared/blob/master/python/crypto/tpm_ea_policy.py,
#              https://raw.githubusercontent.com/fishilico/home-files/refs/heads/master/bin/tpm-show
handle_names = {
	### NV indices

	# per [fishilico]
	0x01800001: "Intel TXT - LCP Platform Supplier (old)",
	0x01800003: "Intel TXT - Launch Auxiliary (old)",
	0x01800004: "Intel TXT - SGX Software Version Number (old)",
	# per TCG
	0x01C00002: "EK Certificate (RSA) (TPM TCG)",
	0x01C0000A: "EK Certificate (EC) (TPM TCG)",
	0x01C00012: "EK Certificate (RSA-2048) (TPM TCG)",
	0x01C00014: "EK Certificate (NIST P-256) (TPM TCG)",
	0x01C00016: "EK Certificate (NIST P-384) (TPM TCG)",
	0x01C00018: "EK Certificate (NIST P-521) (TPM TCG)",
	0x01C0001A: "EK Certificate (SM2 P-256) (TPM TCG)",
	0x01C0001C: "EK Certificate (RSA-3072) (TPM TCG)",
	0x01C0001E: "EK Certificate (RSA-4096) (TPM TCG)",
	0x01C90000: "IDevID Certificate (TPM TCG)",
	# per [fishilico]
	0x01C10102: "Intel TXT - Launch Auxiliary",
	0x01C10103: "Intel TXT - LCP Platform Supplier",
	0x01C10104: "Intel TXT - SGX Software Version Number",
	# per [fishilico],
	# > https://learn.microsoft.com/en-us/windows/security/threat-protection/windows-defender-system-guard/how-hardware-based-root-of-trust-helps-protect-windows
    # > states that "Platform firmware must set up a TPM NV index for use by the OS with: Handle: 0x01C101C0"
    0x01C101C0: "Windows Defender System Guard DRTM thing?",

	### Object handles

	0x81000001: "Storage Root Key (Microsoft, TPM TCG)",
	# Generated by Windows; [fishilico] suggests it might be an AIK
	0x81000002: "Windows AIK? (Microsoft)",
	# https://docs.trustauthority.intel.com/main/articles/tpm-ak-provision.html
	0x81000800: "Intel Trust Authority Attestation Key",
	0x81000801: "Intel Trust Authority Endorsement Key",
	0x81010001: "Endorsement Key (RSA) (Microsoft, TPM TCG)",
	0x81020000: "IDevID Key (TPM TCG)",
	# Seen on my Lenovo ThinkPad,
	# named as per https://github.com/fishilico/shared/blob/master/python/crypto/tpm_ea_policy.py
	0x81800000: "Platform key 0?",
	0x81800001: "Platform key 1?",
}

# Well-known policy digests (entire table copied from [fishilico])
# From https://github.com/fishilico/shared/blob/master/python/crypto/tpm_ea_policy.py
policy_names = {
    "8FCD2169AB92694E0C633F1AB772842B8241BBC20288981FC7AC1EDDC1FDDB0E": "PolicyAuthValue()",
    "1D2DC485E177DDD0A40A344913CEEB420CAA093C42587D2E1B132B157CCB5DB0": "PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)",
    "DDEE6AF14BF3C4E8127CED87BCF9A57E1C0C8DDB5E67735C8505F96F07B8DBB8": "PolicyLocality(ONE)",
    "07039B45BAF2CC169B0D84AF7C53FD1622B033DF0A5DCDA66360AA99E54947CD": "PolicyLocality(THREE, FOUR)",
    "3C326323670E28AD37BD57F63B4CC34D26AB205EF22F275C58D47FAB2485466E": "PolicyNvWritten(NO)",
    "F7887D158AE8D38BE0AC5319F37A9E07618BF54885453C7A54DDB0C6A6193BEB": "PolicyNvWritten(YES)",
    "84B506C91F205E06ABD6F83F269D8D8011D495E09214A40FE32B4660301DDA09": "PolicyPCR(0,1,2,3 ZEROS)",
    "0D7C6747B1B9FACBBA03492097AA9D5AF792E5EFC07346E05F9DAA8B3D9E13B5": "PolicyPhysicalPresence()",
    "0D84F55DAF6E43AC97966E62C9BB989D3397777D25C5F749868055D65394F952": "PolicySecret(RH_OWNER)",
    "A0CAB3762662675A14347A87504584A08E1002525D91371C3289224BEA3FF4AF": "PolicySecret(RH_LOCKOUT)",
    "837197674484B3F81A90CC8D46A5D724FD52D76E06520B64F2A1DA1B331469AA": "PolicySecret(RH_ENDORSEMENT)",
    "C8B1292EFF2CE7A3FA0FB1AED9AD254FB03FC01C9ABC2DD1985161BA6811BDC7": "PolicySecret(RH_PLATFORM)",
    "0C8DF0CF0169C38828C8FA4C0FF37A548C23C041AEECD2A12CA740D501D620B7": "PolicySecret(RH_LOCKOUT) OR PolicyNvWritten(YES)",
    "3767E2EDD43FF45A3A7E1EAEFCEF78643DCA964632E7AAD82C673A30D8633FDE": "PolicyAuthorizeNV(0x01c07f01)",
    "CA3D0A99A2B93906F7A3342414EFCFB3A385D44CD1FD459089D19B5071C0B7A0": "PolicySecret(RH_ENDORSEMENT) OR PolicyAuthorizeNV(0x01c07f01)",
    "06C7D805AD3BEC1106502A44C6B2E3B36D157750E8EFCA1FFF998C874A7664C5": "PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)",
    "EF9A26FC22D1AE8CECFF59E9481AC1EC533DBE228BEC6D17930F4CB2CC5B9724": "PolicyLocality(THREE, FOUR) OR (PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))",
    "DFFDB6C8EAFCBE691E358882B18703121EAB40DE2386F7A8E7B4A06591E1F0EE": "(PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_Write)) OR (PolicyLocality(THREE, FOUR) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))",
    "CB45C81FF34BCF0AFB9E1A8029FA231C8727303C0922DCCE684BE3DB817C20E1": "PolicyAuthorize(MSFT_DRTM_AUTH_BLOB_SigningKey) OR (PolicyAuthorize(MSFT_DRTM_AUTH_BLOB_SigningKey) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial))",
    "98CBABA62E682C148A202ACAD533D9D63F85A68392FE1B66648D1C491AEF1B3A": "PolicyAuthValue() AND PolicyCommandCode(TPM2_CC_ObjectChangeAuth)",
    "363AC945B6457C47C31F3355DBA0DB27DE8DB213D6250C6BF79685003F9FE7AB": "PolicyAuthValue() AND PolicyCommandCode(TPM2_CC_NV_ChangeAuth)",
    "A13EB52D274DEB81431F5ADC2E40F7E7C4094ABBA5D325DA741E7AFC4117901F": "PolicyCommandCode(TPM2_CC_NV_Read) OR PolicyNvWritten(NO)",
    "3355408F64A7EBE10AC90DAB8A4405EEF7C8F164EAA9034220C961EDF1DBB680": "PolicyCommandCode(TPM2_CC_NV_Write) AND PolicyAuthValue()",
    "C1EF6962E6E15B2FDA5026EFCA791AE9272BD87338C6FCACDF2CCCA45D03D7BE": "PolicyCommandCode(TPM2_CC_NV_Read) OR (PolicyCommandCode(TPM2_CC_NV_Write) AND PolicyAuthValue())",
    "310A0EB2A2C3EBD96C39D954D2865A80C7925AB8996C5D73D0BB723756EC42BF": "PolicyCounterTimer(safe=YES)",
    "7F48CCEB9FAE31E1662D7F8306FDD1C4F81D2B8D3B0E9D82FDEC42949AD5257E": "PolicyCounterTimer(time<60000)",
    "47A3A4E8C7567B07E33AAD03B2ADCA52B02C2F96CD0EA41073D67F3E3F80EAF8": "PolicyCounterTimer(clock<60000)",
    "CD397212FEC5F9C77C0F9EFF5D6878D7D5D43FE0F0EF4BFD9C9EDF2ADC7AB30F": "PolicyCounterTimer(resets<=42)",
    "12D20CA971BF0EAAFAE3C58F4666013CAB78654330EF8C95A3E7FC9D87C9658D": "PolicyCounterTimer(restarts<=42)",
    "E529F5D6112872954E8ED6605117B757E237C6E19513A949FEE1F204C458023A": "PolicyCommandCode(TPM2_CC_ObjectChangeAuth) AND PolicyAuthValue()",
    "AF2CA569699C436A21006F1CB8A2756C98BC1C765A3559C5FE1C3F5E7228A7E7": "PolicyCommandCode(TPM2_CC_Certify) AND PolicyAuthValue()",
    "C413A847B11112B1CBDDD4ECA4DAAA15A1852C1C3BBA57461D257605F3D5AF53": "PolicyCommandCode(TPM2_CC_ActivateCredential) AND PolicyAuthValue()",
    "048E9A3ACE08583F79F344FF785BBEA9F07AC7FA3325B3D49A21DD5194C65850": "PolicyCommandCode(TPM2_CC_Certify)",
    "9DFFCBF36C383AE699FB9868DC6DCB89D7153884BE2803922C124158BFAD22AE": "PolicyAuthValue() OR (PolicyCommandCode(TPM2_CC_ObjectChangeAuth) AND PolicyAuthValue()) OR (PolicyCommandCode(TPM2_CC_Certify) AND PolicyAuthValue()) OR (PolicyCommandCode(TPM2_CC_ActivateCredential) AND PolicyAuthValue()) OR PolicyCommandCode(TPM2_CC_Certify)",
    "C4DFABCEDA8DE836C95661952892B1DEF7203AFB46FEFEC43FFCFC93BE540730": "PolicyCommandCode(TPM2_CC_Clear)",
    "FCA06036F1972D4D6069C625455D5C9F0D413F6C6B3BF5FD85314809DEA99E8E": "PolicyNV(windows_nvbits_0x01880001: bit 0 clear)",
    "9711091A3AA56173F59B73B6D27D446FEA52FD6FCEFBC51BFA9271B09A206A87": "PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set)",
    "268B6BAC0DEBB1E5A1659D35F0D28421C9F62B8EA1A3326E9B71DD5BA295214A": "PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set) AND PolicyCpHash(TPM2_CC_DictionaryAttackParameters(RH_LOCKOUT,32,7200,86400))",
    "89D26CF200E9169047CFCB7A597B23E647F336A9A45C2AA09068CC370D2606F5": "PolicyCommandCode(TPM2_CC_Clear) OR (PolicyNV(windows_nvbits_0x01880001: bit 0 clear->set) AND PolicyCpHash(TPM2_CC_DictionaryAttackParameters(RH_LOCKOUT,32,7200,86400)))",
    "21784FE1FC7D5496C488E4FA33DD95F82FCE48F440A75D882F8C8A44BC12018A": "PolicyNV(windows_nvbits_0x01880001: bit 1 clear)",
    "983D228D2827649DA8E461587538D741991AEF5CD1B5CEAE869242F537535CE1": "PolicyNvWritten(YES) AND PolicyLocality(TWO, THREE, FOUR)",
    "FB204F312ABAAAC0980CE9FBBF5260788C7C7B6D4B68B6CE0845750C761511ED": "PolicyNV(windows_nvbits_0x01880001: bit 1 clear) OR (PolicyNvWritten(YES) AND PolicyLocality(TWO, THREE, FOUR))",
    "093CEB41181D47808862D7946268EE6A17A10E3D1B79B32351BC56E4BEACEFF0": "PolicyPCR(0 is ZEROS)",
    "4B44FC4192DB5AD7167E0135708FD374890A06BFB56317DF01F24F2226542A3F": "PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is ZEROS)",
    "CB5C8014E27A5F7586AAE42DB4F9776A977BCBC952CA61E33609DA2B2C329418": "PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(00,01,00))",
    "E6EF4F0296AC3EF0F53906480985B1BE8058E0E517E5F74A5B8A415EFE339D87": "PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(01,01,00))",
    "44447900CBB83F5B15765650EF96980A2B966EA909044A01B85FA54A96FC5984": "(PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is ZEROS)) OR (PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(00,01,00))) OR (PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial) AND PolicyPCR(0 is extended from SHA1(01,01,00)))",
    "22030B7E0BB1F9D50657571EE2F7FCE1EB91990C8B8AE977FCB3F158B03EBA96": "zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4",
    "B75CE1946F78DF8BAA426918DB09318017E6B38D048C954E05C2C4F34BD44060": "(zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)",
    "C001C8000210D0FAA4F4F4F8A78EF4F8264E6F8555340D2F04180F8CF110FFDD": "((zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR (zeros OR 771CEB9D52438BB72009A316750DEA301A6A62ED3835A18ED9AF89F9EF36EBE4) OR PolicyNvWritten(NO)",
    "A4469AF0287113E5D5EB95287D94BAB42BD166A42DFA89FE91866E7034420805": "FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)",
    "9F970E88340B836B768E682DB1BE76EC3F4284282FDDF64B05ACF8FD2699A71C": "(FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR FD516FA72051D00FA032B98DF1E2110A20C2766E49B5FB417621D5572601743A OR PolicyNvWritten(NO)",
    "A8652EA8A9787937BC33C4164B58F07F8378BBAE396875293A626048B5955C4C": "061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)",
    "6CE3379D380001BD45B1F6B3DFFFF39A03CA61BFFB13FEAFC2F257196DF49FE4": "(061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 AND PolicyCommandCode(TPM2_CC_NV_UndefineSpaceSpecial)) OR 061408869C564D49F631C981EA9C303AA0B126671532CBA86ABBEDC73B8A5692 OR PolicyNvWritten(NO)",
    "1169A46A813A8CCDD0F3066785207BB9B67AFD3A6CD6DFE5C5AEE120867A96DF": "Unknown (used by Intel TXT for LCP Platform Supplier and SGX SVN)",

    "8BBF2266537C171CB56E403C4DC1D4B64F432611DC386E6F532050C3278C930E143E8BB1133824CCB431053871C6DB53": "PolicySecret_SHA384(RH_ENDORSEMENT)",
    "B26E7D28D11A50BC53D882BCF5FD3A1A074148BB35D3B4E4CB1C0AD9BDE419CACB47BA09699646150F9FC000F3F80E12": "PolicySecret_SHA384(RH_ENDORSEMENT) OR PolicyAuthorizeNV_SHA384(0x01c07f02)",
    "D6032CE61F2FB3C240EB3CF6A33237EF2B6A16F4293C22B455E261CFFD217AD5B4947C2D73E63005EED2DC2B3593D165": "PolicyAuthorizeNV_SHA384(0x01c07f02)",

    "1E3B76502C8A1425AA0B7B3FC646A1B0FAE063B03B5368F9C4CDDECAFF0891DD682BAC1A85D4D832B781EA451915DE5FC5BF0DC4A1917CD42FA041E3F998E0EE": "PolicySecret_SHA512(RH_ENDORSEMENT)",
    "B8221CA69E8550A4914DE3FAA6A18C072CC01208073A928D5D66D59EF79E49A429C41A6B269571D57EDB25FBDB1838425608B413CD616A5F6DB5B6071AF99BEA": "PolicySecret_SHA512(RH_ENDORSEMENT) OR PolicyAuthorizeNV_SHA512(0x01c07f03)",
    "589EE1E146544716E8DEAFE6DB247B01B81E9F9C7DD16B814AA159138749105FBA5388DD1DEA702F35240C184933121E2C61B8F50D3EF91393A49A38C3F73FC8": "PolicyAuthorizeNV_SHA512(0x01c07f03)",
}

parser = argparse.ArgumentParser()
parser.add_argument("-N", "--nv", action="store_true",
                    help="show NV indices")
parser.add_argument("-T", "--tcti",
                    help="specify the TPM transport")
args = parser.parse_args()

if args.tcti:
    os.environ["TPM2TOOLS_TCTI"] = args.tcti

if not args.nv:
    info = yrun("tpm2_getcap", "properties-fixed")
    capacity = info["TPM2_PT_HR_PERSISTENT_MIN"]["raw"]
    print("TPM has capacity for at least %d persistent objects." % capacity)
    print()

    handles = yrun("tpm2_getcap", "handles-persistent") or []
    if not handles:
        print("No persistent objects.")
    for i, handle in enumerate(handles):
        if i:
            print()
        if hdesc := handle_names.get(handle):
            hdesc = "\033[1m%s\033[m" % hdesc
        else:
            hdesc = "\033[3m%s\033[m" % "unknown"
        if htype := findrange(handle_ranges, handle):
            hdesc += " \033[2m(%s)\033[m" % htype[0]
        print("\033[32mObject \033[1m0x%08x:\033[m %s" % (handle, hdesc))
        info = yrun("tpm2_readpublic", "-c", hex(handle))
        #pprint(info)
        print("\tAttributes:", info["attributes"]["value"])
        print("\tType:", info["type"]["value"].upper())
        if info.get("curve-id") and info["curve-id"]["value"]:
            print("\tCurve:", info["curve-id"]["value"].upper())
        if info.get("sym-alg") and info["sym-alg"]["value"]:
            print("\tSym alg:", info["sym-alg"]["value"].upper(),
                                info["sym-keybits"],
                                info["sym-mode"]["value"].upper())
        if info.get("scheme") and info["scheme"]["value"]:
            print("\tSig alg:", info["scheme"]["value"].upper(),
                                info["scheme-halg"]["value"].upper())
        if info.get("kdfa-alg") and info["kdfa-alg"]["value"]:
            print("\tKDF alg:", info["kdfa-alg"]["value"].upper(),
                                info["kdfa-halg"]["value"].upper())

if args.nv:
    handles = yrun("tpm2_getcap", "handles-nv-index") or []
    if not handles:
        print("No NV indices.")
    for i, handle in enumerate(handles):
        if i:
            print()
        info = yrun("tpm2_nvreadpublic", hex(handle))
        info = info[handle]
        #pprint(info)
        if hdesc := handle_names.get(handle):
            hdesc = "\033[1m%s\033[m" % hdesc
        else:
            hdesc = "\033[3m%s\033[m" % "unknown"
        if htype := findrange(handle_ranges, handle):
            hdesc += " \033[2m(%s)\033[m" % htype[0]
        if "platformcreate" in info["attributes"]["friendly"].split("|"):
            print("\033[95m\033[1m0x%08x\033[22m (platform):\033[m %s" % (handle, hdesc))
        else:
            print("\033[96m\033[1m0x%08x\033[22m (user):\033[m %s" % (handle, hdesc))
        print("\tAttributes:", info["attributes"]["friendly"])
        if policy_digest := info.get("authorization policy"):
            print("\tPolicy:", policy_names.get(policy_digest, policy_digest))
