TPM Tutorial
Introduction
You want to learn to use the TPM 2.0? Then you’re right here! In this tutorial you will get an overview over the TPM using the tpm2-tools.
The TPM (Trusted Platform Module) is a cryptographic processor which is part of most modern motherboards. If your computer runs Windows 10, it certainly contains a TPM 2.0. For those unfamiliar, the TPM provides out-of-band general cryptographic, storage, policy and key management operations (among other things).
Getting Started
Of course we need a TPM. If you do not have a TPM, don’t fret. In this tutorial we will use the TPM simulator. Just download the source code and build it.
Now we install the other dependencies:
Hello World
First, let’s run the TPM simulator. This will open two TCP sockets. Keep in mind that we can use Wireshark to capture the traffic if needed.
localhost:2321
: listening for TPM commandslocalhost:2322
: listening for platform commands
Now, let’s run the resource manager which will connect to the TPM simulator.
Note that is has to be started as user tss
for security reasons. Internally,
the library libtss2-tcti-tabrmd.so
will be used to send commands to the
resource manager.
Now we are ready to call our first TPM tool! After each TPM power cycle (e.g.
after a reboot of the PC), we need to issue the command tpm2_startup
. If the
TPM is already ready, we will get an error 0x00000100 which we can simply
ignore.
The next TPM coommand is tpm2_getrandom
which will give us a specified number
of random bytes. These bytes are truly random.
Creating Objects
All TPM 2.0 entities are referenced with a handle (e.g. 0x8000001
). A
subset of these entities are the so-called objects: keys or data. How to
create and use objects is covered in this section.
Hierarchies
Before we create an object, it’s important to understand the concept of hierarchies. The TPM 2.0 has 4 hierarchies, they are:
- platform - used by firmware and the OS.
- owner - used by the owner (this is the one we will be using).
- endorsement - used for privacy sensitive keys, these keys are known to come from a valid TPM and platform.
- null - reset on every boot, good for transient session keys.
Each hierarchy is essentially a static seed value (with the exception of the null hierarchy, which is a new seed on every boot). This seed is used to create one or more primary objects. Since hierarchies are also TPM entities, they can be referenced by handle, as well.
Every hierarchy has its own authorization value which is basically a password. We will dive into authorization later.
Primary Objects
Under a hierarchy, one can create one or more primary objects. These objects do
not persist across reboots by default. However, a primary object created with
the same inputs under a given hierarchy will produce the same exact key (with
the exception of the null
hierarchy, the seed changes each boot). Primary
objects have special attributes and thus are not intended for general purpose
application. To add a primary object to a hierarchy requires the hierarchy
authorization value.
Usually, one of the first thing to do after the TPM startup is creating a
primary key. The following command creates an primary RSA key under the owner
hierarchy (-C o
) and saves the key context in the file primary.ctx
. Note
that this key has no parent and thus cannot exist outside the TPM.
You might ask yourself why we did not need to authorize to the owner hierarchy
(i.e. pass the password). By default the auth. value of the owner hierarchy is
empty for the TPM simulator. If there was a non-empty password, we would have
passed -P <password>
.
Creating an Object
As you might have guessed, a hierarchy is a hierarchy of objects. That means each child key is encrypted (i.e. wrapped) by its parent key. Creating parent objects requires specific attributes which will be covered shortly.
We can also create data objects. Again, they are encrypted (i.e. sealed) by
its parent object. We can specify either a file (-i <file>
) or reading from
stdin (-i-
). Note that data objects are indeed a part of the hierarchy they
were created in.
To recover this data we use tpm2_unseal
.
I explained earlier that each entity inside the TPM is represented by a handle.
Well, the resource manager handles saving and loading object contexts (the data
in the .ctx
files) for us. That means we do not care about object handles.
However, if we want, we can display information such as the internally used
handle (and e.g. the hierarchy) of an object using tpm2_print
.
Object Attributes
You probably noticed that the the commands to generate objects
(tpm2_createprimary
, tpm2_create
) print some information about the generated
entity.
In this example we generated an RSA key with a length of 2048 bits. Additionally we see a number of attributes. The most important attributes are:
- fixedtpm - key cannot be duplicated at all
- fixedparent - key cannot be duplicated to a different parent
- sensitivedataorigin - sensitive data (private key) is/will be generated by the TPM
- restricted - key can only operate on special data (needed for parent keys)
- decrypt - the private key can be used to decrypt (needed for parent keys)
- sign - the private key can be used to sign data
- noda - multiple failed authorization attempts will not result in the lockout mode (see Dictionary Attack)
Note: if the key is restricted, either decrypt or sign (but not both) must be set.
//TODO what’s the requirements for parent objects?
Importing Keys
Often, you want to import an externally generated key into the TPM. This can
be archieved by using the tpm2_import
command.
Alternatively, an externally generated key can be loaded without being encrypted
(wrapped) by a parent key. Usually, this key is associated with the null
hierarchy (-C n
). If there is no sensitive portion (e.g. only a public key),
the key can be associated with another hierarchy. However, it will never be
wrapped by a parent key.
Signature Generation and Verification
Signing using an asymmetric key is rather easy. Note however that the sign attribute of the key has to be set.
TPM only
Now the signature can be verified using the same key.
Using OpenSSL
By default, the signature written to the specified file in a specific format
called tss
. To be able to verify this signature using other means such as
OpenSSL, we need to specify another format: -f plain
. Additionally, to be
able to use the public key with OpenSSL, it needs to be saved in PEM format.
Signing with the TPM, verifying with OpenSSL:
Of couse we can also sign with OpenSSL and verify using the TPM. Note that in this case loading the public portion of the key into the TPM is enough for the signature verification. To spice things up a little bit, we use Elliptic Curve Cryptography (ECC) keys.
Signing with OpenSSL, verifying with the TPM:
Encryption and Decryption
// TODO
Persistance
By default, every key we created (or imported) is a transient object. This means it’s in the TPM’s volatile memory and will be lost after a reboot (or a TPM reset). To take advantage of the TPM’s non-volatile memory (NV), we have to instruct the TPM to store it persistently. This enables us to use keys without having them stored on the computer’s file system (because the file system is not available in early stages of boot or because the platform is a microcontroller without any file system at all).
In this example we already chose a persistent handle: 0x81000000
. This handle
is used to reference the persistent onject. If we did not specify a handle, the
TPM would have assigned a handle.
We also learned that there is a way of listing the handles of all persistent
objects using the tpm2_getcap
(get capability) tool.
NV Space
// TODO
Authorization
Until now, we did not worry about authorization. Obviously we do not want our keys to be accessible by anyone. To protect our secrets, the TPM offers three kind of sessions:
- password session
- HMAC session
- policy session
Authorization Roles
// TODO
Password Session
Password authorizations are the simplest authorizations. Every object has an auth. value which is by default empty. If the auth. value is specified during object creation or changed afterwards, the entity can only be used with authorization.
The TPM provides a special password session which does not need to be started
and does not maintain any state. It is used to send a password in plaintext to
the TPM alongside a command. For the tpm2-tools, to authorize an entity you
usually pass -p <password>
. If an object’s parent is to be authorized, an
uppercase -P <password>
is passed.
The auth. value can be set during creation of a TPM entity or later on.
Commands to set the auth. value during creation:
tpm2_createprimary
for primary objectstpm2_create
for any other objecttpm2_nvdefine
for NV indices
command to set the auth. value later:
tpm2_changeauth
for transient and persistent objects, hierarchies and NV indices
In the following example, the authorization of a key object is demonstrated.
TPM objects need to be loaded after a call of tpm2_changeauth
.
HMAC Sessions
Behind the scenes
// TODO
The TPM’s memory is quite constraint. To be able to export keys to the computers file system securely i.e. as an encrypted key context, we need the Access Broker and Resource Manager (ABRM). There are two notable implementations:
- The in-linux-kernel resource manager which is part of the TPM device
driver. Typically, the driver provides a character device
/dev/tpmrm0
. - The user space tpm2-abrmd which can be used with the TPM simulator and offers some features such as easy logging etc.