Preface
Motivation
When I started my journey with TPMs (Trusted Platform Modules), I experienced two conflicting emotions: enthusiasm and confusion
. Indeed, the principle of TPM seemed brilliant to me, but I struggled to understand how I could actually put it into practice.
After several months of perseverance, and thanks to the valuable work of the community (i.e. tools, blog posts, etc.), I reached a level of understanding that could be considered acceptable. In hindsight, I can clearly say that my learning cost was too high, but it doesn't necessarily have to be for everyone.
That is why, I am taking on the challenge of producing a (relatively) comprehensive introduction to the subject. In short, the content I would have dreamed of having when I started this journey.
What's TPM Pills
?
TPM Pills
is a direct tribute to Nix Pills
, who has helped many people discover the nix language!
A series of articles that gradually introduce the key concepts about a TPM. The goal is that by the end of reading TPM Pills
, you will have a solid understanding of the functionalities offered by a TPM in order to reduce the complexity of TPM 2.0 specification1 if you find yourself reading it. Additionally, each article will be accompanied by a reproductible example to make things more concrete.
Note:
TPM Pills
will refer a bunch of time to the TPM 2.0 specification. Knowing that the latter evolves over time, we will use the latest version2 available at the time of writing.The spec is also available in here in the repository.
Finally, it is important to emphasize that this content is free.
Who is this for?
To anyone who wants to understand TPM and its functionalities. Whether you are a developer, a security expert, or just curious, you will find something to satisfy your curiosity.
A developer background is recommended especially for the implementation part.
Other educational resources
If you want to explore the topic further or if the TPM Pills
approach simply doesn't suit you, be aware that there are other alternatives:
Resource | Description | Format |
---|---|---|
A Practical Guide to TPM 2.0 | At the time of writing, the most comprehensive book on the subject (my bedside book)! Note: PDF format is free | Book |
Trusted Platform Module (TPM) courses | Note: courses are free | Online course |
TPM.dev tutorials | To share developer-friendly resources about Trusted Platform Modules (TPM) and hardware security, including other Hardware Security Modules (HSM). Note: description from the repo | Tutorials |
TPM-JS by Google | TPM-JS lets you experiment with a software TPM device in your browser. It's an educational tool that teaches you how to use a TPM device to secure your workflows. Note: description from the repo Warning: the repo is archived since 2022 | Tutorials |
TPMCourse by Nokia | A short course on getting started with understanding how a TPM 2.0 works. In this course we explain a number of the features of the TPM 2.0 through the TPM2_Tools through examples and, optionally, exercises. Note: description from the repo | Tutorials |
Table: Educational Resources
Who Am I?
I'm Loïc Sikidi a passionate software engineer from France. I love to learn and share my (little bit of) knowledge with others.
I'm far from being an expert on the subject, but I want to contribute to the democratization of this technology because I'm convinced that the TPM is a powerful tool that can help us to build more secure systems.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion 💬
-
The specification available here is a dense and relatively complex document. ↩
-
Version 184 released on March 2025 ↩
Why TPM is super dope?
Introduction
What is a TPM (Trusted Platform Module)? It's a secure cryptoprocessor — a piece of hardware — capable of performing cryptographic operations. Like a HSM1, a Smart Card2, or a YubiKey, a TPM must be considered as a secure enclave — a device able to create keys without allowing them to be exported, which is highly beneficial from a security perspective.
Where a TPM shines is in its ubiquity! Indeed, it can be found almost everywhere: PCs, servers, network gear, an increasing number of embedded systems, and even in the cloud. Therefore, it's very likely that you can already take advantage of it without spending a dime.
For example, if you have developed the bad habit of storing your SSH key pairs in plaintext (i.e.
~/.ssh/
), be aware that you can generate them with a TPM to protect yourself from data theft if your filesystem is compromised.TipIf you want to learn how: take a look at ssh-tpm-agent repo and its companion blog post.
This functionality (highly important as it is) is actually just the tip of the iceberg. The TPM standard aims to address a root of trust issue through the principle of Hardware Root of Trust (HRoT). This standard operates on the premise that it is safer to base certain low-level functions on hardware rather than software.
For example, this concept can be applied during a machine's boot process, where measurements are transmitted to the TPM to verify its integrity3.
These measurements could be stored in memory or on the filesystem, but that would allow a malware to alter them. Conversely, the TPM is a standalone component 4 dedicated to this task and incapable of executing code from external sources (e.g. malware).
Similarly, a TPM solves the issue of storing a secret on disk. Historically, this problem was simply shifted, leading to the following situation: « I will encrypt my secret with a symmetric key 💡 but where should I store this key?! 🤯».
Fortunately for us, the TPM elegantly solves this problem by providing the encryption key (which is not exportable).
This type of principle is used, for example, by BitLocker or systemd (i.e. systemd-cryptenroll).
It is even possible to combine these two concepts and ensure that the unseal
mechanism is only allowed if the machine is in a trusted state, thanks to integrity measurements. We will explore this in more depth in the upcoming pills, but this already gives you a glimpse of the vast capabilities provided by a TPM.
The spec has been produced and is maintained by a consortium called Trusted Computing Group (TCG)
.
TCG has also produced other specs that revolve around TPMs.
Different kinds of TPM
Note: this section quotes content from TPM 2.0: A Brief Introduction produced by TCG (Trusted Computing Group).
You need to have in mind that there are different kinds of TPMs, each with its own security level and cost. The choice of a TPM will depend on the security level required by the system and the budget allocated to it:
- Discrete TPM: « provides the highest level of security
[...]
. The intent of this level is to ensure that the device it’s protecting does not get hacked via even sophisticated methods. To accomplish this, a discrete chip is designed, built and evaluated for the highest level of security that can resist tampering with the chip, including probing it and freezing it with all sorts of sophisticated attacks. » - Integrated TPM: « is the next level down in terms of security. This level still has a hardware TPM but it is integrated into a chip that provides functions other than security. The hardware implementation makes it resistant to software bugs, however, this level is not designed to be tamper-resistant. »
- Firmware TPM: « is implemented in protected software. The code runs on the main CPU, so a separate chip is not required. While running like any other program, the code is in a protected execution environment called a trusted execution environment (TEE) that is separated from the rest of the programs that are running on the CPU. By doing this, secrets like private keys that might be needed by the TPM but should not be accessed by others can be kept in the TEE creating a more difficult path for hackers. In addition to the lack of tamper resistance, the downside to the TEE or firmware TPM is that now the TPM is dependent on many additional aspects to keep it secure, including the TEE operating system, bugs in the application code running in the TEE, etc. »
- Software TPM: « Software TPM can be implemented as a software emulator of the TPM. However, a software TPM is open to many vulnerabilities, not only tampering but also the bugs in any operating system running it. It does have key applications: it is very good for testing or building a system prototype with a TPM in it. For testing purposes, a software TPM could provide the right solution/approach. »
- Virtual TPM: « is part of the cloud-based environment and it provides the same commands that a physical TPM would but it provides those commands separately to each virtual machine. »
Trust Element | Security Level | Security Features | Relative cost | Use Case |
---|---|---|---|---|
Discrete TPM | Highest | Tamper resistant hardware | 💲💲💲 | Critical systems |
Integrated TPM | Higher | Hardware | 💲💲 | e.g. Network gear |
Firmware TPM | High | TEE | 💲 | Non-critical systems |
Software TPM | N/A | N/A | Free | Testing and prototyping |
Virtual TPM | High | Hypervisor | Cheap | Cloud environment |
Table: TPM classification
Conclusion
In this brief introduction, my goal was to present the key features that make the TPM an essential component for establishing a paradigm focused on security. It is also in this spirit that Microsoft requires a TPM 2.0 to install Windows 115 6 on a machine.
Up until now, this is the first time I mention TPM 2.0
. TPM 2.0
is simply the version that follows TPM 1.2
. Just consider that the second iteration was designed to address several issues and that it is now de facto the industry standard.
note: every time I use the term TPM, I always refer to TPM 2.0.
If you're interested in the differences between TPM 1.2
and TPM 2.0
, I recommend you to read this documentation provided by Microsoft.
Please note that in this first pill, I haven't mentioned all the features offered by a TPM (e.g., authorization system, auditing, etc.). However, those I believe important will be covered in next pills.
For the sake of impartiality, I must highlight the drawbacks inherent in using a TPM:
- Resources are limited (storage, memory)
- Cryptographic operations (e.g., key generation, signatures, encryption) are much slower than on other devices (due to the previous point)
- You have to be aware of many concepts to use or administrate TPMs properly - but don't worry,
TPM pills
will guide you through them!
What's about Apple devices?
Contrary to Microsoft and Linux, Apple made the decision to use a proprietary solution called Secure Enclave.
If you are a macOS user, you can still stay with us because most of the exemples will use a Software TPM which you will be able to run on your machine.
Next pill...
...we will setup a minimal environment to interact with a TPM in your running system.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion 💬
-
via a
Secure Boot
or aMesured Boot
. ↩ -
it has its own memory (RAM) and its own storage, although the resources are very limited. ↩
-
This decision by a tech giant led to a drop in TPM prices. ↩
Install tooling in Your Running System
The goal here is to prepare your environment to run the examples provided in TPM Pills
, if (like me) you only trust what you see with your own eyes. It's not a requirement. Indeed, you can limit yourself to read the content and code snippets. However, I strongly recommend you to read and run the examples to better understand the concepts.
Disclaimer regarding Windows
So far, my experience with TPMs has been exclusively limited to a Linux context — this is why, I am open to feedback from Windows users, if they encounter any issues.
Unfortunately, according to this issue, TPM is not added to WSL (Windows Subsystem for Linux), so it will be necessary to run commands from the host machine.
Prerequisites
TPM Pills
will require you to have the following tools (in addition to git
):
Tool | Description | Linux Support | Windows Support | MacOS Support |
---|---|---|---|---|
go >= v1.22 | A language that no longer needs an introduction | ✅ | ✅ | ✅ |
openssl | Crypto Swiss Army Knife which here is a dependency for using the Software TPM | ✅ | ✅ | ✅ |
swtpm | A Software TPM Emulator | ✅ | ✅ (using Cygwin) | ✅ |
tpm2-tools | A CLI (Command-Line Interface) for interacting with a TPM | ✅ | ❌ | ❌ |
Table: Tooling support per Operating System
tpm2-tools
is a great tool to have in your toolbox! However, since it is not available everywhere, it will be used sparingly.
Note:
PowerShell
provides some commands to interact with a TPM, but they will not be covered here.
Why go
?
Most educational content on the subject is in C... but why follow the crowd, right?!
More seriously:
- I am not an experienced C developer, but I am proficient in Go
go-tpm
provides a rich interface for communicating with a TPM- In the upcoming pills, we will make the TPM interact with a server in gRPC, and Go allows me to do this easily
- More and more projects in Golang ecosystem use TPMs (e.g., spire, sks, u-root, constellation, etc.)
Fundamentally, since the TPM 2.0
interface is a standard, all the concepts we will cover here are also applicable in other languages.
TPM 2.0 Clients
For those interested, here is a (probably non-exhaustive) list of TPM 2.0 clients.
Name | Language | Description |
---|---|---|
tpm2-tss | C | Intel implementation of TCG's TPM Software Stack (TSS). The current standard meter bar regarding TPM libraries. |
ibmtss | C | IBM implementation of TPM Software Stack (TSS) but not API compatible with TCG TSS. |
wolfTPM | C | TPM 2.0 librairy designed for embedded system. |
go-tpm | golang | |
tpm2-pytss | python | Wrapper of tpm2-tss . |
tpm-rs | rust | |
rust-tss-fapi | rust | Wrapper of libtss2-fapi which is an upper API provided by tpm2-tss named FAPI1. Warning: project's maintainers underline that the implementation is experimental and shouldn't be use in production. |
TSS.MSR | c#, c++, java, nodejs and python |
Table: TPM libraries
Installation
OCI
🚧 TBD 🚧
Nix
If you are a Nix user, TPM Pills
provides a Nix shell (i.e. shell.nix
) at the root of the repository.
To install dependencies, simply run the following commands:
git clone https://github.com/loicsikidi/tpm-pills.git
cd ./tpm-pills
# launch the derministic shell
nix-shell
# inside the shell
go version
tpm2 --version
Note: with this method
tpm2-tools
will only be installed on a Linux platform.
Devbox
For those who are unfamiliar, Devbox is a layer on top of Nix that allows you to obtain a deterministic shell without having to master Nix language.
If you are a Devbox user, TPM Pills
also provides a configuration (i.e. devbox.json
) at the root of the repository.
To install dependencies, simply run the following commands:
git clone https://github.com/loicsikidi/tpm-pills.git
cd ./tpm-pills
# launch the derministic shell
devbox shell
# inside the shell
go version
tpm2 --version
Note: with this method
tpm2-tools
will only be installed on a Linux platform.
Manually
- go: Use your preferred package manager or download the binary from the official website
- openssl: Use your preferred package manager or get the sources from the official website
- swtpm: Use your preferred package manager or build the sources by following the official documentation
- tpm2-tools: Use your preferred package manager or build the sources by following the official documentation
Example: validate TPM's version
Let’s finally get to the serious stuff! We will check the version of the TPM installed on your machine and ensure that it is a TPM 2.0
. We will able to do this by interacting directly with the TPM using a command called TPM2_GetCapability
.
On Linux, access to the Hardware TPM is secured by sudo rights. It is possible to have finer control using a udev
policy to allow specific users or groups to access it (e.g., the policy available in NixOS).
tpm2-tools
Only works on Linux.
# dependending on your config it might require 'sudo'
tpm2_getcap properties-fixed | grep -i pt_family_indicator -A 2
You should get the following output:
go
The script will works on all environments (on Darwin
, the code relies on a Software TPM).
Run the following command:
# dependending on your config it might require 'sudo'
go run github.com/loicsikidi/tpm-pills/examples/02-pill
# output:
# TPM Version: 2.0
Depending on your local setup, you can also run the following command:
# nix command
nix-shell --run "go run github.com/loicsikidi/tpm-pills/examples/02-pill"
# devox command
devbox run -- go run github.com/loicsikidi/tpm-pills/examples/02-pill
Next pill...
...we will see in much more details how we can interact with a TPM.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion 💬
-
Feature API ↩
How to interact with a TPM?
Introduction
It’s important to keep in mind that a TPM is primarily a passive device. By “passive”, I mean that a TPM will never act on its own—it always responds to a request. This behavior could be compared to a REST API, for example. The specification1 defines a set of commands (a little over a hundred) that a TPM must support, along with the associated interface contract.
In the previous pill, we used the TPM2_GetCapability
command to display the TPM version. This command is very useful because it provides a set of information about a TPM (static data) and its current state (dynamic data). The two libraries (tpm2-tools and go-tpm) that we used did more or less the same thing:
- The library created the
TPM2_GetCapability
command in accordance with the specification - The command was sent to the TPM2
- The command was received and interpreted by the TPM
- The TPM produced a response in accordance with the specification
- The response was sent back to the client2
Local Access
The content below focuses on Linux.
Let’s take a closer look at the transmission part (steps 2 and 5), as this is a topic that’s often overlooked. The diagram above describes a somewhat simplistic interaction. In reality, a TPM client never has a direct connection to a TPM, which, let’s remember, is a physical device. It's the operating system that provides an interface to bridge the userspace and the hardware.
The device is usually named /dev/tpm0
.
As we saw in pill #1, a TPM is characterized by being resource-efficient, which is why it does not natively manage multi-tenancy when several applications need to communicate concurrently with it. To address this issue, the TCG produced a specification (TCG TSS 2.0 TAB and Resource Manager) describing how to implement this logic.
tpm2-abrmd
is an implementation of that spec.
Adding this functionality unfortunately requires installing an additional tool on your machine and maintaining it in an operational state. Fortunately for us, starting from kernel version 4.123, the Resource Manager is a service directly integrated into the OS—which greatly simplifies integration. In summary, if your application communicates with a TPM, it will generally follow the pattern below:
As you’ve noticed, the OS provides a new device named /dev/tpmrm0
. It is preferable to use it to take advantage of the features offered by the Resource Manager.
By default, the data flow is unencrypted; however, we’ll see in a dedicated pill how it is possible to encrypt it.
Limitations
In most distributions, the devices dedicated to the TPM are owned by the root
user.
Screenshot from a NixOS Laptop
This has significant implications, as it requires your application to be run with root privileges—an unacceptable situation in many contexts (e.g., general public usage). By comparison, a Yubikey will ask the user to enter a PIN or press the device to unlock access to keys, without the underlying driver requiring root
privileges. The concept of authorization exists on a TPM, so there’s fundamentally nothing preventing a rootless approach.
Unfortunately, there is no consensus on the subject that would allow all applications to communicate with a TPM in a rootless manner. As of now, the hack consists of requiring the user to install tpm2-tss
in order to benefit from its udev rules, which have become a sort of unofficial standard. Admittedly, this method is not ideal, for two reasons:
- It installs a binary that might not even be used
tpm2-tss
is not necessarily packaged on all distributions
Nevertheless, it allows you to achieve the following result:
Screenshot from a NixOS Laptop
Note: in the different labs, we won’t be affected by this issue, as we’ll be using a Software TPM.
What about Windows?
Since Windows 8 and Windows Server 2012, the operating system includes TBS (TPM Base Services), which is an interface that centralizes access to the TPM through RPC calls4. The following illustration shows the relationship between TBS and the TPM:
source: https://learn.microsoft.com/en-us/windows/win32/devio/tpm-base-services
For security reasons, TBS only accepts calls originating from the host machine.
Remote Access
We’ve seen how to interact with a TPM locally, but is it possible to do so remotely? The answer is yes. tpm2-tss
implements a number of drivers5 that enable remote access. There are two approaches—either via SSH access or via a TCP socket.
As an example, the TCP approach is the one used by many simulators such as:
The most important thing is to remember that this type of access exists. If your context requires it, keep in mind that establishing an encrypted channel is a prerequisite to prevent any man-in-the-middle attacks.
Bonus: an exotic TPM
It’s worth knowing that a TPM can also be located outside of a machine, for example... on a USB stick! That’s the rather wild idea proposed by LetsTrust-TPM2Go, offering a portable TPM.
Technically, this is a TPM that responds to SPI (Serial Peripheral Interface) inputs; then, libusb is used to connect the USB device with the host.
source: https://github.com/tpm2-software/tpm2-tss/blob/master/doc/tcti.md#tcti-spi-ltt2go
Conclusion
In this pill, we’ve seen how we can interact with a TPM and, most importantly, the wide range of methods available to us—whether for local or remote access.
Next pill...
...we will create our first key and discover how the TPM manages such resources.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion 💬
-
more precisely in Part 3: Commands ↩
-
released on July 2, 2017 ↩
-
Remote Procedure Call ↩
-
these "drivers" are an implementation of the TPM Command Transmission Interface (TCTI). Please refer to this document for more information. ↩
Create a TPM key
Let’s start by introducing few concepts before getting our hands dirty.
Concepts
Key Hierarchy
A TPM manages its keys in a rather unique way by applying the principle of hierarchy.
Let's analyze the diagram below to better understand this concept:
The diagram represents a tree with the following relationships:
A
is the parent ofB
andC
B
is the parent ofB2
This model directly maps to how a TPM organizes its keys, which are divided into two types:
- Primary key: a resource with no parent (e.g.,
A
) - Ordinary key: a resource that has a parent (e.g.,
B
,B2
, andC
)
Notes:
- A key has only one parent
- An ordinary key can itself be the parent of another key (e.g.,
B
)- A key can have multiple children (e.g.,
A
)
This parent/child distinction explains why two separate commands exist for key creation:
TPM2_CreatePrimary
: creates a primary keyTPM2_Create
: creates an ordinary key (which requires a reference to a parent key)
Moreover, all keys belong to a specific tenant referred to as a hierarchy in the TPM specification1 which I admit can be confusing. Each TPM 2.0
implementation provides exactly four fixed tenants/hierarchies, which can be disabled if needed2.
What is the rationale behind this design?
The spec assumes a TPM may be used in different contexts, so fine-grained access control is needed to support multiple roles. Each hierarchy has its own independent authorization scheme2.
Below is a full list of hierarchies:
Name | Purpose | Type |
---|---|---|
Storage or Owner hierarchy | Intended for non-privacy-sentive operations by the platform owner (e.g., an IT department, the end user). | Persisted |
Platform hierarchy | Intended to be under the control of the platform manufacturer3 for early boot tasks (e.g., BIOS, UEFI). In other words, it's not meant to be used by the end user. | Persisted |
Endorsement hierarchy | Intended for privacy-sentive operations when the user or an organization has privacy concerns. | Persisted |
Null hierarchy | Intended to store ephemeral objects (e.g. sessions, context, etc.) used internally by the TPM. The end user can use this hierarchy to use ephemeral keys that will be erased at reboot. | Volatile |
Table: Four TPM hierarchies
Privacy
refers to mecanisms which prevent a third party from correlating or identifying that multiple keys originate from the same TPM.
Note: in a enterprise context, the opposite may be desirable for auditing and control purposes.
The diagram below summarizes the key ideas we've just covered:
Most of the time, you will only use the Storage
and Endorsement
hierarchies.
Exceptionally, you may need to use the Null
hierarchy if ephemerality is a requirement.
Reproducibility
Reproducibility is a fundamental primitive in a TPM. Indeed, the latter can regenerate a Primary Key bit-for-bit, eliminating the need for backups — at least for this kind of keys.
How does this work?
Each hierarchy is tied to a seed4, a complex string of characters generated randomly by the TPM and never exportable. When creating a key, the client provides inputs that describe the key’s desired characteristics. The TPM then derives the actual key using a Key Derivation Function (KDF)5, with two parameters: the inputs and the seed.
We can express this as the following equation:
\( KDF(inputsX, seedY) = keyXY \)
Since the seed is typically stable, the TPM can deterministically reproduce the key whenever needed.
Note: given the high entropy of seeds, it’s extremely unlikely that two TPMs will generate the same key.
Persisted vs. Volatile Hierarchies
Earlier, we distinguished between persisted and volatile hierarchies. Now that the seed concept has been introduced, we can clarify this:
- A persisted hierarchy maintains the same seed across reboots.
- A volatile hierarchy, by contrast, generates a new seed on every reboot, making its keys ephemeral.
With sufficient privileges, it's possible to force a seed rotation for any persisted hierarchy — but this will invalidate all previously created keys.
Use this operation with caution.
Key Attributes
When creating a key, you define its properties through a set of attributes.
Here’s a non-exhaustive list of possible attributes:
Name | Description |
---|---|
sign | The key can sign data |
decrypt | The key can decrypt data (it previously encrypted) |
restricted | The key can sign or encrypt internal TPM data |
fixedTPM | The key cannot be duplicated |
fixedParent | The key cannot be duplicated to another parent |
sensitiveDataOrigin | The key was generated by the TPM itself6 |
Table: Key Attributes
A complete list is available here.
Typically, you will combine these attributes based on your use case.
Example: A Parent Key
As mentioned above in the key hierarchy section, a key can act as a parent to others. The TPM spec mandates that such a key conforms to the following pattern:
sign | decrypt | restricted |
---|---|---|
0 | 1 | 1 |
Why?
A TPM has limited storage. To manage keys securely outside the TPM (e.g., on disk), the parent key must be able to encrypt the private portion (i.e. TPM2B_PRIVATE
) of its children.
Later, when the private key is loaded into the TPM, the parent key decrypts the blob. Since TPM2B_PRIVATE
is treated as an internal TPM resource, the key must have the restricted attribute.
In the spec, this kind of key is also called a Storage Parent.
A Practical Example
Now that we’ve covered the main ideas, let’s get hands-on!
- Create an ECC NIST P256 ordinary key (requires creating a primary key first)
- Store the key on the filesystem
- Load the key into the TPM
Creating the Primary Key
We start by creating the root key in the hierarchy. It MUST have both restricted
and decrypt
attributes set to TRUE
to be considered a Storage Key. We provide this information to the TPM2_CreatePrimary
command as shown in the snippet below:
// without error handling for more clarity
createPrimaryCmd := tpm2.CreatePrimary{
PrimaryHandle: tpm2.TPMRHOwner, // indicates in which hierarchy the key must belong (Owner == Storage)
InPublic: tpm2.New2B(tpm2.TPMTPublic{
Type: tpm2.TPMAlgECC, // type of key
NameAlg: tpm2.TPMAlgSHA256,
ObjectAttributes: tpm2.TPMAObject{
FixedTPM: true,
FixedParent: true,
SensitiveDataOrigin: true, // indicates that the key must be produced by the TPM
UserWithAuth: true,
// required attributes to get a storage key ⬇️
Restricted: true,
Decrypt: true,
SignEncrypt false,
},
Parameters: ... // truncated
})
}
createPrimaryRsp, _ := createPrimaryCmd.Execute(tpm)
Creating the Ordinary Key
With the Storage Key created, we now have everything we need to create an ordinary key using the TPM2_Create
command.
// without error handling for more clarity
createRsp, _ := tpm2.Create{
ParentHandle: tpm2.NamedHandle{ // indicates a reference to the parent (i.e. storage key)
Name: createPrimaryRsp.Name,
Handle: createPrimaryRsp.ObjectHandle,
},
InPublic: template,
}.Execute(tpm)
TPM2_Create
's response contains the following fields:
Type | Name | Description |
---|---|---|
TPM2B_PRIVATE | outPrivate | the private portion of the object |
TPM2B_PUBLIC | outPublic | the public portion of the created object |
TPM2B_CREATION_DATA | creationData | contains a TPMS_CREATION_DATA |
TPM2B_DIGEST | creationHash | digest of creationData.creationData using |
nameAlg of outPublic | ||
TPMT_TK_CREATION | creationTicket | ticket used by TPM2_CertifyCreation() to validate |
that the creation data was produced by the TPM |
Table: TPM2_Create Response
The result contains two important fields: outPrivate
and outPublic
, which can safely be stored on disk because:
TPM2B_PUBLIC
is meant to be publicTPM2B_PRIVATE
is opaque (because encrypted by the parent key)
Due to the TPM's limited resources, keys are typically stored externally.
Loading the Ordinary Key
To load a key into the TPM, we need to use TPM2_Load
command along with TPM2B_PUBLIC
and TPM2B_PRIVATE
data.
// without error handling for more clarity
loadRsp, _ := tpm2.Load{
ParentHandle: tpm2.NamedHandle{ // reference to the parent key
Name: createPrimaryRsp.Name,
Handle: createPrimaryRsp.ObjectHandle,
},
InPublic: public, // TPM2B_PUBLIC
InPrivate: private, // TPM2B_PRIVATE
}.Execute(tpm)
Once loaded, the key can be used to perform cryptographic operations, depending on its attributes.
Complete Example
To make this more concrete, this pill includes a working example that implements everything we’ve discussed so far.
The source code is available here.
It provides a CLI for creating and loading an ordinary key into a TPM.
Depending on your setup, you can run the following terminal commands:
# Create the key
# Note: the key will be stored in the current directory with the name `tpmkey.pub` and `tpmkey.priv`
go run github.com/loicsikidi/tpm-pills/examples/04-pill create
# output: Ordinary key created successfully 🚀
# list created files
ls -l tpmkey.*
# OPTIONAL: use tpm2-tools to print public object
tpm2 print -t TPM2B_PUBLIC ./tpmkey.pub
# Load the key in the TPM
go run github.com/loicsikidi/tpm-pills/examples/04-pill load --public ./tpmkey.pub \
--private ./tpmkey.priv
# output: Ordinary key loaded successfully 🚀
# Clean up
# Note: the command will remove swtpm state
go run github.com/loicsikidi/tpm-pills/examples/04-pill cleanup
# output: State cleaned successfully 🚀
# remove created files
rm ./tpmkey.pub ./tpmkey.priv
Note: by default, the example uses swtpm as a TPM simulator. If you want to use a real TPM, you can specify the
--use-real-tpm
flag.
The output of TPM2_CreatePrimary
doesn’t include a TPM2B_PRIVATE
, which prevents loading it later via TPM2_Load
.
This is by design: a primary key is reproducible.
Bonus
The file 04-pill/concepts_test.go includes unit tests that demonstrate key concepts from this pill, including key reproducibility and that only storage keys can have children.
Feel free to check it out if you’re curious.
Conclusion
In this pill, we introduced key concepts in TPM key management.
In summary, we covered:
- The idea of hierarchies
- The difference between primary and ordinary keys
- The reproducible nature of primary keys
- Key attributes and their purposes
Then, we applied these ideas in a practical example.
Next pill...
...we’ll use TPM keys to perform cryptographic operations.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion 💬
-
if you're familiar with the PKCS#11 protocol, a hierarchy is similar to the concept of a
token
↩ -
this refers to the OEM (Original Equipment Manufacturer) ↩
-
also referred to as the primary seed ↩
-
it may have been generated elsewhere and then imported into the TPM ↩
Perform cryptographic operations with asymmetric keys
As the title suggests, in this pill we will focus on asymmetric keys; however, note that the next pill will be dedicated to the use of symmetric keys.
A TPM is capable of performing basic cryptographic operations such as encryption/decryption and signing/verification. To use these methods, keep in mind that the key must first be loaded (i.e., TPM2_Load
).
The code of the CLI that you will see below is fully available here.
Note: if you want to use a real TPM in the examples, you can add
--use-real-tpm
flag ingo run github.com/loicsikidi/tpm-pills/examples/05-pill create
command.
Encryption / Decryption
Let’s begin with encryption + decryption using an asymmetric key. This function ensures data integrity and confidentiality and follows this pattern:
- The public key encrypts a payload
- The private key decrypts the payload
However, asymmetric encryption has a limitation: maximum payload size. For example, here are the constraints when using an RSA key with OAEP padding1:
Algorithm | Parameters | Maximum message length (in bytes) |
---|---|---|
RSA_DECRYPT_OAEP_2048_SHA256 | k = 256; hLen = 32; | 190 |
RSA_DECRYPT_OAEP_3072_SHA256 | k = 384; hLen = 32; | 318 |
RSA_DECRYPT_OAEP_4096_SHA256 | k = 512; hLen = 32; | 446 |
RSA_DECRYPT_OAEP_4096_SHA512 | k = 512; hLen = 64; | 382 |
Table: RSA OAEP maximum payload size
In short, asymmetric encryption is not recommended for variable-length messages, as some values may exceed the size limit. In such cases, hybrid cryptography2 is preferred.
Now that this is clarified, let’s look at a practical example! First, we’ll create a key with the following attributes:
sign | decrypt | restricted |
---|---|---|
0 | 1 | 0 |
To do so, run the command below:
# Note: the key will be stored in the current directory
# with the names `tpmkey.pub`, `tpmkey.priv` and `public.pem`
go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type decrypt
# output: Ordinary key created successfully 🚀
Exceptionally this key is of type RSA because the command TPM2_ECC_Decrypt
3 was introduced in a recent version of the spec (i.e., 1.83), and very few TPMs currently comply with it.
Typically, a TPM version can be updated via a BIOS firmware upgrade.
Now, let’s encrypt a message using the public key:
# Encrypt a blob using the public key
go run github.com/loicsikidi/tpm-pills/examples/05-pill encrypt --key ./public.pem \
--message 'Hello TPM Pills!' --output ./blob.enc
# output: Encrypted message saved to ./blob.enc 🚀
# Alternatively, you can use the `openssl` command to encrypt the blob
openssl pkeyutl -encrypt -in <(echo -n 'Hello TPM Pills!') -out ./blob.enc \
-pubin -inkey public.pem -pkeyopt rsa_padding_mode:oaep -pkeyopt rsa_oaep_md:sha256
We are using OAEP padding with SHA256 hashing. It's an arbitrary choice.
Technically, a TPM is not required for encryption; however, in constrained environments (e.g., IoT or embedded systems), the TPM2_RSA_Encrypt
command can be used.
All prerequisites are now met to decrypt:
# Decrypt the blob using the private key held in the TPM
go run github.com/loicsikidi/tpm-pills/examples/05-pill decrypt --public ./tpmkey.pub \
--private ./tpmkey.priv --in ./blob.enc
# output: Decrypted "Hello TPM Pills!" successfully 🚀
# clean up
go run github.com/loicsikidi/tpm-pills/examples/05-pill cleanup
rm ./tpmkey.pub ./tpmkey.priv ./public.pem ./blob.enc
Under the hood, the command looks like this:
decryptRsp, _ := tpm2.RSADecrypt{
KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
Handle: loadedOrdinaryKey.ObjectHandle,
Name: loadedOrdinaryKey.Name,
},
CipherText: tpm2.TPM2BPublicKeyRSA{Buffer: ciphertext}, // encrypted blob
InScheme: tpm2.TPMTRSADecrypt{
Scheme: tpm2.TPMAlgOAEP, // algorithm
Details: tpm2.NewTPMUAsymScheme(
tpm2.TPMAlgOAEP,
&tpm2.TPMSEncSchemeOAEP{
HashAlg: tpm2.TPMAlgSHA256, // hash algorithm
},
),
},
}.Execute(tpm)
fmt.Println(decryptRsp.Message.Buffer) // original message in plain text
Signature
Now, let’s see how to perform a digital signature using a TPM.
This time the flow is reversed: the private key signs, and the public key verifies the integrity.
First, let’s create a signing key:
go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type signer
# output: Ordinary key created successfully 🚀
This key has the following attributes:
sign | decrypt | restricted |
---|---|---|
1 | 0 | 0 |
Run the following commands to create and verify a message signature:
go run github.com/loicsikidi/tpm-pills/examples/05-pill sign --public ./tpmkey.pub \
--private ./tpmkey.priv --message 'Hello TPM Pills!' --output ./message.sig
# output: Signature saved to ./message.sig 🚀
go run github.com/loicsikidi/tpm-pills/examples/05-pill verify --key ./public.pem \
--signature ./message.sig --message 'Hello TPM Pills!'
# output: Signature verified successfully 🚀
# Alternatively, you can use the `openssl` command to verify the signature
openssl dgst -sha256 -verify ./public.pem -signature ./message.sig <(echo -n 'Hello TPM Pills!')
# output: Verified OK
As before, the TPM provides a command for signature verification (i.e., TPM2_VerifySignature
4).
In code, the TPM_Sign
command looks like this:
signRsp, _ := tpm2.Sign{
KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
Handle: loadedOrdinaryKey.ObjectHandle,
Name: loadedOrdinaryKey.Name,
},
Digest: digest, // payload's digest
}.Execute(tpm)
Deep dive: signing with a restricted key
In the previous pill, we introduced the concept of a restricted key. To recap, this property allows a key to sign or encrypt/decrypt TPM-internal objects. Now the question arises: is it possible to sign external data with a restricted key? The answer is yes, but under certain conditions.
A restricted signing key is mainly used to produce attestations in the TPMS_ATTEST
5 format.
It's a proof generated by a TPM and intended for a third party. The attestation typically relates to some internal TPM state. This document is always signed by a restricted signing key, commonly known as an Attestation Key.
In a simplified view, a TPMS_ATTEST
structure looks like this:
You’ll notice the structure includes a header (TPM_GENERATED_VALUE
), which is critical — it indicates that the data within the attestation (i.e., the body) originates from a TPM. In our case, though, the data does not come from a TPM — but don’t worry, the spec takes this situation into account. The signature is accepted only if the TPM produced the digest itself using TPM2_Hash
. Let’s break this down using the diagram below:
As shown, the TPM2_Hash
6 command returns two elements: a digest and a ticket. The first is straightforward — but what is a ticket? Think of the TPM as a stateless component. Like an HTTP cookie, a ticket carries information between TPM commands. In our case, the ticket tells TPM_Sign
that the TPM itself produced the digest.
The ticket is a signed document created by the TPM using an internal, non-exportable HMAC key. When a command supports tickets, the TPM first verifies the signature before proceeding.
Here's the equivalent in code:
// without error handling for more clarity
rspHash, _ := tpm2.Hash{ // hash performed by the TPM
Data: tpm2.TPM2BMaxBuffer{Buffer: []byte(message)},
HashAlg: tpm2.TPMAlgSHA256,
Hierarchy: tpm2.TPMRHOwner,
}.Execute(tpm)
signRsp, _ := tpm2.Sign{
KeyHandle: tpm2.NamedHandle{ // reference to the key doing the job
Handle: loadedOrdinaryKey.ObjectHandle,
Name: loadedOrdinaryKey.Name,
},
Digest: rspHash.OutHash, // digest
Validation: rspHash.Validation, // ticket
}.Execute(tpm)
As a demonstration, here’s the same example as earlier, but this time using a restricted key:
sign | decrypt | restricted |
---|---|---|
1 | 0 | 1 |
go run github.com/loicsikidi/tpm-pills/examples/05-pill create --type restrictedSigner
# Sign a message using the private key held in the TPM
go run github.com/loicsikidi/tpm-pills/examples/05-pill sign --public ./tpmkey.pub --private ./tpmkey.priv --message 'Hello TPM Pills!' --output ./message.sig
# output: Signature saved to ./message.sig 🚀
# Verify the signature using the public key
go run github.com/loicsikidi/tpm-pills/examples/05-pill verify --key ./public.pem --signature ./message.sig --message 'Hello TPM Pills!'
# output: Signature verified successfully 🚀
# Alternatively, you can use the `openssl` command to verify the signature
openssl dgst -sha256 -verify ./public.pem -signature ./message.sig <(echo -n 'Hello TPM Pills!')
# output: Verified OK
# Clean up
go run github.com/loicsikidi/tpm-pills/examples/05-pill cleanup
rm ./tpmkey.pub ./tpmkey.priv ./public.pem ./message.sig
Conclusion
In this pill, we explored how to perform cryptographic operations with an asymmetric key pair (encryption/decryption, signing/verification). We haven’t covered every function (e.g., Elliptic Curve Diffie-Hellman, ECDH), but we’ve tackled the essentials. We also took a detour to introduce two key concepts: attestation and ticket.
Next Pill...
...we will focus on symmetric keys and how to use them.
🚧 TPM Pills
is in beta 🚧
- if you encounter problems 🙏 please report them on the tpm-pills issue tracker
- if you think that
TPM Pills
should cover a specific topic which isn't in the roadmap, let's initiate a discussion
-
see Optimal asymmetric encryption padding article on Wikipedia ↩
-
see hybrid cryptosystem article on Wikipedia ↩
-
https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=110 ↩
-
https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=165 ↩
-
https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-2-Version-184_pub.pdf#page=158 ↩
-
security note: if the payload to be hashed includes the
TPM_GENERATED_VALUE
header, the command will not return a ticket. ↩