UEFI Secure Boot and the TPM: Config Data
KentYoder 270001NFM8 Visits (1960)
Now lets say we'd like to continue to lock down the system at each of these layers by ensuring our system is configured the way we like. We know firewire is dangerous, so we want to verify that its always disabled. And we want to prevent that evil maid we just hired from booting our box off an external drive to get to our encrypted data. And on top of that we want to be sure selinux is enforcing -- that it wasn't just disabled on the kernel command line with "selinux=0".
Can UEFI Secure Boot help us here? Not really. This is because secure boot only verifies the images you're booting, not how those images are configured. In a system with secure boot enabled, you can still boot from any device that offers up an image signed with a key we trust. You can still set selinux=0 on the kernel command line.
There are other ways of trying to secure your config data of course. You can set a UEFI password and a GrUB password. You can recompile your kernel so that the selinux= option isn't available anymore. This works, but the config data still isn't part of your root of trust. You still need software means to validate that your system is configured the way you trust.
Now lets look at how we might use a TPM to accomplish this. With a TPM, we can integrate whatever config data we care about directly into the root of trust in a transparent way. We can continue to use passwords to protect our config data, or not. The integrity check on the config data won't require a key since its performed by the TPM hardware, so use of our trust chains will become much more flexible. Here's how we can use the TPM to do this.
If you recall from the last blog, the TPM creates its roots of trust by hashing code and data into Platform Configuration Registers (PCRs). The TCG has published some guidance on how to use the various PCRs, so that the software layers don't step on each other while trying to record their state in PCRs. The guidance gets fairly detailed, but I can sum it up a little here: PCRs with even indexes are used to chain executable code digests together, while PCRs with odd indexes are used to chain the config data for that code.
So for example, ROM code may chain a digest of our UEFI image into PCR 0 and the UEFI code would chain a digest of its config data into PCR 1. UEFI code would then chain the boot loader's digest into PCR 2, the boot loader would log its config data to PCR3, and so on.
Since the digests of code and config data are spread across multiple PCRs, we just need to bind a secret to all the PCRs that contain digests we care about. To bind a TPM key or NVRAM area to multiple PCRs, we create what's called a composite hash. This is a specific data structure that TPMs understand that includes a bit mask of the PCRs we've selected and the expected hash values for each of them. Creating a digest of the composite hash structure basically gives us the signature for the full scope of the state of the code and config data we care about. This composite hash is the signature we'd bind a key to for say, disk encryption. (Here's some example code that binds a key to PCRs 1 and 15).
Our trust chain as expressed in PCR values gives us as much granularity as we need. We can tell, for example, we loaded UEFI version 2.3.1 from Phoenix, with firewire disabled, boot device 2 disabled, and so on. The number of possible PCR states is only limited by the number of possible config states for the software we're running.
We can combine the digest in any PCR index with any other PCR index to create arbitrary roots of trust. We can choose to include config data in our root of trust by integrating the odd PCR indexes into our composite hash. There are some additional ways to use PCRs as well.
What if we want an event to trigger denial of use of a key? In this case if the key is bound to PCR values, we can "cap" the PCR by sending random data to the TPM PCR, then throwing the data away. Once capped, the PCR values for that key's composite hash no longer match, and the TPM will not release the key data. To regain access to the key, we'll need to go through whatever steps were used to create the original root of trust (usually a reboot).
We can also use PCRs to prove we've seen some prior trusted state. Let's say we've bound a key used for disk encryption, parentkey1, to our boot config in a trusted boot setup. Once we've used it to unlock our disk encryption key, send a digest of parentkey1 to PCR 9 (for example) in the TPM. Now PCR 9 contains a chained hash of the secret key data. Since cryptographic hashes can't be reversed (by design) our key is still safe, but other secrets on the system can then be bound to PCR 9. But what does PCR 9 now represent in terms of a root of trust? PCR 9's value expresses that the disk encryption key was released and then extended in the TPM, which in turn tells us that we completed a trusted boot. Binding other secrets to PCR 9 allows us to require a trusted boot without binding to any of the trusted boot PCRs. This gives us even more flexibility in terms of software and firmware updates on the system. Since the value in PCR 9 has no dependency on what software and config data makes up the trusted boot root of trust (it only depends on the parentkey1 key data itself), we only need to update the composite hash for parentkey1 after an update.
Using the TPM's PCRs is a powerful way to create and control roots of trust. The Achilles' heel here though is management. Your root of trust is likely to be different than mine. There's a lot of work to be done in terms of management software for TPM-based roots of trust. There are some emerging projects that are steps in the right direction. Open Attestation is a project from Intel that helps in monitoring the roots of trust for a set of machines. Its being integrated in various distros and has even been submitted to the OpenStack project as part of the Trusted Computing Pools effort. It'll be interesting to keep an eye on these projects in the months to come.