Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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?

Info

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.

Warning

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:

ResourceDescriptionFormat
A Practical Guide to TPM 2.0At 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 freeOnline course
TPM.dev tutorialsTo 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 GoogleTPM-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 NokiaA 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 💬

  1. The specification available here is a dense and relatively complex document.

  2. 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.

Tip

If 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.

Who produced the TPM specification?

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 ElementSecurity LevelSecurity FeaturesRelative costUse Case
Discrete TPMHighestTamper resistant hardware💲💲💲Critical systems
Integrated TPMHigherHardware💲💲e.g. Network gear
Firmware TPMHighTEE💲Non-critical systems
Software TPMN/AN/AFreeTesting and prototyping
Virtual TPMHighHypervisorCheapCloud 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.

Important

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.

Tip

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 💬

  1. Hardware Security Module (see more in Wikipedia)

  2. see more in Wikipedia

  3. via a Secure Boot or a Mesured Boot.

  4. it has its own memory (RAM) and its own storage, although the resources are very limited.

  5. Window 11 - System requirements

  6. 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.

Warning

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):

ToolDescriptionLinux SupportWindows SupportMacOS Support
go >= v1.22A language that no longer needs an introduction
opensslCrypto Swiss Army Knife which here is a dependency for using the Software TPM
swtpmA Software TPM Emulator

(using Cygwin)
tpm2-toolsA 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.

NameLanguageDescription
tpm2-tssCIntel implementation of TCG's TPM Software Stack (TSS). The current standard meter bar regarding TPM libraries.
ibmtssCIBM implementation of TPM Software Stack (TSS) but not API compatible with TCG TSS.
wolfTPMCTPM 2.0 librairy designed for embedded system.
go-tpmgolang
tpm2-pytsspythonWrapper of tpm2-tss.
tpm-rsrust
rust-tss-fapirustWrapper 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.MSRc#, 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

Info

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.

Info

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

Warning

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 💬

  1. 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:

  1. The library created the TPM2_GetCapability command in accordance with the specification
  2. The command was sent to the TPM2
  3. The command was received and interpreted by the TPM
  4. The TPM produced a response in accordance with the specification
  5. The response was sent back to the client2

Local Access

Important

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.

Info

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.

Info

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:

Info

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:

  1. It installs a binary that might not even be used
  2. 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:

TBS diagram

source: https://learn.microsoft.com/en-us/windows/win32/devio/tpm-base-services

Info

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 💬

  1. more precisely in Part 3: Commands

  2. here in the spec ↩2

  3. released on July 2, 2017

  4. Remote Procedure Call

  5. 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 of B and C
  • B is the parent of B2

This model directly maps to how a TPM organizes its keys, which are divided into two types:

  1. Primary key: a resource with no parent (e.g., A)
  2. Ordinary key: a resource that has a parent (e.g., B, B2, and C)

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 key
  • TPM2_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:

NamePurposeType
Storage or Owner hierarchyIntended for non-privacy-sentive operations by the platform owner (e.g., an IT department, the end user).Persisted
Platform hierarchyIntended 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 hierarchyIntended for privacy-sentive operations when the user or an organization has privacy concerns.  Persisted
Null hierarchyIntended 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

What does "privacy" means in the TPM specification?

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:

Info

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.
Warning

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:

NameDescription
signThe key can sign data
decryptThe key can decrypt data (it previously encrypted)
restrictedThe key can sign or encrypt internal TPM data
fixedTPMThe key cannot be duplicated
fixedParentThe key cannot be duplicated to another parent
sensitiveDataOriginThe key was generated by the TPM itself6

Table: Key Attributes

Info

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:

signdecryptrestricted
011

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.

Info

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!

Goals
  1. Create an ECC NIST P256 ordinary key (requires creating a primary key first)
  2. Store the key on the filesystem
  3. 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:

TypeNameDescription
TPM2B_PRIVATEoutPrivatethe private portion of the object
TPM2B_PUBLICoutPublicthe public portion of the created object
TPM2B_CREATION_DATAcreationDatacontains a TPMS_CREATION_DATA
TPM2B_DIGESTcreationHashdigest of creationData.creationData using
nameAlg of outPublic
TPMT_TK_CREATIONcreationTicketticket 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 public
  • TPM2B_PRIVATE is opaque (because encrypted by the parent key)
Info

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.

Info

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.

Why isn’t the primary key exported?

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 💬

  1. if you're familiar with the PKCS#11 protocol, a hierarchy is similar to the concept of a token

  2. with the exception of the NULL hierarchy ↩2

  3. this refers to the OEM (Original Equipment Manufacturer)

  4. also referred to as the primary seed

  5. see the Wikipedia article

  6. it may have been generated elsewhere and then imported into the TPM

Perform cryptographic operations with asymmetric keys

Foreword

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).

Info

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 in go 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:

  1. The public key encrypts a payload
  2. 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:

AlgorithmParametersMaximum message length
(in bytes)
RSA_DECRYPT_OAEP_2048_SHA256k = 256; hLen = 32;190
RSA_DECRYPT_OAEP_3072_SHA256k = 384; hLen = 32;318
RSA_DECRYPT_OAEP_4096_SHA256k = 512; hLen = 32;446
RSA_DECRYPT_OAEP_4096_SHA512k = 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:

signdecryptrestricted
010

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 🚀
Info

Exceptionally this key is of type RSA because the command TPM2_ECC_Decrypt3 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
Note

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:

signdecryptrestricted
100

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
Tip

As before, the TPM provides a command for signature verification (i.e., TPM2_VerifySignature4).

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_ATTEST5 format.

What is an attestation?

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_Hash6 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.

How does the TPM trust the ticket?

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:

signdecryptrestricted
101
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

  1. see Optimal asymmetric encryption padding article on Wikipedia

  2. see hybrid cryptosystem article on Wikipedia

  3. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=110

  4. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-3-Version-184_pub.pdf#page=165

  5. https://trustedcomputinggroup.org/wp-content/uploads/Trusted-Platform-Module-2.0-Library-Part-2-Version-184_pub.pdf#page=158

  6. security note: if the payload to be hashed includes the TPM_GENERATED_VALUE header, the command will not return a ticket.