#! /usr/bin/python3 ######################################################################## # Copyright (c) 2022 VMware, Inc. All rights reserved # VMware Confidential ######################################################################## """ Simple recursive-descent parser/printer for UEFI signature database variables such as db, dbx, dbDefault, etc., as well as dbx update files from https://uefi.org/revocationlistfile. Only a few of the possible signature type GUIDs are currently recognized; for the other types we just print the numeric GUID and hex signature data. See UEFI specification 2.10 section 32.4.1. """ import uuid import struct import subprocess from subprocess import Popen, PIPE, STDOUT from uefi import uefivar import sys import argparse description = \ '''Display UEFI signature list databases in human readable form.''' # UEFI variable GUIDs EFI_GLOBAL_VARIABLE_GUID = \ uuid.UUID("8BE4DF61-93CA-11d2-AA0D-00E098032B8C") EFI_IMAGE_SECURITY_DATABASE_GUID = \ uuid.UUID("d719b2cb-3d3a-4596-a3bc-dad00e67656f") # UEFI signature type GUIDs EFI_CERT_SHA256_GUID = \ uuid.UUID("c1c41626-504c-4092-aca9-41f936934328") EFI_CERT_X509_GUID = \ uuid.UUID("a5c059a1-94e4-4aa7-87b5-ab155c2bf072") EFI_CERT_EXTERNAL_MANAGEMENT_GUID = \ uuid.UUID("452e8ced-dfff-4b8c-ae01-5118862e682c") # Some known signature owner GUIDs knownSigOwners = { uuid.UUID("a3d5e95b-0a8f-4753-8735-445afb708f62"): "VMware, Inc.", uuid.UUID("77fa9abd-0359-4d32-bd60-28f4e78f784b"): "Microsoft Corporation", uuid.UUID("F5A96B31-DBA0-4faa-A42A-7A0C9832768E"): "Hewlett Packard Enterprise", uuid.UUID("2879c886-57ee-45cc-b126-f92f24f906b9"): "SUSE Software Solutions Germany GmbH", } sigListFmt = '16sIII' efiTimeFmt = 'HBBBBBBIhBB' # DB parser state is kept in these globals: v = b'' # bytes object being parsed i = 0 # current parse offset verbose = False def parseSigDB(db): """Initialize the parser and parse the given bytes object.""" global v, i v = db i = 0 parseSigLists() def parseDBXUpdateFile(fileName): """Initialize the parser and parse the given DBXUpdate.bin file.""" global v, i v = open(fileName, "rb").read() i = 0 parseEfiTime() print() skipAuthInfo() parseSigLists() ## begin internal parser functions ## def parseSigLists(): """Internal parser function. Parse out a sequence of signature lists from v starting at offset i. """ global v, i while i < len(v): parseSigList() print() def parseSigList(): """Internal parser function. Parse out one signature list from v starting at offset i. """ global v, i (sigType, sigListSize, sigHdrSize, sigSize) = \ struct.unpack_from(sigListFmt, v, i) sigType = uuid.UUID(bytes_le=sigType) sigListEnd = i + sigListSize i = i + struct.calcsize(sigListFmt) if sigHdrSize > 0: print("SignatureHeader", sigHdrSize, "bytes") i = i + sigHdrSize while i < sigListEnd: parseSig(sigType, sigSize) print() def parseSig(sigType, sigSize): """Internal parser function. Parse out one signature from v starting at offset i. """ global v, i, verbose sigOwner = uuid.UUID(bytes_le=v[i : i + 16]) sigData = v[i + 16 : i + sigSize] i = i + sigSize print("SignatureOwner: GUID", sigOwner, knownSigOwners.get(sigOwner, '')) print(sigSize - 16, "bytes of data") if sigType == EFI_CERT_SHA256_GUID: print("SignatureType: SHA-256 hash") print(sigData.hex()) elif sigType == EFI_CERT_X509_GUID: print("SignatureType: x509 certificate") p = Popen(("openssl", "x509", "-inform", "DER", "-text") + (() if verbose else ("-certopt", "no_pubkey,no_sigdump", "-noout")), stdin=PIPE, stdout=PIPE, stderr=STDOUT) sigText = p.communicate(sigData)[0].decode() print(sigText) elif sigType == EFI_CERT_EXTERNAL_MANAGEMENT_GUID: print("SignatureType: External management") else: print("SignatureType: GUID", sigType) print(sigData.hex()) def parseEfiTime(): """Internal parser function. Parse out a timestamp from v starting at offset i. """ global v, i efiTime = struct.unpack_from(efiTimeFmt, v, i) i = i + struct.calcsize(efiTimeFmt) print("%u/%02u/%02u %02u:%02u:%02u pad1=%u ns=%u tz=%u dl=%u pad2=%u" % efiTime) def skipAuthInfo(): """Internal parser function. Skip over a certificate from v starting at offset i. """ global v, i # Get length to skip from WIN_CERTIFICATE.dwLength field. i = i + struct.unpack_from('I', v, i)[0] ## end internal parser functions ## def parseSigDBVar(varName): """Parse the specified UEFI secure boot signature list variable.""" parseSigDB(uefivar.get(varName)[4:]) def parseAllVars(): """Parse all standard UEFI secure boot signature list variables.""" vars = uefivar.list() for v in ("db", "dbx", "dbt", "dbr"): var = v + "-" + str(EFI_IMAGE_SECURITY_DATABASE_GUID) print("-----", v, "-----") if var in vars: parseSigDBVar(var) else: print("Nonexistent") print() var = v + "Default-" + str(EFI_GLOBAL_VARIABLE_GUID) print("-----", v + "Default", "-----") if var in vars: parseSigDBVar(var) else: print("Nonexistent") print() def makeArgParser(): """Construct command line argument parser. """ ap = argparse.ArgumentParser(description=description) ap.add_argument('-v', '--verbose', action='store_true', help='Include additional details.') group = ap.add_mutually_exclusive_group() group.add_argument('-u', '--uefi-var', help= '''Parse one UEFI secure boot variable (db, dbx, etc.). The GUID will be guessed if omitted.''') group.add_argument('-a', '--all', action='store_true', help= 'Parse all UEFI secure boot variables.') group.add_argument('-f', '--filename', help= '''Parse a DBXUpdate.bin file, downloadable from https://uefi.org/revocationlistfile.''') return ap if __name__ == "__main__": ap = makeArgParser() args = ap.parse_args() verbose = args.verbose if args.uefi_var is not None: var = args.uefi_var if '-' not in var: if 'Default' in var: guid = str(EFI_GLOBAL_VARIABLE_GUID) else: guid = str(EFI_IMAGE_SECURITY_DATABASE_GUID) var = var + '-' + guid parseSigDBVar(var) elif args.all: parseAllVars() elif args.filename is not None: parseDBXUpdateFile(args.filename) else: ap.print_help() exit(2)