A Trusted Platform Module, in short TPM, is a small piece of hardware designed to provide various security functionalities. It offers numerous features, such as storing secrets, ‘measuring’ boot, and may act as an external cryptographic engine. The Trusted Computing Group (TCG) delivers a document called TPM Interface Specifications (TIS) which describes the architecture of such devices and how they are supposed to behave as well as various details around the concepts.
These TPM chips are either compliant with the first specification (up to 1.2) or the second specification (2.0+). The TPM2.0 specification is not backward compatible and this is the one this post is about. If you need more details, there are many documents available at https://trustedcomputinggroup.org/.
Among the functions listed above, this blog post will focus on the measured boot functionality.
Measured boot principles
Measuring boot is a way to inform the last software stage if someone tampered with the platform. It is impossible to know what has been corrupted exactly, but knowing someone has is already enough to not reveal secrets. Indeed, TPMs offer a small secure locker where users can store keys, passwords, authentication tokens, etc. These secrets are not exposed anywhere (unlike with any standard storage media) and TPMs have the capability to release these secrets only under specific conditions. Here is how it works.
Starting from a root of trust (typically the SoC Boot ROM), each software stage during the boot process (BL1, BL2, BL31, BL33/U-Boot, Linux) is supposed to do some measurements and store them in a safe place. A measure is just a digest (let’s say, a SHA256) of a memory region. Usually each stage will ‘digest’ the next one. Each digest is then sent to the TPM, which will merge this measurement with the previous ones.
The hardware feature used to store and merge these measurements is called Platform Configuration Registers (PCR). At power-up, a PCR is set to a known value (either 0x00s or 0xFFs, usually). Sending a digest to the TPM is called extending a PCR because the chosen register will extend its value with the one received with the following logic:
PCR[x] := sha256(PCR[x] | digest)
This way, a PCR can only evolve in one direction and never go back unless the platform is reset.
In a typical measured boot flow, a TPM can be configured to disclose a secret only under a certain PCR state. Each software stage will be in charge of extending a set of PCRs with digests of the next software stage. Once in Linux, user software may ask the TPM to deliver its secrets but the only way to get them is having all PCRs matching a known pattern. This can only be obtained by extending the PCRs in the right order, with the right digests.
Linux support for TPM devices
A solid TPM 2.0 stack has been around for Linux for quite some time, in the form of the tpm2-tss and tpm2-tools projects. More specifically, a daemon called resourcemgr, is provided by the tpm2-tss project. For people coming from the TPM 1.2 world, this used to be called trousers. One can find some commands ready to be used in the tpm2-tools repository, useful for testing purpose.
From the Linux kernel perspective, there are device drivers for at least SPI chips (one can have a look there at files called
tpm_tis*.c for implementation details).
Bootlin’s contribution: U-Boot support for TPM 2.0
Back when we worked on this topic in 2018, there was no support for TPM 2.0 in U-Boot, but one of customer needed this support. So we implemented, contributed and upstreamed to U-Boot support for TPM 2.0. Our 32 patches patch series adding TPM 2.0 support was merged, with:
- SPI TPMs compliant with the TCG TIS v2.0
- Commands for U-Boot shell to do minimal operations (detailed below)
- A test framework for regression detection
- A sandbox TPM driver emulating a fake TPM
In details, our commits related to TPM support in U-Boot:
- tpm: remove redundant blank line
- tpm: remove extra spaces between a function and its opening bracket
- tpm: substitute deprecated uint
_t types with their u equivalent
- tpm: align arguments with open parenthesis
- tpm: use the BIT() macro where applicable
- tpm: fix spelling
- tpm: add extra blank lines between declarations and code
- tpm: add Revision ID field in the chip structure
- tpm: prepare introduction of TPMv2.x support in Kconfig
- tpm: disociate TPMv1.x specific and generic code
- tpm: add missing parameter in private data structure description
- tpm: prepare support for TPMv2.x commands
- tpm: add macros to enhance TPM commands readability
- tpm: add possible traces to analyze buffers returned by the TPM
- tpm: report driver error code to upper layer
- tpm: add TPM2_Startup command support
- tpm: add TPM2_SelfTest command support
- tpm: add TPM2_Clear command support
- tpm: add TPM2_PCR_Extend command support
- tpm: add TPM2_PCR_Read command support
- tpm: add TPM2_GetCapability command support
- tpm: add dictionary attack mitigation commands support
- tpm: add TPM2_HierarchyChangeAuth command support
- tpm: add PCR authentication commands support
- tpm: add support for TPMv2.x SPI modules
- tpm2: tis_spi: add the possibility to reset the chip with a gpio
- doc: device-tree-bindings: add TIS TPMv2.0 SPI module info
- test/py: add TPMv2.x test suite
- tpm: add a Sandbox TPMv2.x driver
- doc: device-tree-bindings: add Sandbox TPMv2.0 module info
- sandbox: dts: add Sandbox TPMv2.x node
- configs: add TPMv2.x support in Sandbox
- tpm: fix typo in kernel doc
- tpm: compile Sandbox driver by default
- tpm: remove stale symbol in Kconfig
- tpm: allow TPM v1 and v2 to be compiled at the same time
- test/py: tpm2: switch from ‘tpm’ to ‘tpm2’ command
- tpm: make TPM_V2 be compiled by default
- sandbox: compile both TPM stack versions and drivers
- tpm: sandbox: fix wrong check on pcr_map
- tpm: sandbox: fix wrong assignment with a simplification
Details of U-Boot commands
Available commands for v2.0 TPMs in U-Boot are currently:
DICTIONARY ATTACK LOCK RESET
DICTIONARY ATTACK CHANGE PARAMETERS
HIERARCHY CHANGE AUTH
With this set of functions, minimal handling is possible with the following sequence.
First, the TPM stack in U-Boot must be initialized with:
> tpm init
Then, the STARTUP command must be sent.
> tpm startup TPM2_SU_CLEAR
To enable full TPM capabilities, one must request to continue the self tests (or do them all again).
> tpm self_test full
> tpm self_test continue
This is enough to pursue measured boot as one just need to extend the PCR as needed, giving 1/ the PCR number and 2/ the address where the digest is stored:
> tpm pcr_extend 0 0x4000000
Reading of the extended value is of course possible with:
> tpm pcr_read 0 0x4000000
Managing passwords is about limiting some commands to be sent without previous authentication. This is also possible with the minimum set of commands recently committed, and there are two ways of implementing it. One is quite complicated and features the use of a token together with cryptographic derivations at each exchange. Another solution, less invasive, is to use a single password. Changing passwords was previously done with a single
TAKE OWNERSHIP command, while today a
CLEAR must precede a
CHANGE AUTH. Each of them may act upon different hierarchies. Hierarchies are some kind of authority level and do not act upon the same commands. For the example, let’s use the
LOCKOUT hierarchy: the locking mechanism blocking the TPM for a given amount of time after a number of failed authentications, to mitigate dictionary attacks.
> tpm clear TPM2_RH_LOCKOUT [<pw>]
> tpm change_auth TPM2_RH_LOCKOUT <new_pw> [<old_pw>]
Drawback of this implementation: as opposed to the token/hash solution, there is no protection against packet replay.
Please note that a
CLEAR does much more than resetting passwords, it entirely resets the whole TPM configuration.
Finally, Dictionary Attack Mitigation (DAM) parameters can also be changed. It is possible to reset the failure counter (aka. the maximum number of attempts before lockout) as well as to disable the lockout entirely. It is possible to check the parameters have been correctly applied.
> tpm dam_reset [<pw>]
> tpm dam_parameters 0xffff 1 0 [<pw>]
> tpm get_capability 0x0006 0x020e 0x4000000 4
In the above example, the DAM parameters are reset, then the maximum number of tries before lockout is set to 0xffff, the delay before decrementing the failure counter by 1 and the lockout is entirely disabled. These parameters are for testing purpose. The third command is explained in the specification but basically retrieves 4 values starting at capability 0x6, property index 0x20e. It will display first the failure counter, followed by the three parameters previously changed.
Although TPMs are meant to be black boxes, U-Boot current support is too light to really protect against replay attacks as one could spoof the bus and resend the exact same packets after taking ownership of the platform in order to get these secrets out. Additional developments are needed in U-Boot to protect against these attacks. Additionally, even with this extra security level, all the above logic is only safe when used in the context of a secure boot environment.
Thanks to this work from Bootlin, U-Boot has basic support for TPM 2.0 devices connected over SPI. Do not hesitate to contact us if you need support or help around TPM 2.0 support, either in U-Boot or Linux.