lanzaboote icon indicating copy to clipboard operation
lanzaboote copied to clipboard

Can I automatically run systemd-cryptenroll after bootloader is updated?

Open vroad opened this issue 3 years ago • 19 comments

I want to auto-unlock LUKS encrypted system drive only when the computer boots NixOS installed to an SSD. For that I need to run systemd-cryptenroll /dev/nvme0n1p1 --tpm2-device=auto --tpm2-pcrs=0+2+4+7 There appears to be no way to run custom commands when lanzaboote bootloader is installed:

https://github.com/nix-community/lanzaboote/blob/367d36775d8058ef7f2e171f9397dbff89cd22dc/nix/modules/lanzaboote.nix#L54-L67

Can we add custom command option to the module that runs only when bootloader is updated?

vroad avatar Jan 13 '23 04:01 vroad

Interesting, quick question:

  • what is the effect to run this every time you run nixos-rebuild switch ?

RaitoBezarius avatar Jan 13 '23 22:01 RaitoBezarius

Interesting, quick question:

  • what is the effect to run this every time you run nixos-rebuild switch ?

systemd-cryptenroll takes some time to enroll keys , I don't want to run it when there is no update to the bootloader.

My goal is preventing other OSes on USB sticks from accessing LUKS encryption keys stored in the TPM, while still allowing them to boot. I can still manually unlock my volume with a password when I want.

vroad avatar Jan 14 '23 05:01 vroad

I'm not sure exactly when I need to re-run this command. Upgrading the kernel doesn't seem to cause boot failure, which might mean that bootloader configuration doesn't affect calculation of PCR 4 value.

vroad avatar Jan 14 '23 05:01 vroad

So basically, some sort of --post-bootloader-update, --post-kernel-update, --post-initrd-update would constitute adequate hooks to enable these usecases, right?

RaitoBezarius avatar Jan 14 '23 16:01 RaitoBezarius

So basically, some sort of --post-bootloader-update, --post-kernel-update, --post-initrd-update would constitute adequate hooks to enable these usecases, right?

What I planned was running a single script with environment variables indicating what are updated, rather than having multiple hooks. Having one fook for each type of update may not work well for some use cases, such as "run a script if any of bootloader, kernel, initrd has changed".

vroad avatar Jan 17 '23 06:01 vroad

I was misunderstanding how PCR value calculation works. Values in PCRs won't update until next reboot. Running systemd-cryptenroll automatically after each bootloader update will not enroll new keys. As a result you will be asked LUKS encryption password everytime you update the bootloader (if PCR 4 is used).

tpm_futurepcr would allow pre-calculating the value for PCR4, but no longer maintained.

I try enabling early SSH instead to type password from main machine. Hooks won't be useful for my use case without a tool like tpm_futurepcr.

vroad avatar Jan 17 '23 08:01 vroad

Should this be closed? Is there any way to have working tpm unlocking after a kernel/bootloader/initrd update? Could lzbt possibly have the same feature as mentioned tpm_futurepcr?

js6pak avatar Feb 05 '23 01:02 js6pak

I'm not sure why this was closed. Looks like a pretty useful feature.

blitz avatar Feb 05 '23 12:02 blitz

To me this sounds like you want to use something like systemd-measure to pre-calculate the PCR values.

nikstur avatar Feb 12 '23 16:02 nikstur

If someone uses systemd-measure with lanzaboote, maybe perhaps with systemd-cryptenroll for automatic TPM-based LUKS unlocking, I'd love to hear a few hints/details. Unless someone else wants to just do it, I could add it to the README once I get it figured out.

colemickens avatar May 12 '23 21:05 colemickens

We discussed it today with @nikstur some of the PR inflight will enable this, feel free to participate in reviews. :)

Le ven. 12 mai 2023 à 23:58, Cole Mickens @.***> a écrit :

If someone uses systemd-measure with lanzaboote, maybe perhaps with systemd-cryptenroll for automatic TPM-based LUKS unlocking, I'd love to hear a few hints/details. Unless someone else wants to just do it, I could add it to the README once I get it figured out.

— Reply to this email directly, view it on GitHub https://github.com/nix-community/lanzaboote/issues/61#issuecomment-1546355165, or unsubscribe https://github.com/notifications/unsubscribe-auth/AACMZRACNVQAFXL4IQTZM6TXF2W7ZANCNFSM6AAAAAATZ7BQOY . You are receiving this because you commented.Message ID: @.***>

RaitoBezarius avatar May 12 '23 23:05 RaitoBezarius

So one thing to keep note of: If you're using self-signed secure boot and just want to avoid re-enrolling your TPM2 based LUKS keys, you can just bind to PCR 7 (and 0 and 2 IMO). No need for all the systemd-measure stuff. The systemd-measure stuff is more about sealing things against specific UKI configurations, but still depends on the previous boot stages to be secure. systemd-measure also confusingly includes the idea of PCR phases in there (which is why I've messed with it), but that's realistically a distinct concept from both PCR sigs and secure boot.

ElvishJerricco avatar May 20 '23 23:05 ElvishJerricco

This is slightly offtopic (because I ended up not using lanzaboote in the end), but here's my config where I pre-measure an UKI, auto-enroll and workaround https://github.com/systemd/systemd/issues/30164: https://github.com/t184256/nix-configs/blob/c48e6583f56fcb9ca1dbcf527fe96ea43b76b79a/hosts/quince/secureboot.nix Hope that'd be of help for the next soul who ends up figuring these bits out.

t184256 avatar Nov 23 '23 20:11 t184256

fwiw, my auto cryptenroll config:

  boot.lanzaboote = {
    enable = true;
    pkiBundle = "/etc/secureboot";
    package = lib.mkForce (pkgs.writeShellApplication {
      name = "lzbt";
      runtimeInputs = with pkgs; [ coreutils binutils vim openssl jq ];
      text = let
        systemd-pcr-value = pkgs.systemd.overrideAttrs (old: {
          patches = old.patches ++ [
            (pkgs.fetchpatch {
              url = "https://github.com/systemd/systemd/pull/28398.patch";
              hash = "sha256-VCDB8tdkBiG0eOlSN5PS4cYkOxl0BtiORxmrfpRKoKo=";
            })
            (pkgs.fetchpatch {
              url = "https://github.com/systemd/systemd/pull/28916.patch";
              hash = "sha256-G/cx9RsVhah18rNqtmy2fzkfvBFGLXUCuDIby5vaZZ4=";
            })
          ];
        });
      in ''
        set -o pipefail

        "${lanzaboote.packages."${pkgs.system}".tool}/bin/lzbt" "$@"

        work_dir="$(mktemp -d)"
        pushd "$work_dir" > /dev/null

        hash_algo=sha256
        hash_len="$(openssl "$hash_algo" -binary /dev/null | wc -c)"
        stub_path="$(bootctl list --json pretty | jq -r '.[] | select(.isDefault) | .path')"
        section_names=".linux .osrel .cmdline .initrd .splash .dtb .pcrsig .pcrpkey"
        head -c "$hash_len" /dev/zero > pcr
        for section_name in $section_names; do
          objcopy -O binary --dump-section "$section_name=section$section_name" "$stub_path" /dev/null
          if [ ! -f "section$section_name" ]; then
            continue
          fi
          cat pcr <(openssl "$hash_algo" -binary "section$section_name") | openssl "$hash_algo" -binary -out pcr_new
          echo "pcr old=$(xxd -p -c0 pcr) new=$(xxd -p -c0 pcr_new)"
          mv pcr_new pcr
        done
        "${systemd-pcr-value}/bin/systemd-cryptenroll" \
          "${config.boot.initrd.luks.devices."cryptroot".device}" \
          --wipe-slot tpm2 --tpm2-device auto --tpm2-pcrs "7+11:$hash_algo=$(xxd -p -c0 pcr)" \
          --unlock-key-file /etc/nixos/credentials/luks/root

        popd > /dev/null
        rm -rf "$work_dir"
      '';
    });
  };

The systemd patches are for specifying pcr values to systemd-cryptenroll --tpm2-pcrs. Once nixpkgs has systemd v255, these patches will no longer be required and we can probably use systemd-pcrlock to avoid keeping a key file on disk or entering recovery key every time upgrading.

Note that currently lanzaboote's support for measuring kernel image into pcr 11 is only available in master branch, and it is still wip. It only measures .osrel and .cmdline, not kernel and initrd sections. See #167, #168. Don't use it in production environment. Once support for systemd-stub compatible measurement is complete, I guess systemd-measure can be used to replace manual calculation of pcr values.

DDoSolitary avatar Jan 13 '24 14:01 DDoSolitary

As someone going through the process of migrating from LUKS1 to LUKS2 in order to use the TPM to decrypt my drives instead of manually entering a password, is it secure to simply bind to PCRs 0,2, and 7 and call it a day? If so, why, and if not, why not?

Jackaed avatar Mar 22 '24 10:03 Jackaed

decrypt my drives [without] entering a password

is it secure

🙄

define your threat model, put yourself into the attacker's shoes, arrive at the answer. if you want to discuss that, please use some discussions space that's not an issue tracker.

t184256 avatar Mar 22 '24 10:03 t184256

I fully agree with the stated goal of wanting TPM-based disk locking to "just work" across generation changes/kernel, initrd, and bootloader updates.

Having post-installation hooks is probably also useful for many things, but I believe there is a better solution for this particular goal. That said, I'm not an expert on the TPM, so take this with a grain of salt.

The setup described above binds a TPM secret to a particular set of PCRs that change on an upgrade. Hence the need to re-enroll the secret any time the PCRs change. Instead, it is possible to bind the secret indirectly. From systemd-cryptenroll docs:

Secrets may also be bound indirectly: a signed policy for a state of some combination of PCR values is provided, and the secret is bound to the public part of the key used to sign this policy. This means that the owner of a key can generate a sequence of signed policies, for specific software versions and system states, and the secret can be decrypted as long as the machine state matches one of those policies. For example, a vendor may provide such a policy for each kernel+initrd update, allowing users to encrypt secrets so that they can be decrypted when running any kernel+initrd signed by the vendor. Such bindings may be created with the options --tpm2-public-key=, --tpm2-public-key-pcrs=, --tpm2-signature= described below.

It is my understanding that this can be combined with the .pcrsig and .pcrpsig sections as described in systemd-stub docs to allow extracting the secret securely even when PCRs change.

My understanding of the flow is as follows:

  • We generate a public/private key pair.
  • We seal the disk encryption key bound to the just-generated public key and some set of PCRs.
  • Upon building a new UKI, we pre-compute the above set of PCRs and sign these values with the private key.
  • This signature (and the public key) are embedded in the UKI.
  • During boot, the contents of these sections are passed via a initrd CPIO.
  • systemd-cryptsetup can use these files to unseal the secret, while making sure that the actual PCR values are equal to the pre-computed, signed ones, and unlock the disk.

Having written all of this, I have now also discovered that #169 already exists so work seems to be underway :-) I'll still leave this comment in the hope that it helps someone perusing the issue tracker.

Cu3PO42 avatar May 07 '24 06:05 Cu3PO42

I should reiterate: In most cases, simply binding to PCR 7 will be sufficient for a securely locked disk that needs no re-enrollment on upgrades. This works because PCR 7 is essentially validating that your secure boot chain was honored. i.e. Only your self-signed OS can unlock it. Even if you've enrolled MS keys, the first time an individual key is used to validate a boot phase, it is measured into PCR 7, so MS-signed OSes won't be able to unlock your disk.

I'm not really a fan of the .pcrsig and .pcrpsig design. I'm much more a fan of the pcrlock stuff that's been developed recently. But either way, just binding to PCR 7 is going to accomplish the goal for simple setups.

ElvishJerricco avatar May 20 '24 07:05 ElvishJerricco