On Tue, 14 Apr 2020, Lukasz Hawrylko wrote:
I don't know if that tool exists. Anyway, I will look at that multiple
SINITs bug in TBOOT, when it will be fixed, that kind of tool will not
be required.

True, that would mostly not be needed if tboot worked automatically. I can think of two use cases where it might still be useful:

1) You could save disk space and boot speed by not having grub read all SINIT modules from disk.

2) You might want to know before reboot that you have the matching SINIT module if you are enabling TPM support remotely for your server :)

In mean time, you can check acminfo from utils directory. It examines
SINIT binary and also can check if SINIT is compatible with current
platform. You can easily adopt it (with bash scripting help) to do what
you need.

Thanks, I will look into that.


Currently I am trying to automate sealing of disk encryption keys
after an upgrade. Here's a quick and dirty prototype that "works on my server"(TM) but probably contains many bugs. In particular it does not currently know how to predict PCR-17 and just assumes that it has the same value on next reboot. Posting it here in the hope that this will activate discussion on the list for potential alternatives :)

#!/usr/bin/python3
# tpm-luks-auto-seal 2020-04-14
import argparse
import subprocess
import hashlib
import binascii
import glob
import re
import os
import tempfile

INITIAL_HASH = b'\x00'*20

def text_to_hash(text):
    hash = binascii.unhexlify(text.replace(' ', '').replace('\n', ''))
    assert len(hash) == 20
    return hash

def hash_to_text(hash):
    assert len(hash) == 20
    return binascii.hexlify(hash).decode('utf-8')

def extend_hash(hash1, hash2):
    assert len(hash1) == 20
    assert len(hash2) == 20
    return sha1(hash1 + hash2)

def sha1(data):
    m = hashlib.sha1()
    m.update(data)
    return m.digest()

def predict_pcrs(configuration):
    pcr = {}

    pcr[17] = get_current_pcrs()[17]

    pcr[18] = INITIAL_HASH
    tboot_hash = text_to_hash(subprocess.check_output([
        '/usr/sbin/lcp_mlehash',
        '-c',
        configuration.tboot_cmdline,
        configuration.tboot], encoding='utf-8'))
    pcr[18] = extend_hash(pcr[18], tboot_hash)
    with open(configuration.kernel, 'rb') as f:
        kernel_hash = sha1(sha1(configuration.kernel_cmdline.encode('utf-8')) + 
sha1(f.read()))
        pcr[18] = extend_hash(pcr[18], kernel_hash)

    pcr[19] = INITIAL_HASH
    with open(configuration.initrd, 'rb') as f:
        initrd_hash = sha1(sha1(b'') + sha1(f.read()))
        pcr[19] = extend_hash(pcr[19], initrd_hash)

    return pcr

class Configuration:
    def __init__(self, tboot, tboot_cmdline, kernel, kernel_cmdline, initrd):
        self.tboot = tboot
        self.tboot_cmdline = tboot_cmdline
        self.kernel = kernel
        self.kernel_cmdline = kernel_cmdline
        self.initrd = initrd
    def __str__(self):
        return "{}({}) {}({}) {}".format(self.tboot, self.tboot_cmdline, 
self.kernel, self.kernel_cmdline, self.initrd)

def get_current_pcrs():
    pcrs = {}
    with open("/sys/devices/pnp0/00:05/tpm/tpm0/pcrs") as f:
        for line in f.readlines():
            assert line.startswith("PCR-")
            n = int(line.split(":")[0].split("-")[1])
            pcrs[n] = text_to_hash(line.split(":")[1])
    return pcrs

def get_grub_entries(cfg, prefix):
    inside_entry = False
    entries = []
    with open(cfg) as f:
        for line in f.readlines():
            line = line.rstrip().strip()
            if line.startswith("menuentry"):
                inside_entry = True
                entry = {}
            elif inside_entry:
                parts = re.split("\s+", line)
                if line.startswith("}"):
                    if "tboot_cmdline" in entry:
                        entries.append(entry)
                    inside_entry = False
                elif parts[0] == "multiboot":
                    entry["tboot"] = prefix + parts[1]
                    entry["tboot_cmdline"] = parts[2]
                elif parts[0] == "module" and "vmlinuz" in parts[1]:
                    entry["kernel"] = prefix + parts[1]
                    entry["kernel_cmdline"] = " ".join(parts[2:])
                elif parts[0] == "module" and "initrd" in parts[1]:
                    entry["initrd"] = prefix + parts[1]
    return entries

def get_configurations():
    configurations = []

    for e in get_grub_entries("/boot/grub/grub.cfg", "/boot"):
        assert os.path.exists(e["tboot"])
        assert os.path.exists(e["kernel"])
        assert os.path.exists(e["initrd"])
        if " single " in e["kernel_cmdline"]:
            # Skip for  now
            continue

        c = Configuration(tboot=e["tboot"],
                          tboot_cmdline=e["tboot_cmdline"],
                          kernel=e["kernel"],
                          kernel_cmdline=e["kernel_cmdline"],
                          initrd=e["initrd"])
        configurations.append(c)
    return configurations

if __name__ == "__main__":
    parser = argparse.ArgumentParser()
    parser.add_argument("--luks-key-file", default="/etc/luks.key")
    parser.add_argument("--force", action="store_true")
    args = parser.parse_args()

    print("==== Current configuration")
    current_pcr = get_current_pcrs()
    for i in range(17, 20):
        print("PCR-{} {}".format(i, hash_to_text(current_pcr[i])))

    configurations = get_configurations()
    count = 1
    pcrs = []
    for c in configurations:
        print("==== Predicted configuration #{}".format(count))
        print("# {}".format(c))
        pcr = predict_pcrs(c)
        for i in range(17, 20):
            print("PCR-{} {}{}".format(i, hash_to_text(pcr[i]), "*" if 
pcr[i]!=current_pcr[i] else ""))
        count += 1
        pcrs.append(pcr)

    with open(args.luks_key_file) as f:
        luks_key_length = len(f.read())
    assert luks_key_length >= 12
    assert luks_key_length < 128

    if not args.force:
        reply = input("Do you want to enable these configurations (use --force to 
automate this) (Y/n)? ")
        if not reply.lower().startswith("y"):
            print("Aborting")
            sys.exit(0)

    print("Removing old configurations (1-20)")
    for i in range(20):
        try:
            subprocess.check_call(["tpm_nvrelease",
                                   "-i", str(i+1),
                                   "--pwdo=1234"])
        except subprocess.CalledProcessError:
            pass

    for i in range(len(configurations)):
        print("Enabling configuration #{}".format(i+1))
        with tempfile.NamedTemporaryFile(mode="w+") as f:
            for j in range(17, 20):
                f.write("r {} {}\n".format(j, hash_to_text(pcrs[i][j])))
            f.flush()

            subprocess.check_call(["tpm_nvdefine",
                                   "-i", str(i+1),
                                   "-s", str(luks_key_length),
                                   "-p", "OWNERWRITE|READ_STCLEAR",
                                   "--pwdo=1234",
                                   "-z",
                                   "-f", f.name])
            subprocess.check_call(["tpm_nvwrite",
                                   "-i", str(i+1),
                                   "-f", args.luks_key_file,
                                   "-z", "--password=1234"])




-Timo


_______________________________________________
tboot-devel mailing list
tboot-devel@lists.sourceforge.net
https://lists.sourceforge.net/lists/listinfo/tboot-devel

Reply via email to