Scroll to navigation

PCR-ORACLE(8) Predict TPM PCR values and Seal Secrets PCR-ORACLE(8)

NAME

pcr-oracle - predict TPM PCR values and seal secrets

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.

PCR Policies vs Authorized Policies

The simplest approach to 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.

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.

Policy File Formats

To make things a little more complex, different file formats are being used to store policies and sealed secrets, depending on the implementation. The current standard is the so-called TPM2.0 key file format described in https://www.hansenpartnership.com/draft-bottomley-tpm2-keys.html. This format combines key material and policies into a single, ASN.1 encoded file.

In addition, file formats have been used in the past that stored TPM object in a native format defined in the TPM specification.

Finally, the systemd-boot implementation for unlocking disk partitions stores policies in a JSON formatted file, which can be fed to the systemd-cryptenroll tool. This file will contain any number of signed policies that we want systemd-cryptsetup to recognize during boot time. Each entry contains the list of PCRs that compose the PCR policy, a finger print of the public key, the hash of the policy and the signature (in base64) of the PCR policy.

pcr-oracle supports all three implementations. The target implementation can be selected using the --target-platform option, which accepts tpm2.0, systemd and oldgrub as arguments, respectively.

The default format is tpm2.0.

RSA Key File Formats

By default, pcr-oracle stores private RSA keys as PEM files and public RSA keys in native TPM encoded format. This behavior can be tuned, however. When it detects a file name that has a .pem extension, it will automatically assume that the caller expects the file to be in PEM format.

In addition, you can explicitly force a specific file format by prefixing the entire path by either pem: opr native:, respectively.

ACTIONS

The pcr-oracle command supports the following actions:

Perform a TPM self-test to verify the chip exists and works.
Check whether the TPM supports a given RSA key length. The desired key length is given using the --rsa-bits option.
Try to predict a specific set of PCR values.
Create a PCR authorized policy. Optionally, a new RSA key can be generated.
Given a private key, extract the public portion and write it to a separate file. This is useful when a secret key has been generated while creating the authorized policy.
Read a secret piece of data from a file, and seal it against a TPM policy (either a PCR policy or an authorized policy). 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.
When using an authorized policy, predict a set of PCR values and sign them using an RSA key.
This action exists primarily for test purposes. Given a sealed secret and (optionally) a signed policy, unseal the secret and write it to the specified output file.

EXAMPLES

This section illustrates the use of pcr-oracle in different scenarios through HOWTO examples.

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.

TPM RSA key size test

To perform a TPM RSA key size test, simply execute pcr-oracle like this:


# pcr-oracle --rsa-bits 2048 rsa-test

This subcommand allows external programs, such as fde-tools, to find out the largest supported RSA key size.

Prediction Mode

In prediction 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.

Sealing Against PCR Policies

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


# pcr-oracle --from eventlog \
--target-platform oldgrub \
--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 \
--target-platform oldgrub \
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.

Sealing Against Authorized Policies

The following sequence of commands seals a secret against an authorized policy, and then authorized a set of PCR values by signing them into a signed policy:


# pcr-oracle \
--target-platform oldgrub \
--private-key policy-key.pem \
--auth authorized.policy \
create-authorized-policy 0,2,4,7,9 # pcr-oracle \
--target-platform oldgrub \
--auth authorized.policy \
--input file-containing-secret \
--output sealed-secret \
seal-secret # pcr-oracle \
--target-platform oldgrub \
--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 encrypted secret is stored in sealed-secret.

Finally, the third command uses 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.

Alternatively, if you want the public key to be stored as PEM formatted key, simply specify the file name of the public key with a .pem extension:


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

For details, please see section RSA Key File Formats.

Using the TPM2.0 Policy File Format

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 \ --target-platform=tpm2.0 \ --input secret \ --output sealed-pcr.tpm \ seal-secret 0,2,4,7

To seal a secret for an authorized policy:


# pcr-oracle \ --target-platform=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 \ --target-platform=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

Generating a Signed Policy for Systemd

When using systemd-boot instead of grub2, you would create an authorized policy as above, and seal your secret LUKS key against that. The main difference is in the way the signed policy is stored:


# pcr-oracle \ --target-platform=systemd \ --private-key policy-key.pem \ --from eventlog \ --output tpm2-pcr-signature.json \ sign 0,2,4,7,9,12

This will predict a set of PCR values, sign them using the specified private key, and then store the signature in tpm2-pcr-signature.json. Note that this command will not overwrite the JSON file, it will update it by adding a new entry. If there already is an entry for the predicted set of PCR values, this will merely update the public key fingerprint and signature.

These policies can then be enrolled for future system boots using systemd-cryptenroll:


# systemd-cryptenroll \ --tpm2-device=auto \ --tpm2-public-key=public-key.pem \ --tpm2-public-key-pcrs="0,2,4,7,9,12" --tpm2-signature=tpm2-pcr-signature.json \ /dev/sda3

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

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.
In a systemd-boot environment, the values of PCR registers also depend on the contents of the kernel and initrd image, as well as the kernel command line options. After a kernel update or an initrd rebuild, these values will change. So when predicting PCR values in this case, pcr-oracle needs to be instructed to refer to the future kernel rather than the current one.
This is what the --boot-entry option is for. It takes one argument, which is either an ID (in the sense of systemd-boot IDs), or auto. When the latter is given, pcr-oracle will make a best guess as to what kernel image will be used on next boot.
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. For notes on the file format, please see section RSA Key File Formats.
Specify an RSA public key to be used with authorized policies. For notes on the file format, please see section RSA Key File Formats.
When used while creating an authorized policy, a signature, or when storing the public key, 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, RSA 2048 is used as the algorithm to create the public and private key and configure the storage root key(SRK) in TPM. This option allows the user to specify the larger RSA key size. The supported key sizes are: 2048, 3072, and 4096 bits.
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.
Write key and policy information using file format(s) compatible with the specified target implementation. Please see the section Policy File Formats for more information.
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'.

SEE ALSO

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

AUTHOR

pcr-oracle was written by Olaf Kirch with significant contributions from Gary Lin, Michael Chang and Alberto Planas.

March 25, 2024 0.5.5