This tool tries to predict TPM PCR values for future boot, based on the current state of the system it runs in.
The top objective in creating this tool is to support full disk encryption, and be able to have the TPM unseal the encryption key that is protecting the system partition. Of course, as changes are made to the system, the PCR values during boot will change, making it necessary to adjust the policy that's protecting the SRK. Examples include updates of the shim and/or boot loader, or changes made to the GPT of the hard disk that EFI is booting from.
The primary mode of operation uses the TPM event log to replay the sequence of PCR Extend operations. However, instead of hashing the event as contained in the event log, it will use the current values of EFI variables, boot applications and files etc as found in the running system.
In order to verify that the tool works on a specific EFI system, it is best to run it in verification mode, like this:
pcr-oracle --from eventlog all --verify current
This will re-hash all events found inside the event log, and when done, compare the predicted values against the current PCR values read from the TPM. (For those who are curious about what's going on under the hood, run the command with the "-d" option for debugging; use -ddd for really verbose output).
Of course, in order to be usable for full disk encryption, the prediction needs to output the PCR values not at the very end of the boot process, but at the exact point where the key would be unsealed. In the case of the grub based approach we're currently evaluating at SUSE, this would happen in the early grub.cfg file, looking like this:
tpm2_key_protector_init -b sha256 -p 0,2,4,7,9 -k $prefix/sealed.key
cryptomount -u e333642244ee4f0c828a466855781386 -k tpm2
In order to predict the PCR values for this scenario, you would invoke pcr-oracle like this:
pcr-oracle --from eventlog 0,2,4,7,9 \
--before --stop-event grub-command=tpm2_key_protector_init \
--format binary >pcr.state
This will stop processing the TPM event log right before the
first grub command event for the tpm2_key_protector_init
command.
By writing the output in binary format, the pcr.state
file generated
by this example can be used directly with the --pcr option of
tpm2_policypcr
.
Using a set of PCR values to seal a secret directly is a challenge when dealing with updates of software components of grub, the shim loader, etc - because these updates change the outcome of the PCR computation done by the firmware.
Of course, it's possible to re-seal the secret using the new set of PCR values, and that is what pcr-oracle was originally designed to do. However, re-sealing means you need to have the original secret from somewhere. And that can be difficult, or at least lead to implementations that are not very user friendly.
To deal with this challenge, authorized policies were invented (sometimes called "brittle PCR policies"). Now, what's the difference?
With regular PCR policies, you basically tell the TPM "I want you to divulge the following secret whenever PCR registers A, B, and C have the following value(s)."
With authorized PCR policies, you tell the TPM "Whenever I present you with a set of PCR register values, and a digital signature of these values signed with RSA key X, I want you to divulge the secret whenever PCR registers A, B, and C have the given values."
In other words, with regular PCR policies, you tie the sealed secret to specific PCR values directly. Whereas an authorized policy inserts an RSA key in the middle - and if you update say the grub2 boot loader, you do not re-seal the secret, you just re-compute the PCR values, and sign them with an RSA secret key.
How is this an improvement? After all, it's only a gradual difference if I leave a copy of a LUKS key on my disk, or an unencrypted RSA key that can be used to sign arbitrary PCR policies, right?
However, authorized policies can provide drastic improvements in managed environments, eg when keeping the RSA key in a central location. In this case, a managed system, when performing an update of grub2, would send its TPM event log to the central system, along with the name/version of grub it wants to update to. The central authority would verify that it's okay to update to the indicated grub version, predict the new PCRs based on the event log plus its copy of the files from the grub package, sign these values and return the signature to the managed node.
So far, pcr-oracle does not yet implement this mode of operation. However, it already supports the creation of, and sealing against, authorized policies.
For an example of how to use pcr-oracle with authorized policies, please refer to test-authorized.sh
The UEFI spec is large and complex, so writing a UEFI Firmware is without doubt a daunting task. And not surprisingly, firmwares sometimes do not adhere to the spec 100%. Which is why a tool like this needs to detect such issues, and handle them gracefully.
In order to keep regressions to a minimum, need to build a collection of test cases that covers a broad range of vendors and system configurations.
You can contribute to this collection by submitting a test case! Simply run the following commands:
pcr-oracle --from eventlog all --verify current -d \
--create-testcase /tmp/pcr-oracle.test
tar cjf /tmp/pcr-oracle.test.tar.bz2 /tmp/pcr-oracle.test
Submit your test case as a github issue to the pcr-oracle project, or send it to me via email.
If you're curious, you can also re-run your own test case using this command:
pcr-oracle --from eventlog all --verify current -d \
--replay-testcase /tmp/pcr-oracle.test
Thank you!