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

Open
opened 2023-01-13 04:09:20 +00:00 by vroad · 19 comments
vroad commented 2023-01-13 04:09:20 +00:00 (Migrated from github.com)

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:

367d36775d/nix/modules/lanzaboote.nix (L54-L67)

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

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?
RaitoBezarius commented 2023-01-13 22:56:09 +00:00 (Migrated from github.com)

Interesting, quick question:

  • what is the effect to run this every time you run nixos-rebuild switch ?
Interesting, quick question: - what is the effect to run this every time you run `nixos-rebuild switch` ?
vroad commented 2023-01-14 05:40:53 +00:00 (Migrated from github.com)

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.

> 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 commented 2023-01-14 05:59:23 +00:00 (Migrated from github.com)

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.

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 commented 2023-01-14 16:42:05 +00:00 (Migrated from github.com)

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

So basically, some sort of `--post-bootloader-update`, `--post-kernel-update`, `--post-initrd-update` would constitute adequate hooks to enable these usecases, right?
vroad commented 2023-01-17 06:26:20 +00:00 (Migrated from github.com)

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".

> 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 commented 2023-01-17 08:54:45 +00:00 (Migrated from github.com)

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.

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](https://github.com/grawity/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 commented 2023-02-05 01:49:19 +00:00 (Migrated from github.com)

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?

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 commented 2023-02-05 12:50:37 +00:00 (Migrated from github.com)

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

I'm not sure why this was closed. Looks like a pretty useful feature.
nikstur commented 2023-02-12 16:17:33 +00:00 (Migrated from github.com)

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

To me this sounds like you want to use something like `systemd-measure` to pre-calculate the PCR values.
colemickens commented 2023-05-12 21:58:10 +00:00 (Migrated from github.com)

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.

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 commented 2023-05-12 23:07:23 +00:00 (Migrated from github.com)

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:
@.***>

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: > ***@***.***> >
ElvishJerricco commented 2023-05-20 23:48:28 +00:00 (Migrated from github.com)

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.

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](https://github.com/DeterminateSystems/bootspec-secureboot/pull/240)), but that's realistically a distinct concept from both PCR sigs and secure boot.
t184256 commented 2023-11-23 20:30:08 +00:00 (Migrated from github.com)

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: c48e6583f5/hosts/quince/secureboot.nix Hope that'd be of help for the next soul who ends up figuring these bits out.

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.
DDoSolitary commented 2024-01-13 14:34:13 +00:00 (Migrated from github.com)

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.

fwiw, my auto cryptenroll config: ```nix 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 commented 2024-03-22 10:39:42 +00:00 (Migrated from github.com)

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?

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 commented 2024-03-22 10:47:52 +00:00 (Migrated from github.com)

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.

> 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 commented 2024-05-07 06:49:41 +00:00 (Migrated from github.com)

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.

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](https://www.freedesktop.org/software/systemd/man/latest/systemd-cryptenroll.html#TPM2%20PCRs%20and%20policies): >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](https://www.freedesktop.org/software/systemd/man/latest/systemd-stub.html#Description) 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 commented 2024-05-20 07:32:26 +00:00 (Migrated from github.com)

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.

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.
m00nwtchr commented 2024-12-16 15:02:15 +00:00 (Migrated from github.com)

Systemd recommends using PCRs 7+11.

In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if shim/MOK is used). In order to allow firmware and OS version updates, it is typically not advisable to use PCRs such as 0 and 2, since the program code they cover should already be covered indirectly through the certificates measured into PCR 7. Validation through certificates hashes is typically preferable over validation through direct measurements as it is less brittle in context of OS/firmware updates: the measurements will change on every update, but signatures should remain unchanged. See the Linux TPM PCR Registry[1] for more discussion.

Systemd recommends using PCRs 7+11. > In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if shim/MOK is used). In order to allow firmware and OS version updates, it is typically not advisable to use PCRs such as 0 and 2, since the program code they cover should already be covered indirectly through the certificates measured into PCR 7. Validation through certificates hashes is typically preferable over validation through direct measurements as it is less brittle in context of OS/firmware updates: the measurements will change on every update, but signatures should remain unchanged. See the Linux TPM PCR Registry[1] for more discussion.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: raito/lanzaboote#61
No description provided.