Firmware authentication, avoiding relay attacks
While the TOTP solution cleverly solves the replay attack, it’s still vulnerable to a relay attack.
An attacker could steal your laptop and leave behind an identical-looking malicious laptop. When you (unknowingly) boot the malicious relay laptop, it communicates out to your real laptop — which relays the 6-digit OTP code down to the malicious laptop. You verify that the 6-digit OTP is correct and type your FDE decryption password — which is relayed out to the attacker with your real laptop.
|context=[https://tech.michaelaltfield.net/2023/02/16/evil-maid-heads-pureboot/ Trusted Boot (Anti-Evil-Maid, Heads, and PureBoot)]
Authentication is not a new concept at all in computing. Many of the things we do on a daily basis, such as posting on forums, reading emails, and accessing work resources, require us to authenticate to a machine to prove we are who we say we are. Interestingly though, the reverse is rarely done machines rarely authenticate themselves to users. The machine refuses to trust the user until they prove that they are who they say they are, but the user blindly trusts that the machine is who it says it is and inputs sensitive login credentials to that machine. Most of the time, the machine is who it says it is, and so everything is fine, but every so often, you can end up with one machine pretending to be some other machine (for instance, if you click a link in a phishing email). If you provide login credentials to the wrong machine, the machine may be able to authenticate to the real machine as if it were you, providing the operator of the fake machine with access to your data or identity. To avoid this, it's of paramount importance that users have some way to authenticate the machine they are talking to before authenticating to it in return.
The primary scenario we are concerned with here is firmware authentication, although the principles here can apply to any authentication scenario. The primary question is, how can a user know that the machine they are using is running firmware they trust, and how can they know that they are actually using the machine they think they are?
First, we have to establish the threat model. We assume that the user is in a potentially hostile environment. They have to leave a device unattended for some duration of time and intend to continue using the device after they return. During the time they are away, an attacker has unrestricted physical access to the device, where they can freely read or modify any components on the system that can be read or modified, including the firmware. The user needs to be able to establish trust in the device when they return, so they can continue using the device safely after the attacker has had access to it. If the attacker has modified the system's state, they must be able to reliably verify that their machine is corrupted so they can discard it.
In this scenario, how can the user know that the firmware is uncorrupted? A simple way would be to open the machine, read the firmware off the chip, and compare it to a known-good version. However, this is a very difficult task, requires access to a substantial amount of equipment, and consumes a large amount of time. It's analogous to a website owner driving to your house and looking at your ID before instructing their website to provide you access to your account; it's just not practical. Thus, we need some way for the firmware to be able to tell the user, "I am who you think I am," in a reliable fashion.
In order for this to work, we need the firmware to have some sort of shared secret with the user, which the user can verify the firmware has without having to actually transmit that secret over an insecure connection. (For avoidance of doubt, we are considering the machine's display to be an insecure connection, since the attacker can easily view it while tampering with the machine in the user's absence.) The easiest way to do this is via TOTP. The user can have a TOTP secret stored in their phone, and the firmware can have access to that same secret. The user can then compare the TOTP code generated by the firmware with the TOTP code on their phone, and verify that the device is authentic. We can't just embed the TOTP secret into the firmware or store it in a UEFI variable though, because the attacker may be able to read either of those. If they can steal the TOTP secret, they can swap the authentic machine for a fake replacement that can authenticate itself to the user.
To avoid this, the TOTP secret needs to only be accessible if the firmware is unmodified. The firmware can be proven to be unmodified if a cryptographic hash of it matches the cryptographic hash of trusted firmware, but we can't use this fingerprint as an authentication mechanism directly because it could easily be saved by an attacker and displayed to the user fraudulently (i.e., the firmware could show the user any hash it wants, it doesn't have to share its own hash). We need something other than the firmware that can verify the firmware, something the attacker cannot control.
Before we get to that point, let's just assume that we have something that can verify the firmware that is external to the firmware. This external verifier is then able to access a TOTP secret because of the firmware's validity, and it can prove to us that the firmware is unmodified by showing us a TOTP token generated from the secret. Our job is done, right? Well, not exactly, because the attacker has another nifty trick available to them, a relay attack. In essence, how do you *know* that the machine you're typing into is the machine that is receiving your keystrokes? How do you *know* the output you're looking at on the computer screen is actually coming from the computer? You don't, you just assume it. This is a major problem since it means the attacker can steal your computer, put a fake one in its place, and then relay messages between the two. When you boot the malicious computer, it can then send a signal to the real computer to generate a TOTP code. The original computer does so, and dutifully sends it to the malicious computer as if it were trying to authenticate to the user. The malicious computer then displays the TOTP code on the screen, thus fooling the user into thinking that the machine they're using is authentic. So that's another thing we need to grapple with, but first, we need to figure out how to verify the firmware externally.
On the surface, one might think that a hardware solution like Intel Boot Guard can work for firmware verification. This is basically Secure Boot for system firmware; it verifies signatures on firmware and only allows it to boot if it is authentic. Boot Guard is implemented on the CPU itself, in firmware that the attacker cannot read or modify. Because of its immutable and secret nature, Boot Guard can be assumed to be trusted (so long as the user trusts Intel), and so if it reports that the firmware is good, we can believe it. There's a bit more work that needs to be done to make sure the firmware is not only trusted by the machine, but is also the firmware the user expects. However, we don't need to go that far, since there's a major problem here. How do you know that Intel Boot Guard is actually being used? What if the attacker has swapped your CPU for some other x86_64 implementation that doesn't have Boot Guard at all, or that has a Boot Guard implementation with attacker-controlled keys? You could require the CPU to send you some signed random data that you can then verify the signature of, but now you have a relay attack to worry about (the malicious CPU could forward your request for identity verification to an authentic CPU, which would then send back the verification information to the malicious CPU, which would then present it as if it were an authentic reply). So this isn't going to work.
If you stop and think about it, this relay attack possibility is a big problem because every interface in the computer could theoretically just be a relay. Even the firmware itself could just be relayed, meaning the firmware chip might present itself to a verification routine one way and then present malicious code when it came time to actually execute it. Because the relay attack involves the transfer of actual, authentic data, the only hint the user has that they may be experiencing a relay attack is *positional* information. If the device they're talking to is not in the same location as the device they're receiving responses from, a relay attack is occurring. Because of the pervasive threat relay attacks pose, we need to temporarily change our goal in authentication. We don't need to verify the device's identity half so much as we need to verify the device's location.
The first question to ask ourselves is, what information can we send in order to verify the location of the device we're talking to? GPS coordinates are one option, but they require that the entirety of the GPS system (including the GPS receiver used by the user's authentic device) is trustworthy, accurate, precise, and functional. We can't prove that GPS is trustworthy, and it's easy for it to be inaccurate, imprecise, or non-functional if our authentic machine's GPS receiver is broken or we're in a location where GPS doesn't work at all (underground, for instance). It also can only pinpoint the location of a device on the surface of the earth, which is a severe problem if the authentic device is *under* a malicious device. So this isn't something we can use. Instead, we can use the same technique that is used by GPS for calculating location, which is observing time discrepancies between when a message is sent and when it is received.
When you send a message to a device, it takes some time for that device to respond to that message. The exact amount of time can vary, but it is usually measurable, especially when dealing with individual silicon chips with strict timing limits. If a device's response to a particular message is static or predictable, it's vulnerable to a replay attack, but if the response is dynamic, you can be sure that your message got all the way to the device you're querying, the device actually processed it, and then the response got all the way back to you. You can then time how long this entire cycle takes when you're talking to a device directly. Now imagine you have a relay in between you and the device you're querying. The message has to be transmitted to the relay, then to the device you're actually talking to, and then the response has to go through the relay to get back to you. No matter how short the distances involved are or how fast the equipment you're using is, this is going to add *some* delay that isn't present when querying the device directly. How much delay depends on the speed and location of the components in the relay and the device you're querying, but the delay *will* be longer than normal. (This is ignoring signal integrity and error correction concerns, both of which are irrelevant here because of the short distances and high reliability of communication when using a physical machine in-person. Signal integrity complicates things because if the two devices are far from each other, signals may be corrupted or lost in transit, requiring retransmission to correct. In this scenario, a relay can actually speed up the connection, because the signal between each device and the relay is more reliable than the signal between the two devices directly. WiFi extenders are good examples of this.)
Because of the behavior of delays here, we can use timing to authenticate a device's location. Send a signal, get the response, calculate the delay. A short delay indicates there is no unexpected relay, a not-short delay indicates there is an unexpected relay. What exactly "short" is depends on the device and the situation, and is something that would probably have to be calculated on a per-device basis. Not-short is anything significantly longer than short, where again "significantly longer" may vary from situation to situation and from device to device.
The nice thing about time-based location authentication is that we can combine it with traditional identity authentication. Send an authentication challenge, receive the response, time how long it took for the response to show up. If the response is invalid, or if it took too long to show up, authentication fails. Not only can you combine these two forms of authentication, you actually have to. Otherwise a malicious machine could authenticate its own position to you, but then relay the identity authentication challenge to the real machine. We now have an idea of how a relay-attack-proof authentication model would work.
The primary issue with this form of relay attack prevention is that modern electronics operate too quickly for this to be practical without specialized equipment. A human will generally miss a delay of 50ms or less, and if they're not paying extremely close attention they will almost certainly miss an 80 or even 100ms delay. I can type on my Bluetooth keyboard and see characters show up effectively instantly on my screen, despite there being a wireless link, a Bluetooth card, some chipset circuitry, the CPU and RAM, and a rather long DisplayPort cable in between the keyboard and the screen. All of these are acting as relays, yet despite the relay multitude I don't notice the delay. (It might be barely perceptible if I'm paying extremely close attention, but otherwise I never see it, and even when I do see it, it's so short that I doubt whether I see it or not.) Thus time-based location authentication is not suitable if you're trying to authenticate location without the need for specialized hardware. For now, we'll accept that this is the case, and continue on regardless.
So now, we can authenticate that the device we're talking to is where we think it is. Picking up where we left off before, we need some device that is immutable that is able to read the system's firmware and verify it, and this device needs to have a secret that cannot be read by an attacker, which can be used to verify its authenticity. Intel Boot Guard uses effectively immutable code (the code that the CPU runs is either baked into the chip in ROM or is signed by Intel so the chip can trust it when executing it). However, verifying the authenticity of an Intel CPU is not all that easy - you can do it with Intel TDX, but there isn't any feature in an Intel CPU to my awareness that can be used to verify the authenticity of the CPU very early in the boot process, before system firmware is even loaded. Thus Intel Boot Guard isn't really a good solution here.
There's a deeper problem here too, which is that unless we physically open the device and verify it looks like what we expect it to, it's entirely possible that the device we're verifying is not the device we're about to use. For instance, an attacker could give us a replacement computer with two motherboards, one with an authentic CPU and BIOS that would pass our location and identity authentication attempts, and another one connected to the I/O ports, mouse, keyboard, and screen that would steal our data as we did things. This is not easy to prevent without requiring an extreme amount of effort on the part of the user, and a large amount of code in our BIOS verification engine. The user would have to somehow authenticate the BIOS using a mechanism that ensured the BIOS being verified was in control of the mouse, keyboard, all I/O ports, and screen, and even then a malicious device could "take over" after authentication was complete. Requiring the user to authenticate the system twice, once during boot and once after boot, it a major pain, and dealing with all of the different ports and the screen and input devices just isn't practical. At this point we can say that it's highly impractical to keep a computer reliably secure with the threat model defined earlier.
Instead of this, we need to protect the hardware and firmware from being tampered with in the first place, using authentication routines only to verify that the device is genuine. There are solutions for this (Design Shift's ORWL computer is a good example, and glitter nail polish can be used to prevent laptop screws from being removed without leaving evidence of tampering). This makes it reasonable to assume that preventing tampering at the outset is feasible. Now we can adjust our threat model. We assume the user is in a potentially hostile environment and must leave their device unattended for some duration. They plan to continue using the device upon their return. During their absence, an attacker has unrestricted physical access to the device but cannot open the device's case. The attacker can only interact with the device via its I/O ports, screen, and input devices. Ideally, the attacker would be unable to boot an operating system from a USB drive. However, for simplicity and to maintain defense-in-depth, we assume the attacker *can* boot the system using an OS of their choice, which enables them to read and write the firmware. Consequently, we cannot store a secret in the firmware itself or assume the firmware will remain unaltered. Nevertheless, we can store a secret in the TPM, and we assume that an external verification device of some sort will function correctly. Additionally, we can embed a secret within the verification device or seal it in a TPM.
At this point, assuming Intel and AMD do not allow malicious firmware to be signed, we can rely on Intel Boot Guard or AMD Platform Secure Boot to prevent malicious firmware from being installed, provided it is properly implemented. Under these conditions, we can trust the firmware to measure itself into the TPM, enabling the unsealing of a TOTP secret or another secret that can be used to authenticate the device to the user. (Why not blindly trust the firmware at this point? Because we are authenticating the ''device'' to the user, not merely the firmware. If authentication passes, it indicates that we have the device we think we have. Assuming our device has Boot Guard functioning correctly, we can infer that the device's firmware has been authenticated by Boot Guard.) With this in place, we can utilize a high-speed device, connected via a serial or perhaps USB interface, to manage authentication. This device can send an authentication challenge, receive a response, measure its timing, and verify it. If the response is both valid and received quickly enough, we can trust that the device and its firmware are authentic.
To make this process user-friendly, an OEM can embed an authentication secret into the TPM during hardware manufacturing and provide the secret to the user over a secure channel (e.g., via a GPG-encrypted email). The user can then program this secret into their phone or another hardware device, which can subsequently be used to authenticate their computer.
Android has a different solution to avoiding relay attacks called "key provenance attestation", which appears to let you verify that a particular device is also in possession of a non-exportable encryption key that is used for establishing a connection to a remote service. Basically this means that not only is the device's ID verifiable, it's also possible to verify that the device itself is the device being used to take privileged actions. This is useful for avoiding relay attacks in a remote access scenario (even if there is a relay, all it can do is shuttle encrypted data back and forth, it therefore becomes benign), but is unsuitable for a local scenario. This is because it would require all communication between the user interface devices (mouse, keyboard, I/O ports, screen) and the machine in possession of the authentication secret to be encrypted. Additionally, the user would have to remain in possession of all of those I/O devices and not leave them unattended, so they could remain trusted. That doesn't seem at all practical.
Further reading and discussion:
* https://github.com/linuxboot/heads/issues/1881
* https://mjg59.dreamwidth.org/70630.html
TODO: So how do we verify the firmware externally? Flashkeeper shows some promise here, but it has issues we would prefer to avoid. What other options do we have? Do we need custom silicon for this? The device will need to handle a challenge-response process via high-speed signature or TOTP verification. It must then verify the firmware, which, in turn, must pass a high-speed signature or TOTP verification using a secret unsealed by the TPM. Or... perhaps the verification engine and the firmware chip could be combined into a single component? The firmware chip could include a verification engine that authenticates the firmware first. If the firmware is confirmed authentic, the chip would respond positively to a high-speed identity challenge; otherwise, it would respond negatively. Such a device would need to be resistant to TOCTOU (Time of Check to Time of Use) attacks. This means it must verify the firmware as the system boots it, not merely before the system begins booting.
TODO: Are there CPU features in Intel or AMD hardware that can be used to verify the authenticity of the CPU in a cryptographically secure way before the first instruction is executed?
TODO: Would it be possible to verify the firmware via an external port? Then the location authentication could be done by measuring the speed of reading the firmware from the external port, and the device doing the measuring would be physically held by the user and thus trusted. It would check the firmware signature and display a notification about whether it passed or not. The problem with this though is that an attacker can swap in a malicious device with the authentic firmware connected to the verification port, and the malicious firmware on a different chip that the system actually boots from. This defeats the security entirely. This same kind of attack could even defeat a scheme that solved all the problems above - ship two motherboards in one device, one with authentic firmware and hardware that authenticates itself to the user, one with malicious firmware that is connected to the keyboard, screen, and storage drives. Need to figure out some way around that.
TODO: Is there some other way of authenticating location without having to use time? It's very frustrating to require a dedicated piece of hardware for this. GPS isn't a good choice, but maybe something else would work. Alternatively, could a phone be able to handle the time measurements with sufficient precision and accuracy to be usable without needing a special authentication key?
TODO: The 3mdeb founder seems to think D-RTM can help with relay attacks: https://tech.michaelaltfield.net/2023/02/16/evil-maid-heads-pureboot/#comment-43489 It's not totally clear to me how that would work though, it might allow attesting the OS but how would it allow verifying the security of the firmware? Or is the point that the security of the firmware no longer matters at that point because D-RTM has forced everything to a secure state?
TODO: Does heads firmware mitigate this? Heads firmware feature request: [https://github.com/linuxboot/heads/issues/1881 mitigate relay attacks #1881]
TODO: Does the Librem key have measures to avoid relay attacks? How does it work? (I would guess it probably just times the response.)