2026-01-14

authval.py - A python script to validate Secure Boot signed auth variables

Since I had the displeasure of fighting broken/quirky/limited UEFI Firmware implementations, as part of Mosby, and had to double check whether a signed Secure Boot authvar should be accepted as valid.

Usage ./authval.py <var.auth> where var.auth should start with PK, KEK, DB or DBX according to the variable you are trying to check (so that we automatically fill GUID and attributes for the signed payload). Also note that APPEND is assumed for all variables, except PK.

#!/bin/env python3
# authval.pl - A Python script to validate Secure Boot signed auth variables
# Copyright 2026 Pete Batard <pete@akeo.ie> - GPL v3 or later

import re
import argparse
import struct
import uuid
from datetime import datetime, timezone

from cryptography import x509
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from asn1crypto import cms, x509 as asn1_x509

def guid_to_bytes(g):
    """Convert GUID string to EFI little-endian byte order"""
    u = uuid.UUID(g)
    return struct.pack("<IHH8s", u.time_low, u.time_mid, u.time_hi_version, u.bytes[8:])

def utf16le_no_null(s):
    return s.encode("utf-16le")

def parse_authentication_2(data):
    efi_time = data[:16]
    cert_hdr = data[16:16+24]

    length, revision, cert_type = struct.unpack("<IHH", cert_hdr[:8])
    cert_guid = uuid.UUID(bytes_le = cert_hdr[8:24])

    cert_data = data[16+24:16+length]
    payload_offset = 16 + length
    payload = data[payload_offset:]

    return efi_time, cert_data, payload

def build_signed_data(variable_name, vendor_guid, attributes, efi_time, variable_payload):
    buf = b""
    buf += utf16le_no_null(variable_name)
    buf += guid_to_bytes(vendor_guid)
    buf += struct.pack("<I", attributes)
    buf += efi_time
    buf += variable_payload
    return buf

def load_pkcs7_signed_data(pkcs7_der):
    try:
        ci = cms.ContentInfo.load(pkcs7_der)
        if ci['content_type'].native != 'signed_data':
            print(f"ERROR: PKCS#7 ContentType is set to '{ci['content_type'].native}'. UEFI specs requires 'signed_data'.")
            return None
        else:
            print("WARNING: PKCS#7 ContentType is set, but a longstanding EDK2 bug may lead some platforms to reject it!")
            print("For more on this, see https://github.com/tianocore/edk2/commit/37d3eb026a766b2405daae47e02094c2ec248646.")
        return ci['content']
    except Exception:
        # Bare content. *NOT* actually specs-compliant (at least for UEFI v2.3.1), but due to
        # an implementation bug in EDK2's VerifyTimeBasedPayload(), became de-facto standard,
        # especially as Microsoft, who did notice the bug, but never bothered to report it to
        # the EDK2, *like the good UEFI Secure Boot stewards they are*, dropped ContentType
        # from all the DBX updates they published...
        sd = cms.SignedData.load(pkcs7_der)
        # Wrap it to normalize handling
        ci = cms.ContentInfo({
            'content_type': 'signed_data',
            'content': sd
        })
        return ci['content']

def validate_uefi_variable(variable_blob, variable_name, vendor_guid, attributes):
    efi_time, pkcs7, variable_payload = parse_authentication_2(variable_blob)

    signed_data = build_signed_data(variable_name, vendor_guid, attributes, efi_time, variable_payload)

    try:
        signed_data_obj = load_pkcs7_signed_data(pkcs7)
        signer = signed_data_obj['signer_infos'][0]
    except Exception:
        return
    # Extract signer certificate from embedded certs
    certs = [
        cert.chosen for cert in signed_data_obj['certificates']
        if isinstance(cert.chosen, asn1_x509.Certificate)
    ]

    # Match signer by serial number
    serial = signer['sid'].native['serial_number']
    signer_cert = next(c for c in certs if c.serial_number == serial)

    # Load with cryptography
    cert = x509.load_der_x509_certificate(signer_cert.dump())
    pubkey = cert.public_key()

    digest_algo = signer['digest_algorithm']['algorithm'].native
    msg = "ERROR: Signature is INVALID!"
    if digest_algo == 'sha256':
        hash_alg = hashes.SHA256()
        signature = signer['signature'].native
        try:
            pubkey.verify(signature, signed_data, padding.PKCS1v15(), hash_alg)
            msg = "Signature is valid"
        except Exception:
            pass
    else:
        msg = f"Signature uses invalid algorithm: {digest_algo}"

    print("Signer cert subject:", cert.subject)
    print("Signer cert issuer :", cert.issuer)
    print("Signer serial num  :", cert.serial_number)
    print(msg)

if __name__ == "__main__":
    ap = argparse.ArgumentParser()
    ap.add_argument("file", help="signed authvar")
    args = vars(ap.parse_args())

    if re.search('PK', args['file'], re.IGNORECASE):
        var_guid = "8be4df61-93ca-11d2-aa0d-00e098032b8c"
        var_name = "PK"
        var_attr = 0x27 # NV_BS_RT_AT
    elif re.search('KEK', args['file'], re.IGNORECASE):
        var_guid = "8be4df61-93ca-11d2-aa0d-00e098032b8c"
        var_name = "KEK"
        var_attr = 0x67 # NV_BS_RT_AT_AP
    elif re.search('DBX', args['file'], re.IGNORECASE):
        var_guid = "d719b2cb-3d3a-4596-a3bc-dad00e67656f"
        var_name = "dbx"
        var_attr = 0x67 # NV_BS_RT_AT_AP
    elif re.search('DB', args['file'], re.IGNORECASE):
        var_guid = "d719b2cb-3d3a-4596-a3bc-dad00e67656f"
        var_name = "db"
        var_attr = 0x67 # NV_BS_RT_AT_AP
    else:
        print(f"'{args['file']}' is either missing a Secure Boot variable identifier (PK, KEK, DB, DBX)")
        print(f"in its name, or is not one of the Secure Boot variables we support for validation.")
        sys.exit(1)

    with open(args['file'], "rb") as f:
        variable_blob = f.read()

    print(f"Detected '{var_name}' - Using GUID: {var_guid} and ATTRS: {var_attr:#0x}")

    validate_uefi_variable(
        variable_blob=variable_blob,
        variable_name=var_name,
        vendor_guid=var_guid,
        attributes=var_attr
      )