Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
vroad opened this issue Jan 13, 2023 · 18 comments
Open
Labels
enhancement New feature or request
Milestone

Comments

@vroad
Copy link

vroad commented Jan 13, 2023

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:

installHook = pkgs.writeShellScript "bootinstall" ''
${optionalString cfg.enrollKeys ''
mkdir -p /tmp/pki
cp -r ${cfg.pkiBundle}/* /tmp/pki
${sbctlWithPki}/bin/sbctl enroll-keys --yes-this-might-brick-my-machine
''}
${cfg.package}/bin/lanzatool install \
--public-key ${cfg.publicKeyFile} \
--private-key ${cfg.privateKeyFile} \
--configuration-limit ${toString configurationLimit} \
${config.boot.loader.efi.efiSysMountPoint} \
/nix/var/nix/profiles/system-*-link
'';

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

@RaitoBezarius
Copy link
Member

Interesting, quick question:

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

@vroad
Copy link
Author

vroad commented Jan 14, 2023

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
Copy link
Author

vroad commented Jan 14, 2023

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.

@RaitoBezarius
Copy link
Member

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

@vroad
Copy link
Author

vroad commented Jan 17, 2023

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
Copy link
Author

vroad commented Jan 17, 2023

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.

@js6pak
Copy link

js6pak commented Feb 5, 2023

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?

@blitz blitz reopened this Feb 5, 2023
@blitz
Copy link
Member

blitz commented Feb 5, 2023

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

@blitz blitz added the enhancement New feature or request label Feb 5, 2023
@nikstur
Copy link
Collaborator

nikstur commented Feb 12, 2023

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

@RaitoBezarius RaitoBezarius added this to the Release 2.0.0 milestone Apr 30, 2023
@colemickens
Copy link
Member

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.

@RaitoBezarius
Copy link
Member

RaitoBezarius commented May 12, 2023 via email

@ElvishJerricco
Copy link

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.

@t184256
Copy link

t184256 commented Nov 23, 2023

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 systemd/systemd#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.

@DDoSolitary
Copy link

DDoSolitary commented Jan 13, 2024

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.

@Jackaed
Copy link

Jackaed commented Mar 22, 2024

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?

@t184256
Copy link

t184256 commented Mar 22, 2024

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.

@Cu3PO42
Copy link

Cu3PO42 commented May 7, 2024

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.

@ElvishJerricco
Copy link

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests