Code Sample: Protecting secret data and keys using Intel® Platform Trust Technology

ID 671683
Updated 3/12/2022
Version Latest
Public

author-image

By

File(s): Download
License: BSD-3-Clause
Optimized for...  
OS: Linux*
Hardware: Platforms based on Intel® Core™ processors with Intel® Platform Trust Technology (Intel® PTT) or discrete TPM
Software:
(Programming Language, tool, IDE, Framework)
Bourne Shell
Prerequisites: Kernel TPM2 driver, TPM 2.0 tools (tpm2-tools) 4.0 release based on TPM2 Software Stack (tpm2-tss) and TPM2 Access Broker daemon (tpm2-abrmd), and OpenSSL command-line tools
 

Introduction

Applications running on connected devices often lack many protections that applications running in the cloud or in corporate data centers by virtue of the platform hardware being more physically accessible.  This sample will demonstrate how to use Intel® Platform Trust Technology (Intel® PTT) to protect the pass phrase for an encrypted storage volume by "sealing" and "unsealing" data to the underlying platform using TPM 2.0.  You will learn how to perform basic sealing and unsealing operations and then add various types of authorization policies that protect the data against different kinds of attacks such as physical disk cloning or unauthorized versions of key pieces of system software.

Trusted Platform Module

Encryption schemes function by virtue of keeping protected the encryption secret called a digital key. Key protection requires both physical access protection as well as restricted access to sensitive operations with the key, such as decryption and digital signing. The Trusted Computing Group (TCG) calls these two requirements root of trust for storage (RTS) and root of trust for reporting (RTR). TCG is a consortium of industry and academic expertise coming together to define specifications for a security module—the Trusted Platform Module or TPM—that achieves these objectives. TPM implementations comply with two major standard specification versions: 1.2 and 2.0. Algorithm flexibility and enhanced authorization schemes are the major improvements in version 2.0. Other notable TPM properties include: secure persistent storage, platform configuration registers (PCRs), a per-TPM unique certified key pair, and hierarchies for partitioning roles for access to TPM objects. Silicon that is purpose-built to function as a TPM is known as a discrete TPM.

Intel® Platform Trust Technology

Intel® Platform Trust Technology (Intel® PTT) is an integrated TPM 2.0 implementation on select Intel platforms. Intel PTT runs on the Intel® Management Engine (Intel® ME) and maintains its state separate and isolated from the the host CPU and hence the host software. The notable differences from a conventional discrete TPM are: 

  • It uses the platform SPI flash for persistent storage and protects the content with part-unique encryption key. 
  • There is a provision for permanent dictionary lockout wherein if the RTC used by the Intel PTT loses power multiple times during an active lockout, a 30 minute mandatory lockout for further attempts is enforced. 
  • Like all TPM manufacturers, Intel is required to certify a per-part unique key under the TPM endorsement hierarchy rooted from an endorsement primary seed or EPS. The EPS is generated as part of Intel's manufacturing process. The tpm2_getmanufec utility provided by the tpm2-tools project will retrieve the endorsement certificate. 
  • In the event of a security vulnerability, Intel will revoke the endorsement certificate via an Intel ME firmware update that programs a new EPS. Host-based software then retrieves the certificate for the newly-generated endorsement key rooted off the new EPS. iCLS client installed with the Intel® Management Engine Components supports PTT recertification on Microsoft* Windows* OS.  TPM re-endorsement software for Linux* is hosted at https://github.com/intel/INTEL-SA-00086-Linux-Recovery-Tools.

Trusted Software Stack

TPM is a resource-limited device: applications may quickly exhaust the secure persistent storage of about 14 KB and a smaller fast memory designed to support a maximum of three sessions. To address these limitations, TCG created a companion specification for a trusted software stack (TSS) to optimally function with the resource-limited TPM. The TSS specification partitions the stack into well-defined interfaces for device communication, command response buffer transport, access brokering, resource management, session management, and defines profiles for the most common use cases.

  • Feature API (FAPI): The specification is still in a draft form and work continues in this space for an implementation. The abstraction should profile common use cases and provide context management.
  • Enhanced System API (ESAPI or ESYS): Provides cryptographic functions for sessions and session management.
  • System API (SAPI): A one-to-one mapping of all the available TPM2 commands. While sufficient, it requires expert TPM knowledge for managing sessions, contexts, and cryptographic functions. Marshalling and unmarshalling of command and response buffers is also handled here.
  • Trusted Access Broker and Resource Manager (TAB/RM): Enables the hardware TPM resource to be shared with multiple applications by serializing TPM access and automatically managing TPM state. There is a user-space dbus-based implementation (tabrmd) as well an in-kernel implementation (/dev/tpmrm0).
  • TPM Command Transmission Interface (TCTI): Decouples the API generating the TPM command and responses from the transport mechanism.
  • Trusted Device Driver (TDD): This is the primary device interface handling the command-response buffering, control registers and interface management. This is part of the mainline kernel tree in the character device driver category.

TPM2 Software

Unlike TPM1.2 where IBM TSS and tools were widely used, for TPM2 there is more than one implementation of the TSS available. Besides the one from IBM there is an implementation from Google that is mostly used for the Google Chromium* project. The one that is getting widespread adoption by distributions and Linux community at large is the tpm2-software project on GitHub. One of its notable features is that it follows the TCG guidelines on having well-defined interfaces for ESYS, SAPI, TAB/RM and the TCTI. Following are the links for the tpm2-software projects:

tpm2-tools has an dependency on the tpm2-tss project and optionally on the tpm2-abrmd project. The use cases in this sample are demonstrated using the tpm2-tools 4.0 release.

All the projects use GNU Autotools since it is widely used and highly portable across Linux distributions. For all the projects above: "bootstrap", "configure", "make" and "make install" are generally sufficient to build and install the packages. The bootstrap and configure scripts should inform the user of missing libraries and packages. The user can then install them using the package manager specific to the distribution in use.

Use Case: Linux Unified Key Setup (LUKS) disk encryption

Disk encryption prevents storage devices from being mounted on alternative operating environments controlled by the attacker where they can observe or tamper with sensitive information. This use case will take a basic unprotected loopback-mounted file system and gradually add stronger and more robust protections to it. Assume for purposes of discussion that this loopback-mounted file system is a logical disk partition automatically mounted by the operating system on boot rather than just a file-based simulation.

The sample code referenced in this article is available in the download link in the header of this article.

Baseline: unprotected file system

To demonstrate the problem, first set up a disk image without encryption and demonstrate disclosure of sensitive content.

Create a disk image and write some content:

dd if=/dev/zero of=plain.disk bs=1M count=10
mkfs.ext4 plain.disk
mkdir mountpoint
sudo mount plain.disk mountpoint
sudo sh -c 'echo "This is my plain text" > mountpoint/plain.txt'
sudo umount mountpoint


At this point we have a disk with a file plain.txt which has the sensitive content "This is my plain text". The file system is not mounted by the operating system, yet we can demonstrate that the sensitive data is not protected. To do this simply run the following command:

strings plain.disk


The above command will disclose the file name and sensitive file content. Future sections will add protections to prevent an attacker from performing the trivial attack above.

Disk encryption is only an effective mitigation when used with other security controls. Once an encrypted volume is mounted, it is subject to the standard operating system enforced file system protections.  An encrypted volume that is automatically mounted without user intervention will offer no protection if an attacker is allowed to override the kernel command line and start an interactive shell without having to log in, for example.

LUKS with on-disk plain text passphrase 

Most standard Linux distributions support LUKS-encrypted disk volumes. LUKS ensures data confidentiality at rest. LUKS stores setup information in the partition header to aid easy migration. LUKS volumes can be automatically mounted, and the encryption passphrase can be supplied interactively (default) and or specified as key file (command line argument). The cryptsetup user-space utility aids creating and managing LUKS volumes.

Set up a new LUKS volume with a simple passphrase as key protector:

It is suggested to run losetup -f to find the next unused loopback device and use that device in place of /dev/loop0 in the script below.  Also, use /dev/hwrng (hardware random number generator) in place of /dev/urandom below if it is available.

dd if=/dev/zero of=enc.disk bs=1M count=50
dd if=/dev/urandom of=disk.key bs=1 count=32
sudo losetup /dev/loop0 enc.disk
sudo cryptsetup --key-file=disk.key luksFormat /dev/loop0

At this point you have set up the LUKS volume and it should display a warning about overwriting the data. Open the LUKS volume by authenticating with the disk.key and complete the setting up the disk with a filesystem:

sudo cryptsetup --key-file=disk.key open /dev/loop0 enc_volume
sudo mkfs.ext4 -j /dev/mapper/enc_volume
sudo mount /dev/mapper/enc_volume mountpoint

Create a plain text file again, add user content, and unmount the volume:

sudo sh -c 'echo "This is my plain text" > mountpoint/plain.txt'
sudo umount mountpoint
sudo cryptsetup remove enc_volume
sudo losetup -d /dev/loop0


Observe that the sensitive data is no longer visible in the (encrypted) disk image: 

strings enc.disk | grep -i plain

The biggest issue with this method of encrypting the disk volume is that LUKS protections are easily subverted if the passphrase or key file is stored in plain text on disk. Thus, many LUKS implementations interactively prompt for the passphrase. This solution is unworkable for many Internet of Things (IoT) and embedded scenarios due to the lack of a human at a keyboard. An effective alternative to human input is to seal the passphrase on a security token that anchors to the platform, like a TPM.

LUKS with passphrase stored in TPM

Since auto-mounting requires providing a passphrase or key to cryptsetup at runtime without user intervention, it must as some point be available in the clear. A method is needed to encrypt the passphrase or key until it is needed at runtime to decrypt the volume. Such a method must also protect against the case where the disk is separated from the system into which it was installed.  This mechanism is TPM.

The improved solution has two steps:

  1. Seal the passphrase or key file into the TPM.
  2. Unseal the secret in memory and pass it to cryptsetup.

Read more about the TPM commands introduced in this section: tpm2_createprimary, tpm2_load, tpm2_evictcontrol, and tpm2_unseal.

Create and persist a sealing object and use it to seal a random byte sequence as the disk key:

tpm2_createprimary -Q --hierarchy=o --key-context=prim.ctx
dd if=/dev/urandom bs=1 count=32 status=none | tpm2_create --hash-algorithm=sha256 --public=seal.pub --private=seal.priv --sealing-input=- --parent-context=prim.ctx
tpm2_load -Q --parent-context=prim.ctx --public=seal.pub --private=seal.priv --name=seal.name --key-context=seal.ctx
tpm2_evictcontrol --hierarchy=o --object-context=seal.ctx 0x81010002

Install the new key in place of the old one, and delete the old key created previously:

tpm2_unseal -Q --object-context=0x81010002 | sudo cryptsetup --key-file=disk.key luksChangeKey enc.disk 
shred disk.key
rm -f disk.key

Mount the volume with the new authentication sealed in the TPM:

sudo losetup /dev/loop0 enc.disk
tpm2_unseal -Q --object-context=0x81010002 | sudo cryptsetup --key-file=- luksOpen /dev/loop0 enc_volume
sudo mount /dev/mapper/enc_volume mountpoint

Disk access is granted with the new secret:

ls mountpoint

Unmount the disk:

sudo umount mountpoint
sudo cryptsetup remove enc_volume
sudo losetup -d /dev/loop0

An attacker now additionally needs the TPM on the platform along with the disk to access the data since the decryption key isn't on the disk—it is safely stored on the TPM anchored to the specific platform.

This improved solution will protect against offline attacks through cloning or physical theft of the encrypted disk. It may still be possible, however, for an attacker to boot a system from alternative boot media, perform the above commands, and retrieve the secret needed to decrypt the LUKS volume. What is needed is an authentication mechanism that anchors to trusted system state. This can be achieved with a TPM and its platform configuration register (PCR) sealing provision.

LUKS with passphrase sealed to TPM PCRs

TPM platform configuration registers (PCRs) are used to protect against the scenario where an attacker alters the system boot parameters or boots to an operating system of their choice in order to access sensitive data.  Effective use of TPM PCRs requires the cooperation of the system firmware, boot loaders, operating system kernel, and applications. Without firmware, drivers, and software to configure the TPM, the TPM will just sit inactive on an I/O bus doing nothing.

TPM PCRs are used to measure boot components using a secure hash algorithm such as SHA-256. The TPM PCRs default to a zero value when the system is reset. As the system boots, measurements of critical system components such as the firmware, BIOS, OS loaders, et cetera are extended into PCRs as boot progresses.  Extending a PCR is an append-only operation, and requires I/O to the TPM.  Because it is impossible to set a PCR to a user-specified value and also impossible to "take back" I/O, the TPM PCRs can attest the system boot sequence and thus the state of the platform up to the point were PCR measurements ceased.  PCR0, for example, contains measurements of the system firmware and BIOS, but not the operating system boot loader or kernel.  A consequence of using PCR0 and only PCR0 is that this example would only protect against a firmware replacement attack, provided the original firmware made measurements properly, and provided the replacement firmware does not forge measurements to the TPM—an attack that requires a separate mitigation.

In practice, an effective PCR set must be sufficiently complete to attest the currently running code and any code that has run beforehand that the designer deems security-critical.  The TCG PC Client Specific Implementation Specification for Conventional BIOS only specifies usage of PCRs 0-7, 16, and 23.  PCR usage during and after operating system boot is operating system specific. 

The further improved solution has two additional steps:

  1. Use PCRs as proxy authentication for the TPM sealing object.
  2. Extend the sealing PCRs after unsealing the key file so that it cannot be unsealed gain on the current boot.

Read more about the TPM commands introduced in this section: tpm2_startauthsession, tpm2_policypcr, tpm2_flushcontext, tpm2_pcrread, and tpm2_pcrextend.

Create a PCR policy with current value of PCR0 in the sha256 bank:

tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:0" --policy=pcr0.sha256.policy
tpm2_flushcontext session.ctx

Now replace the seal object in the TPM non-volatile memory protecting the disk encryption secret with a new one that adds the pcr policy we just created as an authentication mechanism to access the sealed secret:

tpm2_unseal --object-context=0x81010002 | tpm2_create -Q --hash-algorithm=sha256 --public=pcr_seal_key.pub --private=pcr_seal_key.priv --sealing-input=- --parent-context=prim.ctx --policy=pcr0.sha256.policy
tpm2_evictcontrol --hierarchy=o --object-context=0x81010002 
tpm2_load -Q --parent-context=prim.ctx --public=pcr_seal_key.pub --private=pcr_seal_key.priv --name=pcr_seal_key.name --key-context=pcr_seal_key.ctx
tpm2_evictcontrol --hierarchy=o --object-context=pcr_seal_key.ctx 0x81010002 

Now try to mount the encrypted disk again except this time the secret is sealed inside a TPM object whose unsealing operation can only be accessed by satisfying the PCR policy.  In other words, authenticating by virtue of intended system software state being unchanged as reflected by the PCR value.

sudo losetup /dev/loop0 enc.disk
tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:0" --policy=pcr0.sha256.policy

At this point ideally you would want unseal the secret in memory and pipe it directly to cryptsetup like this: "tpm2_unseal --auth=session:session.ctx --object-context=0x81010002 | sudo cryptsetup luksOpen --key-file=- /dev/loop0 encvolume". However, for the purpose of demonstrating flexible PCR in a later section we will make a copy of the unsealed secret:

tpm2_unseal --auth=session:session.ctx --object-context=0x81010002 > disk_secret.bkup
cat disk_secret.bkup | sudo cryptsetup --key-file=- luksOpen /dev/loop0 enc_volume
tpm2_flushcontext session.ctx
sudo mount /dev/mapper/enc_volume mountpoint/
ls mountpoint/

To prevent further unsealing, PCR0 will be extended.  This will cause PCR0 to hold a different value, much like it would during a firmware replacement attack. This will cause a failed policy check and thus a failed unsealing attempt.

Look at the PCR state prior to extending it and then again after extending:

tpm2_pcrread --sel-list=sha256:0
tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000
tpm2_pcrread --sel-list=sha256:0

Try to unseal the sealed disk encryption secret with the dirty PCR:

tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:0" --policy=pcr0.sha256.policy

The following operation should result in a policy check failure preventing the unseal operation:

tpm2_unseal --auth=session:session.ctx --object-context=0x81010002
tpm2_flushcontext session.ctx

Unmount the disk:

sudo umount mountpoint
sudo cryptsetup remove enc_volume
sudo losetup -d /dev/loop0

The improved solution now uses TPM PCRs to attest to the system state prior to retrieving the LUKS encryption pass phrase, but there is now a new problem: updates.  In this example, applying a firmware update to the system could render the encrypted data inaccessible because PCR0 represents a specific version  of the firmware.  In order to successfully update the system, it would be necessary to re-seal the secret against a predicted set of PCR values.  But if the update was rolled-back, the old values would no longer work.  Furthermore, it may be impractical to predict the post-update PCR values prior to actually performing the update—the best approach might be to apply the update on an identical system and see what PCR values would result and pre-authorize both sets of PCR values.  With TPM, there is a a way to do that called "authorized PCR policy."

With LUKS, it is always possible to output the LUKS primary key used for data encryption on a running system. Extending the sealing PCR does not protect the LUKS primary key, as the primary key is needed at runtime to encrypt/decrypt disk I/O.

LUKS with passphrase protected by authorized PCR policy

Authorized PCR policy as an authentication mechanism for the TPM sealing object. Instead of using a rigid PCR policy tied to raw PCR values we now seal it to a PCR signature. The PCR sets are signed by the system designer and verified by the TPM. This is achieved in following steps:

Read more about the TPM commands introduced in this section: tpm2_loadexternal, tpm2_verifysignature, and tpm2_policyauthorize.

Introduce a public/private RSA keypair

Use the OpenSSL command-line tools to generate an RSA keypair.  The private key will be stored offline.  The public key will be distributed with the authorized PCR policy and used as the authorization mechanism for the LUKS encryption pass phrase.

openssl genrsa -out signing_key_private.pem 2048
openssl rsa -in signing_key_private.pem -out signing_key_public.pem -pubout

Sign a set of PCRs with the private key

On each known good system configuration, gather the current PCR values and sign them with the RSA private key.  This step may be done as many times as needed.

tpm2_startauthsession --session=session.ctx
tpm2_policypcr -Q --session=session.ctx --pcr-list="sha256:0" --policy=set2.pcr.policy
tpm2_flushcontext session.ctx
openssl dgst -sha256 -sign signing_key_private.pem -out set2.pcr.signature set2.pcr.policy

Bind the LUKS encryption passphrase to the public key

Before sealing the LUKS encryption passphrase to the TPM, it is necessary to create a policy object that specifies the conditions under which the passphrase can be unsealed.  The policy will specify that a certain set of PCRs (PCR0) must match to the values signed with a specific key (signing_key_public.pem):

tpm2_loadexternal --key-algorithm=rsa --hierarchy=o --public=signing_key_public.pem --key-context=signing_key.ctx --name=signing_key.name
tpm2_startauthsession --session=session.ctx
tpm2_policyauthorize --session=session.ctx --policy=authorized.policy --name=signing_key.name
tpm2_flushcontext session.ctx

Seal the pass phrase to the TPM by creating a sealing object using the above policy.  Note that the backup copy of the passphrase is used since since the previous example extended PCR0 to prevent re-unsealing of the pass phrase:

cat disk_secret.bkup | tpm2_create --hash-algorithm=sha256 --public=auth_pcr_seal_key.pub --private=auth_pcr_seal_key.priv --sealing-input=- --parent-context=prim.ctx --policy=authorized.policy

Replace the old persistent sealing object with the one created above:

tpm2_evictcontrol --hierarchy=o --object-context=0x81010002 
tpm2_load -Q --parent-context=prim.ctx --public=auth_pcr_seal_key.pub --private=auth_pcr_seal_key.priv --name=auth_pcr_seal_key.name --key-context=auth_pcr_seal_key.ctx
tpm2_evictcontrol --hierarchy=o --object-context=auth_pcr_seal_key.ctx 0x81010002

Unseal the encryption passphrase

Load the public key, PCR policy, and signature, and ask the TPM to verify the signature: 

tpm2_loadexternal --key-algorithm=rsa --hierarchy=o --public=signing_key_public.pem --key-context=signing_key.ctx --name=signing_key.name
tpm2_verifysignature --key-context=signing_key.ctx --hash-algorithm=sha256 --message=set2.pcr.policy --signature=set2.pcr.signature --ticket=verification.tkt --format=rsassa

Ask the TPM to now verify that the PCR values match the current values, passing in a verification ticket for the signature verification.  Note that only one set of PCR values can be verified at a time: the whole process must be repeated in order to attempt to verify against another set of signed PCR values:

tpm2_startauthsession --policy-session --session=session.ctx
tpm2_policypcr --pcr-list="sha256:0" --session=session.ctx --policy=set2.pcr.policy
tpm2_policyauthorize --session=session.ctx --input=set2.pcr.policy --name=signing_key.name --ticket=verification.tkt

Unseal the encryption pass phrase and unlock the volume:

sudo losetup /dev/loop0 enc.disk
tpm2_unseal --auth=session:session.ctx --object-context=0x81010002 | sudo cryptsetup --key-file=- luksOpen /dev/loop0 enc_volume
tpm2_flushcontext session.ctx
sudo mount /dev/mapper/enc_volume mountpoint/
ls mountpoint/

Unmount the disk when done:

sudo umount mountpoint
sudo cryptsetup remove enc_volume
sudo losetup -d /dev/loop0

 

Conclusion

Intel Platform Trust Technology (Intel PTT) is a useful technology based on the TCG TPM 2.0 specifications that enables protection of secret data and keys.  In the volume encryption use case, Intel PTT was used to protect an encryption key against offline attacks on encrypted storage.  The use case culminated in using a PCR authorization policy that allowed the data to remain protected while also accommodating system updates.  The workflow for using a PCR authorization consisted of a number of phases with steps in each phase:

Provisioning phase:

  • Create an RSA public/private keypair used for digital signature
  • Create an authorization policy for sealing secrets to digital-signature-verified PCRs
  • Create a PCR policy and sign it with the private key
  • Initialize a disk with LUKS encryption and a random pass phrase
  • Seal the pass phrase to the platform using the authorization policy

Updating phase:

  • Sign additional PCR policies to authorize unsealing on additional system states and distribute them to the systems

Run-time phase:

  • Load the signer public key
  • Verify the signature on the PCR policy and generate a verification ticket
  • Satisfy the PCR policy and authorization policy
  • Unseal the pass phrase and pipe to to cryptsetup to open the LUKS encrypted volume

Feedback

Please contact iot-security-samples@intel.com if you have feedback or wish to report issues with this sample.