Roots of Trust are difficult

Post Syndicated from original https://mjg59.dreamwidth.org/66907.html

The phrase “Root of Trust” turns up at various points in discussions about verified boot and measured boot, and to a first approximation nobody is able to give you a coherent explanation of what it means[1]. The Trusted Computing Group has a fairly wordy definition, but (a) it’s a lot of words and (b) I don’t like it, so instead I’m going to start by defining a root of trust as “A thing that has to be trustworthy for anything else on your computer to be trustworthy”.

(An aside: when I say “trustworthy”, it is very easy to interpret this in a cynical manner and assume that “trust” means “trusted by someone I do not necessarily trust to act in my best interest”. I want to be absolutely clear that when I say “trustworthy” I mean “trusted by the owner of the computer”, and that as far as I’m concerned selling devices that do not allow the owner to define what’s trusted is an extremely bad thing in the general case)

Let’s take an example. In verified boot, a cryptographic signature of a component is verified before it’s allowed to boot. A straightforward implementation of a verified boot implementation has the firmware verify the signature on the bootloader or kernel before executing it. In this scenario, the firmware is the root of trust – it’s the first thing that makes a determination about whether something should be allowed to run or not[2]. As long as the firmware behaves correctly, and as long as there aren’t any vulnerabilities in our boot chain, we know that we booted an OS that was signed with a key we trust.

But what guarantees that the firmware behaves correctly? What if someone replaces our firmware with firmware that trusts different keys, or hot-patches the OS as it’s booting it? We can’t just ask the firmware whether it’s trustworthy – trustworthy firmware will say yes, but the thing about malicious firmware is that it can just lie to us (either directly, or by modifying the OS components it boots to lie instead). This is probably not sufficiently trustworthy!

Ok, so let’s have the firmware be verified before it’s executed. On Intel this is “Boot Guard”, on AMD this is “Platform Secure Boot”, everywhere else it’s just “Secure Boot”. Code on the CPU (either in ROM or signed with a key controlled by the CPU vendor) verifies the firmware[3] before executing it. Now the CPU itself is the root of trust, and, well, that seems reasonable – we have to place trust in the CPU, otherwise we can’t actually do computing. We can now say with a reasonable degree of confidence (again, in the absence of vulnerabilities) that we booted an OS that we trusted. Hurrah!

Except. How do we know that the CPU actually did that verification? CPUs are generally manufactured without verification being enabled – different system vendors use different signing keys, so those keys can’t be installed in the CPU at CPU manufacture time, and vendors need to do code development without signing everything so you can’t require that keys be installed before a CPU will work. So, out of the box, a new CPU will boot anything without doing verification[4], and development units will frequently have no verification.

As a device owner, how do you tell whether or not your CPU has this verification enabled? Well, you could ask the CPU, but if you’re doing that on a device that booted a compromised OS then maybe it’s just hotpatching your OS so when you do that you just get RET_TRUST_ME_BRO even if the CPU is desperately waving its arms around trying to warn you it’s a trap. This is, unfortunately, a problem that’s basically impossible to solve using verified boot alone – if any component in the chain fails to enforce verification, the trust you’re placing in the chain is misplaced and you are going to have a bad day.

So how do we solve it? The answer is that we can’t simply ask the OS, we need a mechanism to query the root of trust itself. There’s a few ways to do that, but fundamentally they depend on the ability of the root of trust to provide proof of what happened. This requires that the root of trust be able to sign (or cause to be signed) an “attestation” of the system state, a cryptographically verifiable representation of the security-critical configuration and code. The most common form of this is called “measured boot” or “trusted boot”, and involves generating a “measurement” of each boot component or configuration (generally a cryptographic hash of it), and storing that measurement somewhere. The important thing is that it must not be possible for the running OS (or any pre-OS component) to arbitrarily modify these measurements, since otherwise a compromised environment could simply go back and rewrite history. One frequently used solution to this is to segregate the storage of the measurements (and the attestation of them) into a separate hardware component that can’t be directly manipulated by the OS, such as a Trusted Platform Module. Each part of the boot chain measures relevant security configuration and the next component before executing it and sends that measurement to the TPM, and later the TPM can provide a signed attestation of the measurements it was given. So, an SoC that implements verified boot should create a measurement telling us whether verification is enabled – and, critically, should also create a measurement if it isn’t. This is important because failing to measure the disabled state leaves us with the same problem as before; someone can replace the mutable firmware code with code that creates a fake measurement asserting that verified boot was enabled, and if we trust that we’re going to have a bad time.

(Of course, simply measuring the fact that verified boot was enabled isn’t enough – what if someone replaces the CPU with one that has verified boot enabled, but trusts keys under their control? We also need to measure the keys that were used in order to ensure that the device trusted only the keys we expected, otherwise again we’re going to have a bad time)

So, an effective root of trust needs to:

1) Create a measurement of its verified boot policy before running any mutable code
2) Include the trusted signing key in that measurement
3) Actually perform that verification before executing any mutable code

and from then on we’re in the hands of the verified code actually being trustworthy, and it’s probably written in C so that’s almost certainly false, but let’s not try to solve every problem today.

Does anything do this today? As far as I can tell, Intel’s Boot Guard implementation does. Based on publicly available documentation I can’t find any evidence that AMD’s Platform Secure Boot does (it does the verification, but it doesn’t measure the policy beforehand, so it seems spoofable), but I could be wrong there. I haven’t found any general purpose non-x86 parts that do, but this is in the realm of things that SoC vendors seem to believe is some sort of value-add that can only be documented under NDAs, so please do prove me wrong. And then there are add-on solutions like Titan, where we delegate the initial measurement and validation to a separate piece of hardware that measures the firmware as the CPU reads it, rather than requiring that the CPU do it.

But, overall, the situation isn’t great. On many platforms there’s simply no way to prove that you booted the code you expected to boot. People have designed elaborate security implementations that can be bypassed in a number of ways.

[1] In this respect it is extremely similar to “Zero Trust”
[2] This is a bit of an oversimplification – once we get into dynamic roots of trust like Intel’s TXT this story gets more complicated, but let’s stick to the simple case today
[3] I’m kind of using “firmware” in an x86ish manner here, so for embedded devices just think of “firmware” as “the first code executed out of flash and signed by someone other than the SoC vendor”
[4] In the Intel case this isn’t strictly true, since the keys are stored in the motherboard chipset rather than the CPU, and so taking a board with Boot Guard enabled and swapping out the CPU won’t disable Boot Guard because the CPU reads the configuration from the chipset. But many mobile Intel parts have the chipset in the same package as the CPU, so in theory swapping out that entire package would disable Boot Guard. I am not good enough at soldering to demonstrate that.

comment count unavailable comments