Skip to content

Commit

Permalink
Merge pull request #28891 from poettering/pcrlock
Browse files Browse the repository at this point in the history
new pcrlock tool for generating signed PCR policies for PCR 0, 1, 4, …
  • Loading branch information
bluca committed Nov 3, 2023
2 parents f742f41 + e43f87a commit 8d04721
Show file tree
Hide file tree
Showing 54 changed files with 8,671 additions and 241 deletions.
95 changes: 17 additions & 78 deletions TODO
Expand Up @@ -147,6 +147,23 @@ Features:
root=nvme:<trtype>:<traddr>:<trsvcid>:<nqn>:<partition> to boot directly from
nvme-oF

* pcrlock:
- make signed PCR work together with pcrlock
- add kernel-install plugin that automatically creates UKI .pcrlock file when
UKI is installed, and removes it when it is removed again
- automatically install PE measurement of sd-boot on "bootctl install"
- write generated pcrlock signature files to the ESP as credential, one for
each installed OS & pick up generated pcrlock signature file in sd-stub,
pass it via initrd to OS
- pre-calc sysext + kernel cmdline measurements
- pre-calc cryptsetup root key measurement
- maybe make systemd-repart generate .pcrlock for old and new GPT header in
/run?
- Add support for more than 8 branches per PCR OR
- add "systemd-pcrlock lock-kernel-current" or so which synthesizes .pcrlock
policy from currently booted kernel/event log, to close gap for first boot
for pre-built images

* add a new systemd-project@.service that is very similar to user@.service but
uses DynamicUser=1 and no PAMName= to invoke an unprivileged somewhat
light-weight service manager. Use HOME=/var/lib/systemd/projects/%i as home
Expand Down Expand Up @@ -299,54 +316,6 @@ Features:
* systemd-mount should only consider modern file systems when mounting, similar
to systemd-dissect

* new "systemd-pcrlock" component for dealing with PCR4. Design idea:
1. define /{etc,usr,var/lib}/pcrlock.d/<component>/<version>.pcrlock
2. these files contain list of hashes that will be measured when component is
run, per PCR
3. each component involved in the boot that is deterministically measured can
place one or more of these files in those dirs (shim, sd-boot,
sd-stub/UKI, cryptsetup, pcrphase, pcrfs, …)
4. since each component has its own dir, with multiple files in them, package
such as kernels (of which there can be multiple installed at the same
time) can be grouped together: only one of them is measured at a time.
5. whenever a new component is added or an old one removed, or the PCR lock
shall be relaxed or tightened the systemd-pcrlock tool is invoked.
6. tool iterates through all these files, orders them alphabetically by
component, then matches them up with current measurements (as per uefi
event log), identifying by hash, accepting that the "beginning" of the
measurements might not be recognizable.
7. Then calculates expected PCR values starting with the "unrecognized
head" from the event log, then continuing with all of components
defined via the .pcrlock files (but dropping out the "recognized tail"
from the uefi event log). (This might mean combinatorial explosion, if
there are multiple shims, multiple sd-boot, and so on.)
8. Generates a public/private key pair on the TPM
9. Generates a counter object in the TPM, with a policy that allows only
one-by-one increase with signature policy by the public/private key pair.
10. now signs policies of all expected PCR values with the generated keypair,
using all combinations of components defined in the .pcrlock files
restricting it to the counter + 1.
11. locks down the keypair with a signed policy with its own public key
12. generates JSON file of all these policies with their signatures, drops
them as singleton in ESP
13. increases the counter by one.
14. after boot sd-stub picks JSON up from ESP, passes it to userspace via
.extra
15. JSON contained policies can now be used to unlock disk as well as the
public/key itself for signing further policies, as well as increment for
the counter
16. whenever any of the components above is added/removed new JSON file with
signatures for counter + 1 is generated, dropped in ESP, then counter
increased. (i.e. this means the "recognized tail" of the event log is
deterministically swapped out)
17. when firmware update is expected, relaxed signed policy is generated for
next boot only valid if counter is increased (this means the
"unrecognized head" for the event log can change without losing access)
18. on every boot checks if releaxed policy is in effect, if so, new strict
policy is generated and counter increased.
Net result: Removes downgrade attack surface + Locks OS to firmware + Allows
downgrades within bounds

* add another PE section ".fname" or so that encodes the intended filename for
PE file, and validate that when loading add-ons and similar before using
it. This is particularly relevant when we load multiple add-ons and want to
Expand Down Expand Up @@ -469,30 +438,6 @@ Features:
* SIGRTMIN+18 and memory pressure handling should still be added to: hostnamed,
localed, oomd, timedated.

* in order to make binding to PCR 4 realistic:
- generate one keypair "U" and store it in a tpm2 nvindex.
- Generate another keypair "P" and store it in a second tpm2 nvindex.
- allocate a persistent counter object "C" in the tpm2
- Enroll all user objects (i.e. luks volumes, creds, …) to a tpm2 policy
signed by U.
- Lock both U and P down with a tpm2 policy signed by P (yes, P can only be
used if a signature by P itself can be provided)
- For regular reboots generate a signature for a restrictive PCR4 + counter C
based policy with key P. Place signature in EFI var, so it can be found on
next boot
- For reboots where a firmware update is expected generate a signature with a
more open policy against just counter C. Place signature in same EFI var.
- Increase C whenever switching between these two signature types.
- During early boot, use the signature from the EFI var to unlock U and P.
Use it to generate a signature for unlocking user objects given the current
PCR 4 value, store that away into /run somewhere, for user during the whole
later boot.
- When booting up automatically update the mentioned efi var so that it
contains the restrictive signature. But also generate a signature ahead of
time that could be used in case during the current boot we later detect we might
need to reboot for a firmware update. Store that in /run somewhere, so that
it can be placed in the EFI var, if needed.

* repart/gpt-auto/DDIs: maybe introduce a concept of "extension" partitions,
that have a new type uuid and can "extend" earlier partitions, to work around
the fact that systemd-repart can only grow the last partition defined. During
Expand Down Expand Up @@ -1027,12 +972,6 @@ Features:
set up the directory so that it can only be accessed if host and app are in
order.

* TPM2: extend unlock policy to protect against version downgrades in signed
policies: policy probably must take some nvram based generation counter into
account that can only monotonically increase and can be used to invalidate
old PCR signatures. Otherwise people could downgrade to old signed PCR sets
whenever they want.

* update HACKING.md to suggest developing systemd with the ideas from:
https://0pointer.net/blog/testing-my-system-code-in-usr-without-modifying-usr.html
https://0pointer.net/blog/running-an-container-off-the-host-usr.html
Expand Down
16 changes: 16 additions & 0 deletions man/crypttab.xml
Expand Up @@ -808,6 +808,22 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>

<varlistentry>
<term><option>tpm2-pcrlock=</option></term>

<listitem><para>Takes an absolute path to a TPM2 pcrlock policy file, as produced by the
<citerefentry><refentrytitle>systemd-pcrlock</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. This permits locking LUKS2 volumes to a local policy of allowed PCR values with
variants. See
<citerefentry><refentrytitle>systemd-cryptenroll</refentrytitle><manvolnum>1</manvolnum></citerefentry>
for details on enrolling TPM2 pcrlock policies. If this option is not specified but it is attempted
to unlock a LUKS2 volume with a TPM2 pcrlock enrollment a suitable signature file
<filename>pcrlock.json</filename> is searched for in <filename>/run/systemd/</filename> and
<filename>/var/lib/systemd/</filename> (in this order).</para>

<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>

<varlistentry>
<term><option>tpm2-measure-pcr=</option></term>

Expand Down
11 changes: 11 additions & 0 deletions man/rules/meson.build
Expand Up @@ -1004,6 +1004,16 @@ manpages = [
['systemd-nspawn', '1', [], ''],
['systemd-oomd.service', '8', ['systemd-oomd'], 'ENABLE_OOMD'],
['systemd-path', '1', [], ''],
['systemd-pcrlock',
'8',
['systemd-pcrlock-file-system.service',
'systemd-pcrlock-firmware-code.service',
'systemd-pcrlock-firmware-config.service',
'systemd-pcrlock-machine-id.service',
'systemd-pcrlock-make-policy.service',
'systemd-pcrlock-secureboot-authority.service',
'systemd-pcrlock-secureboot-policy.service'],
'ENABLE_BOOTLOADER'],
['systemd-pcrphase.service',
'8',
['systemd-pcrextend',
Expand Down Expand Up @@ -1139,6 +1149,7 @@ manpages = [
['systemd.nspawn', '5', [], ''],
['systemd.offline-updates', '7', [], ''],
['systemd.path', '5', [], ''],
['systemd.pcrlock', '5', ['systemd.pcrlock.d'], ''],
['systemd.preset', '5', [], ''],
['systemd.resource-control', '5', [], ''],
['systemd.scope', '5', [], ''],
Expand Down
14 changes: 14 additions & 0 deletions man/systemd-cryptenroll.xml
Expand Up @@ -519,6 +519,20 @@
<xi:include href="version-info.xml" xpointer="v252"/></listitem>
</varlistentry>

<varlistentry>
<term><option>--tpm2-pcrlock=</option><arg>PATH</arg></term>

<listitem><para>Configures a TPM2 pcrlock policy to bind encryption to. Expects a path to a pcrlock
policy file as generated by the
<citerefentry><refentrytitle>systemd-pcrlock</refentrytitle><manvolnum>1</manvolnum></citerefentry>
tool. If a TPM2 device is enrolled and this option is not used but a file
<filename>pcrlock.json</filename> is found in <filename>/run/systemd/</filename> or
<filename>/var/lib/systemd/</filename> it is automatically used. Assign an empty string to turn this
behaviour off.</para>

<xi:include href="version-info.xml" xpointer="v255"/></listitem>
</varlistentry>

<varlistentry>
<term><option>--wipe-slot=</option><arg rep="repeat">SLOT</arg></term>

Expand Down

0 comments on commit 8d04721

Please sign in to comment.