TPM2 Key Trust: where did Keylime go wrong
In my previous blog post , I explained how a verifier can get a signing key that it trusts is on a TPM for attestation (part 2 of the other post in the making).
I have been contributing to a specific implementation of remote attestation for Linux, called Keylime .
As part of the effort on porting the agent to Rust, I was looking into how the process works, and as part of that I identified a vulnerability in how Keylime deals with the TPM2 that breaks the Chain of Trust in two different places.
For the quick rundown, see the advisory . For details, please read on!
Keylime Agent Registration Protocol
When a new keylime agent is started, it will register itself to the Registrar. This happens in two distinct steps, each being an individual HTTP request: During the registration step, the agent sends its Endorsement Key (EK) and corresponding certificate, and an Attestation (Identity) Key (AK/AIK, the signing key from the previous article). The response to this is the encrypted challenge as described in the previous article. During the activation step, the agent decrypts the challenge, computes an hmac of its agent UUID with the challenge as key, and submits that to the Registrar.
One thing to point out is that for this entire process, we should consider everything the agent sends to be untrusted until verified: verifying that the agent is running correct code is the whole point of Keylime!
Chain of Trust break 1: Endorsement Key(s?!)
The first thing I noticed when looking at the agents’ registrar client was that it is passing three different things that all have a name prefixed with “ek”: “ek”, “ekcert” and “ek_tpm”. So my first challenge was to determine what all three were doing there.
It turns out that “ek” was the public key of the Endorsement Key, in SubjectPublicKeyInfo PEM format, the “ekcert” was the Endorsement Key Certificate (in DER format), and “ek_tpm” is the Endorsement Key, but then in the TPM proprietary format.
This means that technically, the same public key is sent in three different encodings: once as a raw ASN.1 SubjectPublicKeyInfo structure (read: “public key”), once as part of the EK Certificate, and once in the proprietary format.
This immediately sets off alarm bells: which of the versions would be used for verifying the chain of trust?
After some digging, it turns out that the “ekcert” is used to determine whether the TPM is indeed from a trusted vendor. The “ek” value (also known as “ekpem” or “pubek”) is verified to contain the same public key as “ekcert” if a cert was provided. (There is an option to make Keylime not use the certificate to establish trust, but instead have a script to validate the raw endorsement public key in case you want to whitelist EK’s for example.)
This leaves the “ek_tpm” version: that is used during the actual Credential Protection protocol to encrypt the challenge against. This is done because the tpm2_makecredential tool that it is using for this purpose requires the TPM proprietary format for the endorsement key.
However, and this is the critical part: never in the code is there any check performed that the public key in “ek_tpm” is the same public key that’s in “ek” or “ekcert”! This means that it would be possible for an attacker to generate a new key in system memory (unprotected), and send that as “ek_tpm” (in the TPM format) together with a valid (but unrelated) “ek” and “ekcert” from any random TPM, and the registrar would just assume that the verified key is correct.
As a fix for this issue, we changed the code on the client to only send the ekcert if it has one, and otherwise send the ek in TPM format, but nothing else. The registrar will ignore any “ek” (PEM format) provided by the agent, and if the agent provided an “ekcert”, it will ignore any provided “ek_tpm” value. If only “ekcert” was provided, the registrar will build the “ek_tpm” value itself. This means that when the certificate is validated later, the “ek_tpm” is indirectly validated (because it is produced by trusted code), and now the trusted “ek_tpm” value is passed into the Credential Protection protocol.
Chain of Trust break 2: Attestation Key: who are you?
The second part that I noticed in the same registrar client was that it sends both an “pub_aik” and a “aik_name”. The “pub_aik” is a PEM encoded version of the SubjectPublicKeyInfo of the Attestation (Identity) Key, and the “aik_name” is the hexadecimal encoded TPM Name of the Attestation (Identity) Key.
This again is a case where the aik_name was sent additionally because that’s what’s required for tpm2_makecredential, and the “pub_aik” was needed to verify quotes from the agent as part of the [attestation](TODO: Link to attestation post) protocol.
However, there is no check done between these two representations, and neither are any checks performed regarding the attributes of the Attestation Key.
This would mean that an attacker would be able to generate a random key in system memory, and send that to the registrar as the “pub_aik”, but providing the “aik_name” of an object that is on an actual, trusted TPM. (Or, in combination with the previous part, any byte stream, since there’s no validation that the Endorsement Key used for the activation step is on a TPM.)
There additionally were no checks on the object attributes of the Attestation Key, which means that even if an attacker sends the name of a key that is on a valid TPM, it might be a key where the key material was provided from outside the TPM. So it would be possible to have an AIK that happens to be loaded into a TPM at this moment, but whose private key is available outside of the TPM, to the main operating system.
As a fix for this issue, the agent now no longer sends the “pub_aik”, or the “aik_name”. Instead, it only sends the TPM representation (“aik_tpm”) of the Attestation Key.
With this, the Registrar will first verify that the object attributes are as expected for an Attestation Key (non-exportable (FIXED_TPM & FIXED_PARENT) , key generated by the TPM (SENSITIVE_DATA_ORIGIN), etc).
After that, the registrar will compute the Attestation Key’s Name for the Credential Protection protocol, and use this instead of a client-provided value.
After this, the aik_tpm is stored, and any time that a quote by the TPM is checked, this will be done against a PEM public key synthesized from the aik_tpm value.
This means that we verify the object attributes of the Attestation Key, and that we have cryptographically verified that the key we store as valid is indeed the one proven to be on a TPM as proven by the Credential Protection protocol.
These issues have been assigned CVE-2021-3406.