mirror of
https://github.com/flatcar/scripts.git
synced 2026-05-04 19:56:32 +02:00
build_library: pcr: Add all command and print all hashes
This commit is contained in:
parent
3ca5bde325
commit
94a97299b8
@ -132,6 +132,35 @@ EV_EFI_ACTION_BOOT = "Calling EFI Application from Boot Option"
|
||||
EV_SEPARATOR_DATA = b"\x00\x00\x00\x00"
|
||||
|
||||
|
||||
def compute_pcr4_hashes(shim_path, grub_path, kernel_path=None, hash_algo='sha256'):
|
||||
"""Compute PCR 4 and return (final_hex, entries).
|
||||
|
||||
Returns:
|
||||
(pcr_hex, entries) where entries is a list of (description, digest_hex)
|
||||
tuples representing each measurement extended into PCR 4.
|
||||
"""
|
||||
entries = []
|
||||
|
||||
action_digest = hash_bytes(EV_EFI_ACTION_BOOT.encode('utf-8'), hash_algo)
|
||||
entries.append(('EV_EFI_ACTION: ' + EV_EFI_ACTION_BOOT, action_digest))
|
||||
|
||||
separator_digest = hash_bytes(EV_SEPARATOR_DATA, hash_algo)
|
||||
entries.append(('EV_SEPARATOR', separator_digest))
|
||||
|
||||
binaries = [shim_path, grub_path]
|
||||
if kernel_path:
|
||||
binaries.append(kernel_path)
|
||||
for path in binaries:
|
||||
digest = pe_authenticode_hash(path, hash_algo)
|
||||
entries.append((path, digest))
|
||||
|
||||
pcr = pcr_init(hash_algo)
|
||||
for _, digest in entries:
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex(), entries
|
||||
|
||||
|
||||
def compute_pcr4(shim_path, grub_path, kernel_path=None, hash_algo='sha256'):
|
||||
"""Compute PCR 4 from the EFI boot chain binaries.
|
||||
|
||||
@ -147,30 +176,36 @@ def compute_pcr4(shim_path, grub_path, kernel_path=None, hash_algo='sha256'):
|
||||
shim verification, and the kernel is NOT measured into PCR 4. Pass
|
||||
kernel_path=None to model this case.
|
||||
"""
|
||||
pcr = pcr_init(hash_algo)
|
||||
|
||||
# Firmware events
|
||||
action_digest = hash_bytes(EV_EFI_ACTION_BOOT.encode('utf-8'), hash_algo)
|
||||
pcr = pcr_extend(pcr, action_digest, hash_algo)
|
||||
|
||||
separator_digest = hash_bytes(EV_SEPARATOR_DATA, hash_algo)
|
||||
pcr = pcr_extend(pcr, separator_digest, hash_algo)
|
||||
|
||||
# EFI application measurements (PE Authenticode hashes)
|
||||
binaries = [shim_path, grub_path]
|
||||
if kernel_path:
|
||||
binaries.append(kernel_path)
|
||||
for path in binaries:
|
||||
digest = pe_authenticode_hash(path, hash_algo)
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex()
|
||||
return compute_pcr4_hashes(shim_path, grub_path, kernel_path, hash_algo)[0]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# PCR 8: GRUB commands and kernel command line
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def compute_pcr8_hashes(commands, hash_algo='sha256'):
|
||||
"""Compute PCR 8 and return (final_hex, entries).
|
||||
|
||||
Returns:
|
||||
(pcr_hex, entries) where entries is a list of (description, digest_hex)
|
||||
tuples for each measured command.
|
||||
"""
|
||||
entries = []
|
||||
for cmd in commands:
|
||||
for prefix in ("grub_cmd: ", "kernel_cmdline: "):
|
||||
if cmd.startswith(prefix):
|
||||
cmd = cmd[len(prefix):]
|
||||
break
|
||||
digest = hash_string(cmd, hash_algo)
|
||||
entries.append((cmd, digest))
|
||||
|
||||
pcr = pcr_init(hash_algo)
|
||||
for _, digest in entries:
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex(), entries
|
||||
|
||||
|
||||
def compute_pcr8(commands, hash_algo='sha256'):
|
||||
"""Compute PCR 8 from a list of GRUB command strings.
|
||||
|
||||
@ -185,18 +220,7 @@ def compute_pcr8(commands, hash_algo='sha256'):
|
||||
Lines starting with "grub_cmd: " or "kernel_cmdline: " will
|
||||
have the prefix stripped automatically for convenience.
|
||||
"""
|
||||
pcr = pcr_init(hash_algo)
|
||||
|
||||
for cmd in commands:
|
||||
# Strip common prefixes if present (convenience for eventlog copy-paste)
|
||||
for prefix in ("grub_cmd: ", "kernel_cmdline: "):
|
||||
if cmd.startswith(prefix):
|
||||
cmd = cmd[len(prefix):]
|
||||
break
|
||||
digest = hash_string(cmd, hash_algo)
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex()
|
||||
return compute_pcr8_hashes(commands, hash_algo)[0]
|
||||
|
||||
|
||||
def load_commands_file(filepath):
|
||||
@ -749,6 +773,29 @@ def evaluate_grub_cfg(grub_cfg, env=None, oem_partition=None,
|
||||
# PCR 9: GRUB source'd files and loaded kernel
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
def compute_pcr9_hashes(kernel_path, oem_grub_cfg_paths=None, hash_algo='sha256'):
|
||||
"""Compute PCR 9 and return (final_hex, entries).
|
||||
|
||||
Returns:
|
||||
(pcr_hex, entries) where entries is a list of (description, digest_hex)
|
||||
tuples for each measured file.
|
||||
"""
|
||||
entries = []
|
||||
if oem_grub_cfg_paths:
|
||||
for cfg_path in oem_grub_cfg_paths:
|
||||
digest = hash_file(cfg_path, hash_algo)
|
||||
entries.append((cfg_path, digest))
|
||||
|
||||
digest = hash_file(kernel_path, hash_algo)
|
||||
entries.append((kernel_path, digest))
|
||||
|
||||
pcr = pcr_init(hash_algo)
|
||||
for _, digest in entries:
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex(), entries
|
||||
|
||||
|
||||
def compute_pcr9(kernel_path, oem_grub_cfg_paths=None, hash_algo='sha256'):
|
||||
"""Compute PCR 9 from measured files.
|
||||
|
||||
@ -764,19 +811,7 @@ def compute_pcr9(kernel_path, oem_grub_cfg_paths=None, hash_algo='sha256'):
|
||||
oem_grub_cfg_paths: list of paths to OEM grub.cfg files that
|
||||
are source'd during boot (measured before the kernel)
|
||||
"""
|
||||
pcr = pcr_init(hash_algo)
|
||||
|
||||
# Source'd config files (OEM grub.cfg etc.)
|
||||
if oem_grub_cfg_paths:
|
||||
for cfg_path in oem_grub_cfg_paths:
|
||||
digest = hash_file(cfg_path, hash_algo)
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
# Kernel file content
|
||||
digest = hash_file(kernel_path, hash_algo)
|
||||
pcr = pcr_extend(pcr, digest, hash_algo)
|
||||
|
||||
return pcr.hex()
|
||||
return compute_pcr9_hashes(kernel_path, oem_grub_cfg_paths, hash_algo)[0]
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
@ -889,8 +924,12 @@ def _eval_grub_cfg_from_args(args):
|
||||
grub_cfg = grub_cfg.replace('@@MOUNTUSR@@', replacement)
|
||||
|
||||
oem_grub_cfg = None
|
||||
if args.oem_grub_cfg:
|
||||
with open(args.oem_grub_cfg, 'r') as f:
|
||||
oem_path = args.oem_grub_cfg
|
||||
# Handle list (from 'all') vs single path (from 'pcr8-eval')
|
||||
if isinstance(oem_path, list):
|
||||
oem_path = oem_path[0] if oem_path else None
|
||||
if oem_path:
|
||||
with open(oem_path, 'r') as f:
|
||||
oem_grub_cfg = f.read()
|
||||
|
||||
root = args.root
|
||||
@ -921,8 +960,41 @@ def main():
|
||||
help='Hash algorithm (default: sha256)')
|
||||
parser.add_argument('--json', action='store_true',
|
||||
help='Output results as JSON')
|
||||
parser.add_argument('--print-hashes', action='store_true',
|
||||
help='Print intermediate measurement digests')
|
||||
sub = parser.add_subparsers(dest='command')
|
||||
|
||||
# all
|
||||
pa = sub.add_parser('all', help='Compute PCR 4, 8, and 9')
|
||||
pa.add_argument('--shim', required=True, help='Path to shim (bootx64.efi)')
|
||||
pa.add_argument('--grub', required=True, help='Path to GRUB (grubx64.efi)')
|
||||
pa.add_argument('--kernel', required=True, help='Path to kernel (vmlinuz-a)')
|
||||
pa.add_argument('--no-sb', action='store_true',
|
||||
help='SecureBoot disabled: skip kernel measurement in PCR 4')
|
||||
pcr8_source = pa.add_mutually_exclusive_group(required=True)
|
||||
pcr8_source.add_argument('--commands-file',
|
||||
help='File with GRUB commands, one per line')
|
||||
pcr8_source.add_argument('--grub-cfg',
|
||||
help='Path to grub.cfg (evaluates for PCR 8)')
|
||||
pa.add_argument('--verity', action=argparse.BooleanOptionalAction,
|
||||
default=True,
|
||||
help='Use dm-verity mount.usr (default: --verity)')
|
||||
pa.add_argument('--oem-grub-cfg', nargs='*', default=[],
|
||||
help='Path(s) to OEM grub.cfg file(s)')
|
||||
pa.add_argument('--root', default='hd0,gpt1',
|
||||
help='GRUB root device (default: hd0,gpt1)')
|
||||
pa.add_argument('--grub-cpu', default='x86_64',
|
||||
choices=['x86_64', 'arm64'],
|
||||
help='CPU architecture (default: x86_64)')
|
||||
pa.add_argument('--oem-partition', default=DEFAULT_OEM_PARTITION,
|
||||
help='OEM partition device (default: %(default)s)')
|
||||
pa.add_argument('--usr-uuid', default=DEFAULT_USR_UUID,
|
||||
help='USR partition UUID from gptprio (default: %(default)s)')
|
||||
pa.add_argument('--first-boot', action='store_true',
|
||||
help='Simulate first boot (first_boot file exists)')
|
||||
pa.add_argument('--menuentry', default='flatcar',
|
||||
help='Menuentry --id to boot (default: flatcar)')
|
||||
|
||||
# pcr4
|
||||
p4 = sub.add_parser('pcr4', help='Compute PCR 4 from EFI binaries')
|
||||
p4.add_argument('--shim', required=True, help='Path to shim (bootx64.efi)')
|
||||
@ -937,25 +1009,6 @@ def main():
|
||||
p8.add_argument('--commands-file', required=True,
|
||||
help='File with GRUB commands, one per line')
|
||||
|
||||
# pcr9
|
||||
p9 = sub.add_parser('pcr9',
|
||||
help='Compute PCR 9 from kernel and config files')
|
||||
p9.add_argument('--kernel', required=True, help='Path to kernel (vmlinuz-a)')
|
||||
p9.add_argument('--oem-grub-cfg', nargs='*', default=[],
|
||||
help='Path(s) to OEM grub.cfg file(s) sourced during boot')
|
||||
|
||||
# all
|
||||
pa = sub.add_parser('all', help='Compute PCR 4, 8, and 9')
|
||||
pa.add_argument('--shim', required=True, help='Path to shim (bootx64.efi)')
|
||||
pa.add_argument('--grub', required=True, help='Path to GRUB (grubx64.efi)')
|
||||
pa.add_argument('--kernel', required=True, help='Path to kernel (vmlinuz-a)')
|
||||
pa.add_argument('--no-sb', action='store_true',
|
||||
help='SecureBoot disabled: skip kernel measurement in PCR 4')
|
||||
pa.add_argument('--commands-file', required=True,
|
||||
help='File with GRUB commands, one per line')
|
||||
pa.add_argument('--oem-grub-cfg', nargs='*', default=[],
|
||||
help='Path(s) to OEM grub.cfg file(s) sourced during boot')
|
||||
|
||||
# pcr8-eval
|
||||
p8e = sub.add_parser('pcr8-eval',
|
||||
help='Compute PCR 8 by evaluating grub.cfg')
|
||||
@ -982,6 +1035,13 @@ def main():
|
||||
p8e.add_argument('--print-commands', action='store_true',
|
||||
help='Print the evaluated command list instead of PCR')
|
||||
|
||||
# pcr9
|
||||
p9 = sub.add_parser('pcr9',
|
||||
help='Compute PCR 9 from kernel and config files')
|
||||
p9.add_argument('--kernel', required=True, help='Path to kernel (vmlinuz-a)')
|
||||
p9.add_argument('--oem-grub-cfg', nargs='*', default=[],
|
||||
help='Path(s) to OEM grub.cfg file(s) sourced during boot')
|
||||
|
||||
# replay
|
||||
pr = sub.add_parser('replay',
|
||||
help='Replay an eventlog to verify PCR values')
|
||||
@ -993,17 +1053,19 @@ def main():
|
||||
sys.exit(1)
|
||||
|
||||
results = {}
|
||||
hashes = {} # pcr_name -> [(description, digest_hex)]
|
||||
|
||||
if args.command == 'pcr4':
|
||||
kernel = None if args.no_sb else args.kernel
|
||||
if not args.no_sb and not args.kernel:
|
||||
parser.error('pcr4: --kernel is required unless --no-sb is given')
|
||||
results['pcr4'] = compute_pcr4(
|
||||
results['pcr4'], hashes['pcr4'] = compute_pcr4_hashes(
|
||||
args.shim, args.grub, kernel, args.algo)
|
||||
|
||||
elif args.command == 'pcr8':
|
||||
commands = load_commands_file(args.commands_file)
|
||||
results['pcr8'] = compute_pcr8(commands, args.algo)
|
||||
results['pcr8'], hashes['pcr8'] = compute_pcr8_hashes(
|
||||
commands, args.algo)
|
||||
|
||||
elif args.command == 'pcr8-eval':
|
||||
commands = _eval_grub_cfg_from_args(args)
|
||||
@ -1011,31 +1073,43 @@ def main():
|
||||
for cmd in commands:
|
||||
print(cmd.replace('\n', '\\n'))
|
||||
else:
|
||||
results['pcr8'] = compute_pcr8(commands, args.algo)
|
||||
results['pcr8'], hashes['pcr8'] = compute_pcr8_hashes(
|
||||
commands, args.algo)
|
||||
|
||||
elif args.command == 'pcr9':
|
||||
results['pcr9'] = compute_pcr9(
|
||||
args.kernel,
|
||||
args.oem_grub_cfg if args.oem_grub_cfg else None,
|
||||
args.algo)
|
||||
oem_cfgs = args.oem_grub_cfg if args.oem_grub_cfg else None
|
||||
results['pcr9'], hashes['pcr9'] = compute_pcr9_hashes(
|
||||
args.kernel, oem_cfgs, args.algo)
|
||||
|
||||
elif args.command == 'all':
|
||||
kernel_pcr4 = None if args.no_sb else args.kernel
|
||||
results['pcr4'] = compute_pcr4(
|
||||
oem_cfgs = args.oem_grub_cfg if args.oem_grub_cfg else None
|
||||
if args.grub_cfg:
|
||||
commands = _eval_grub_cfg_from_args(args)
|
||||
else:
|
||||
commands = load_commands_file(args.commands_file)
|
||||
results['pcr4'], hashes['pcr4'] = compute_pcr4_hashes(
|
||||
args.shim, args.grub, kernel_pcr4, args.algo)
|
||||
commands = load_commands_file(args.commands_file)
|
||||
results['pcr8'] = compute_pcr8(commands, args.algo)
|
||||
results['pcr9'] = compute_pcr9(
|
||||
args.kernel,
|
||||
args.oem_grub_cfg if args.oem_grub_cfg else None,
|
||||
args.algo)
|
||||
results['pcr8'], hashes['pcr8'] = compute_pcr8_hashes(
|
||||
commands, args.algo)
|
||||
results['pcr9'], hashes['pcr9'] = compute_pcr9_hashes(
|
||||
args.kernel, oem_cfgs, args.algo)
|
||||
|
||||
elif args.command == 'replay':
|
||||
results = {("pcr%d" % k): v
|
||||
for k, v in replay_eventlog(
|
||||
args.eventlog, args.algo).items()}
|
||||
|
||||
if args.json:
|
||||
if args.print_hashes and hashes:
|
||||
# JSON output with intermediate measurement digests, keyed by
|
||||
# PCR number to match the generate_grub_hashes.py format.
|
||||
output = {}
|
||||
for name in sorted(hashes):
|
||||
pcr_num = name.replace('pcr', '')
|
||||
output[pcr_num] = [{'value': digest, 'description': desc}
|
||||
for desc, digest in hashes[name]]
|
||||
print(json.dumps(output, indent=2))
|
||||
elif args.json:
|
||||
print(json.dumps(results, indent=2))
|
||||
else:
|
||||
for name, value in sorted(results.items()):
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user