Scroll to navigation

PCR-ORACLE(8) Predict TPM PCR values PCR-ORACLE(8)

NAME

pcr-oracle - predict PCR values

SYNOPSIS

pcr-oracle [options] action ...

DESCRIPTION

This utility can be used to predict TPM PCR values at a specific time during system boot, create a PCR policy for these values, and seal a secret to that.

In simple prediction mode, pcr-oracle will compute expected hash values for a subset of PCRs, and write them to standard output. The most common use case will probably be to base the prediction on the TPM event log that was generated by the firmware during the most recent boot. However, there are other modes as well.

In sealing mode, pcr-oracle will seal a given secret (such as a LUKS device password) to either a set of PCR values, or to an authorized PCR policy.

Prediction Mode

In this mode, pcr-oracle is invoked like this:


# pcr-oracle --from eventlog \
--before --stop-event grub-command=tpm2_key_protector_init \
predict 0,2,4,7,9

This would create a PCR prediction for registers 0, 2, 3, 7, and 9 by scanning the current TPM event log until the point where grub executes a command called tpm2_key_protector_init.

As it processes the log, it will recompute the hash values for certain events to match the latest changes that were made to the system. For instance, for EFI Boot Variable events, it will consult the current value of a EFI variable and rehash it. The same holds for EFI Boot Service Application events for grub2 or shim; or for EFI GPT events.

In the example above, pcr-oracle will print the predicted values to standard output. If you want to process these values with a tool from the tpm2.0-tools package, you may prefer binary output, which you can select by invoking the tool with --format binary.

PCR Policies

The standard approach for sealing a secret against an expected system state using the TPM is to create what is called a "PCR Policy". This policy essentially requires a given set of PCRs to contain a specific set of values. This policy can be used to "seal" a secret, ie encrypt it using a key only the TPM has access to.

At some point, for example during the boot process, the boot loader will attempt to unseal this sealed secret. If the TPM's PCRs contain the expected values at this point in time, unsealing will succeed. If they do not contain the expected values, unsealing will either fail, or produce gibberish.

In order to seal a secret againt a predicted set of PCRs, one would typically invoke pcr-oracle like this:


# pcr-oracle --from eventlog \
--before --stop-event grub-command=tpm2_key_protector_init \
--input secret --output sealed \
seal-secret 0,2,4,7,9

Note that the size of the secret to be sealed must not exceed 128 bytes.

The reverse operation looks like this:


# pcr-oracle --input sealed --output recovered \
unseal-secret 0,2,4,7,9

This will instruct the TPM to unseal the given sealed data, computing the policy hash using the current values of the indicated PCRs.

Using Authorized Policies

While regular PCR policies are sound and simple, they are not very flexible, unfortunately. For example, transparently handling updates of the boot loader is rather difficult because we would first have to retrieve the secret before we're able to re-seal it to the future set of PCR values. This is where Authorized Policies come in.

TPM Authorized Policies provide a mechanism by which a secret is not sealed to a set of specific PCR values, but to a list of PCR indices, and a public RSA key. This combination (PCR indices and public key) is called an Authorized Policy.

The owner of the system can then use the secret RSA key to "authorize" a specific set of PCR values, which is in essence a digital signature.

When unsealing the secret during system boot, the boot loader would present the TPM with the original Authorized Policy, and the signed set of PCR values. The TPM then essentially compares the current set of PCR values (as selected by the policy) to the authorized set of values, and verify the authorization's signature using the public key from the policy. If the PCR values match, and the signature checks out, it will be able to unseal the secret.

The benefit of this approach is that you can now seal the secret (such as a LUKS password) once - and when we later update the boot loader, shim loader etc, we just have to authorize the new set of PCR values using the RSA secret key.

So here's an example sequence of commands to use an authorized policy:


# pcr-oracle \
--private-key policy-key.pem \
--auth authorized.policy \
create-authorized-policy 0,2,4,7,9
# pcr-oracle \
--auth authorized.policy \
--input file-containing-secret \
--output sealed-secret \
seal-secret
# pcr-oracle \
--private-key policy-key.pem \
--from eventlog \
--before --stop-event grub-command=tpm2_key_protector_init \
--output signed.policy \
sign 0,2,4,7,9

The first of these commands creates the authorized policy and writes it to a file named authorized.policy. This file, along with the public portion of the RSA key, needs to be stored somewhere in a location that is accessible at the time we need to unlock the LUKS key.

The second command seals the secret against this policy. The clear text is stored in file-containing-secret, the sealed cipher text is stored in sealed-secret. Note that the TPM is limited in the maximum amount of secret data it can seal; it's probably safe to assume a limit of 128 bytes.

Finally, the third command using the prediction logic of pcr-oracle to predict a specific set of PCR values, in exactly the same way described in the section above on prediction mode. It then uses the given RSA key to sign these values and store the result in signed.policy.

Depending on the boot loader used, it may be helpful to provide the public portion of the RSA key in a format that is trivial to handle. For instance, it would be overkill to add DER parsing of RSA keys to grub. In a case like that, you want the key to be available in a form that can be fed to the TPM chip more or less directly. pcr-oracle supports this via its store-public-key subcommand:


# pcr-oracle \
--private-key policy-key.pem \
--public-key policy-pubkey \
store-public-key

This command will read the RSA private key from the PEM file, and write the public key as a TPM2B_PUBLIC object to the indicated output file policy-pubkey.

Using the TPM2.0 policy file format

By default, pcr-oracle writes the policy and key files (which are supposed to be consumed by the boot loader) in a raw format that can be loaded more or less directly into the TPM.

The upstream grub community has adopted a more versatile (and complex) file format, which combines key and policies into a single, ASN.1 encoded file. The behavior can be selected by invoking the sign, seal-secret and unseal-secret operations with the --key-format=tpm2.0 option. For examples, please see the EXAMPLES section below.

Creating and Replaying Test Cases

Firmwares tend to be buggy, like every piece of code, and UEFI firmwares are no different. In order to create a broad base of test cases to validate pcr-oracle against, it comes with support for creating and replaying test cases.

A test case is mostly a dump of the data that pcr-oracle processes when re-hashing the events from the TPM event log. This includes the content of UEFI variables such as Boot entries, variables related to Secure Boot, GPT headers, full copies of any EFI Applications loaded during bootup (such as shim.efi, grub.efi), and hashes for other files loaded by the boot loader (such as grub.cfg, or the kernel and initrd).

The following command will create a test case and store it in a directory tree at /tmp/pcr-oracle.test:


# pcr-oracle --from eventlog --verify current -d \
--create-testcase /tmp/pcr-oracle.test \
predict all

If you want to submit your test case, please use tar to archive the test case and create an issue in the github issue tracker at github.com/okirch/pcr-oracle.

To replay a test case, you can do this:


# pcr-oracle --from eventlog --verify current -d \
--replay-testcase /tmp/pcr-oracle.test \
predict all

TPM Self-test

To perform a TPM self-test, simply execute pcr-oracle like this:


# pcr-oracle self-test

The reason this subcommand exists is that it allows an installer to set up full disk encryption without having to pull in all of tpm-tools.

OPTIONS

In prediction mode, specify a source from which to initialize the PCRs. This can be one of eventlog, current, or zero.
With eventlog, the tool scans the TPM event log and re-compute the event hashes based on the current state of the system.
Specifying a source of zero will start prediction with all PCRs cleared. Specifying a source of current will start prediction with all PCRs set to the current values of the system's TPM.
By default, pcr-oracle will operate on PCR registers that use the sha256 hash algorithm. Using this option, you can select a different hash algorithm, assuming the chip supports it. For backward compatibility with version 1 of the specification, all TPMv2 chips also support sha1, but using that is not recommended.
In prediction mode, pcr-oracle will write the predicted PCR values to standard output in a human readable format. Using this option, you can select different output formats.
A format of binary will write the predicted values as a binary octet stream, all values concatenated together. This format can be used with tools like tpm2_policypcr(1).Aformatoftpm2-toolswilluseareporting format this is similar to the one that the tpm2-tools produce as output. The default behavior is selected by format plain.
Specify at which point to stop processing the TPM event log, and report the predicted PCR values. The format of event-desc is an event type, followed by a =, followed by a string argument.
Currently, only two event types are supported, grub-command and grub-file. These correspond to the IPL event emitted by grub when executing a command, or reading a file, respectively.
For a grub command event, the string argument is the name of the command, without any arguments. For a grub file event, the string argument is a file name or a (partial) file path. Path tail matching is performed, i.e. an event for reading EFI/BOOT/grub.cfg can be matched by specifying a stop event for grub.cfg, BOOT/grub.cfg, or EFI/BOOT/grub.cfg, respectively.
When a stop event has been given, report predicted PCR values before processing the event. This is the default behavior.
When a stop event has been given, report predicted PCR values after processing the event. This may be useful, for example, if you don't measure grub's command events (tracked in PCR8) but only its file load events (tracked in PCR9). In this case, it would be totally sufficient to stop processing after grub loaded grub.cfg from the EFI System Partition.
Specify the location of the authorized policy. In conjunction with the create-authorized-policy action, the newly created policy is written to this location. For subsequent actions, such as seal, the policy will be read from this location.
Specify the RSA secret key to be used with authorized policies.
Specify an RSA public key to be used with authorized policies.
When used while creating an authorized policy, this will generate the RSA private key "on the fly". This should not be different from what openssl genrsa does; the only reason this switch exists is that it may save you from increasing the footprint of your installed system (by not having to install the openssl command line utilities, for example).
By default, the tool will read the current TPM event log. It is possible to process an event log generated on a different system by specifying it with this option.
By default, the tool writes all data that is supposed to be read by the boot loader in a raw format that requires little or no conversion before being loaded into the TPM. This format can be requested explicitly by using --key-format=raw.
Alternatively, it can read and write a format called tpm2.0 which is becoming the preferred format used by grub.
For the tpm2.0 format, there is an extra field, Name, for the authorized polices. This option is only valid when sigining the sealed key in tpm2.0 format. The Name is optional and only used for display purposes. If the user doesn't specify a name, the default name is 'default'.

NOTES

pcr-oracle also supports an unseal action for unsealing secrets. This is mostly for testing purposes and may change at any time.

EXAMPLES

The following will seal a secret against PCR 0,2,4,7, and write the result to sealed-pcr.tpm using the new tpm2.0 format:


# pcr-oracle \ --key-format=tpm2.0 \ --input secret \ --output sealed-pcr.tpm \ seal-secret 0,2,4,7

To seal a secret for an authorized policy:


# pcr-oracle \ --key-format=tpm2.0 \ --auth authorized.policy \ --input secret \ --output sealed-auth.tpm \ seal-secret

To sign the policy against PCR 0,2,4,7 for sealed-auth.tpm:


# pcr-oracle \ --key-format=tpm2.0 \ --policy-name "sealing test" \ --private-key policy-key.pem \ --input sealed-auth.tpm \ --output sealed-auth-signed.tpm \ sign 0,2,4,7

SEE ALSO

tpm2_policypcr(1), tpm2_policyauthorize(1), tpm2_create(1).

AUTHOR

pcr-oracle was written by Olaf Kirch.

July 26, 2023 0.4.6