TPM2 Attestation Keys
Part 1 of a 2-part series on TPM attestation
Background
These days, the Trusted Platform Module (TPM) is a pretty ubiquitous piece of hardware. This is thanks in part due to Microsoft requiring it [since 2016 for Windows 10] (https://docs.microsoft.com/en-us/windows-hardware/design/minimum/minimum-hardware-requirements-overview#37-trusted-platform-module-tpm) .
The TPM enables very interesting security features, like decryption/signing of data, key exchange protocols, and more, without handling the private key in software.
One of the other big things a TPM can be used for is attesting a server to a remote server. This allows a remote server to verify that the software that booted your computer, or is running on it, is known (unmodified) software. This can be used for example by companies to check their servers, whether their systems have booted with the correct BIOS and configuration, and whether the system files are not modified, before you provide it with access to certain things like TLS/HTTPS keys.
This attestation procedure is what this blog post series is about.
In this post, “attester” is the system that is trying to prove to the “verifier” that it is running valid code.
This blog post talks about some of the core features in a TPM, and how a verifier can use those features to verify that a public key is stored on the TPM (and only a TPM) that is trusted.
TPM Feature: key storage and operations
The TPM is able to hold cryptographic keys and use those keys for various operations. So it can do things like signing things with an asymmetric RSA or ECC key.
These keys can either be imported or generated by the TPM itself, and they have various attributes that the TPM stores and returns about their properties, like whether the key was generated by the TPM, and whether the key is exportable (as in, you can get the private part for this key out of the TPM).
Keys (and other objects in the TPM) also have a “Name”. For keys, this Name is a digest over the TPM representation of the keys, which includes their attributes. This means that two keys with the exact same public numbers (i.e. RSA modulus and exponent) may still have a different name, if one is for example non-exportable and one is exportable.
Endorsement key
One specific private key that the TPM has[1] is the Endorsement Key, which is a key for which it also has a corresponding x.509 certificate issued and signed by the TPM vendor. This certificate can be used by the verifier to ensure a key is in a TPM by a vendor it trusts.
However, this key is restricted by the TPM, in a way where it can only be used for decryption. This means that it is impossible to make signatures with this key. This means that we can’t use it for signing data coming out of the TPM, as we would use in an attestation flow (post for that upcoming) or other use cases.
So, how do we now get a key that the verifier can trust is stored in a trusted TPM in a non-exportable way that we can use for signing things?
Credential Protection protocol
Here comes in: the Credential Protection protocol that is part of the TPM 2 specification!
As part of this protocol, the attester sends two things: the Endorsement Key public key (and maybe its certificate), and the public key and object attributes of the signing key.
The verifier checks that the object attributes for the signing key are Acceptable for its purposes (i.e. non-exportable (FIXED_TPM & FIXED_PARENT) , key generated by the TPM (SENSITIVE_DATA_ORIGIN), etc). It can then compute the Name of the signing key by hashing the TPM structure representing the signing key.
Then the verifier generates a challenge (random byte string). It then computes a symmetric encryption key from the Name of the signing key and a seed (a random number), and encrypts the challenge with that symmetric key, and encrypts the seed for the public key [2] and sends those to the attester.
Now, the attester sends the encrypted Credential structure and the encrypted key to the TPM, providing the handle (reference) to the signing key object in the TPM. The TPM decrypts the seed, and computes the Name of the provided signing key. If the Name matches the value the verifier used, it can compute the same symmetric encryption key that the verifier used, and is then able to decrypt the challenge from the credential.
The attester can then return the challenge back to the verifier as proof that the TPM did indeed have an object with that Name loaded into it, proving that the signing key is indeed on a TPM with the expected attributes.
This terminates the protocol, and at this moment, the verifier is now sure that the public key it got earlier is indeed a valid signing key that is created by a TPM and can not be exported out of the TPM.
Note that because of how this is oriented, after the protocol has concluded, the verifier can’t verify anything about the signing key anymore, so it will have to trust that it previously validated the key correctly before storing it.
But assuming that’s in place, we now have a public key that we can possibly trust, and use for remote attestation (look out for part 2 of this series for information on that).
Footnotes
[1]: Technically keys are (usually) not stored but reproduced every time, but I’m trying to not go into the full depth to keep the blog post somewhat readable.
[2]: It generates a new seed, and then derives the symmetric key and HMAC keys, but again, too technical.