Trusted Platform Module (TPM)

Some computers come with a TPM, which is a small side processor which can perform certain operations which include RSA key generation and signing, a random number generator, accessing a small amount of NVRAM, and a set of PCRs which can be used to measure software state (this is TPMs most famous use, for authenticating a boot sequence).

The TPM NVRAM and PCR APIs are not supported by Botan at this time, patches welcome.

Currently, we support TPM v1.2 as well as v2.0 systems via independent wrappers of TrouSerS (http://trousers.sourceforge.net/) for TPM v1.2 and tpm2-tss (https://github.com/tpm2-software/tpm2-tss) for TPM v2.0. Note however that the support for TPM v1.2 is deprecated as of Botan 3.5.0 and will be removed in a future release.

TPM 2.0 Wrappers

Added in version 3.6.0.

Botan’s TPM v2.0 support is currently based on a wrapper of the tpm2-tss library (https://github.com/tpm2-software/tpm2-tss). The code is tested in CI against the swtpm simulator (https://github.com/stefanberger/swtpm).

Support for TPM v2.0 is provided by the tpm2 module which is not built by default as it requires an external dependency. Use the BOTAN_HAS_TPM2 macro to ensure that support for TPM v2.0 is available in your build of Botan.

The entire implementation is wrapped into the Botan::TPM2 namespace. The remainder of this section will omit the namespace prefix for brevity.

TPM 2.0 Context

The TPM context is the main entry point for all TPM operations. Also, it provides authorative information about the TPM’s capabilities and allows persisting and evicting keys into the TPM’s NVRAM.

class Botan::TPM2::Context
std::shared_ptr<Context> create(const std::string &tcti)

Create a TPM2 context and connect to it via the given TPM Command Transmission Interface (TCTI). The TCTI string is a colon-separated specifier of the form tcti_name[:tcti_options=value,...].

std::shared_ptr<Context> create(std::optional<std::string> tcti, std::optional<std::string> conf)

Create a TPM2 context and connect to it via the given TPM Command Transmission Interface (TCTI). The configuration string is passed to the TCTI. Both values may by empty, in which case the TPM-TSS2 will try to determine them from default values.

TPM2_HANDLE persist(TPM2::PrivateKey &key, const SessionBundle &sessions, std::span<const uint8_t> auth_value, std::optional<TPM2_HANDLE> persistent_handle)

Persists the given key in the TPM’s NVRAM. The returned handle can be used to load the key back into the TPM after a reboot. The auth_value is used to re-authenticate operations after transforming it to a persistent key.

void evict(std::unique_ptr<TPM2::PrivateKey> key, const SessionBundle &sessions)

Evicts the key from the TPM’s NVRAM. The key must be a persistent key and won’t be available for any further use after the eviction. In particular it won’t be re-transformed into a transient key either.

bool supports_botan_crypto_backend()

Returns whether the current configuration supports the Botan crypto backend. This might return false if Botan was not built with the tpm2_crypto_backend enabled or the TPM2-TSS library is too old (3.x or older).

void use_botan_crypto_backend(std::shared_ptr<Botan::RandomNumberGenerator> rng)

Enables the Botan crypto backend for this context. The RNG is needed to generate key material for the communication with the TPM. It is crucial that this RNG does not depend on the TPM for its entropy as this would create a chicken-and-egg problem.

bool supports_algorithm(std::string_view algo_name)

Returns whether the TPM supports the given algorithm. The algo_name is the name of the algorithm as used in Botan. Eg. “RSA”, “SHA-256”, “AES-128”, “OAEP(SHA-256)”, etc.

For further information about the functionality of the TPM context, please refer to the doxygen comments in tpm2_context.h.

TPM 2.0 Sessions

TPM v2.0 uses sessions to authorize actions on the TPM, encrypt the communication between the application and the TPM and perform audits of the operations performed.

Botan provides a Session class to handle the creation of sessions and comes with a SessionBundle helper to manage multiple sessions to be passed to the TPM commands.

class Botan::TPM2::Session
std::shared_ptr<Session> unauthenticated_session(const std::shared_ptr<Context> &ctx, std::string_view sym_algo, std::string_view hash_algo)

Creates an unauthenticated session, i.e. does not provide protection against man-in-the-middle attacks by adversaries who can intercept and modify the communication between the application and the TPM.

The sym_algo and hash_algo parameters specify the symmetric cipher used to encrypt parameters flowing to and from the TPM and the hash of the HMAC algorithm used to protect the integrity of the communication.

std::shared_ptr<Session> authenticated_session(const std::shared_ptr<Context> &ctx, const PrivateKey &tpm_key, std::string_view sym_algo, std::string_view hash_algo)

Creates an authenticated session, i.e. it does provide protection against man-in-the-middle attacks by adversaries who can intercept and modify the communication between the application and the TPM, under the assumption that the tpm_key is trustworthy and known only to the TPM.

The sym_algo and hash_algo parameters specify the symmetric cipher used to encrypt parameters flowing to and from the TPM and the hash of the HMAC algorithm used to protect the integrity of the communication.

Currently, there’s no support for other TPM sessions.

TPM 2.0 Random Number Generator

The RandomNumberGenerator is an adapter to use the TPM’s random number generator as a source of entropy. It behaves exactly like any other RNG in Botan.

class Botan::TPM2::RandomNumberGenerator
RandomNumberGenerator(std::shared_ptr<Context> ctx, SessionBundle sessions)

Creates a new RNG object which uses the TPM’s random number generator as a source of entropy. The sessions parameter is a bundle of sessions to be used for the RNG operations.

Asymmetric Keys hosted by a TPM 2.0

The TPM v2.0 supports RSA and ECC keys. Botan provides the classed PrivateKey and PublicKey in the TPM2 namespace, to manage and use asymmetric keys on the TPM. Additionally there are derived classes for RSA and ECC. Currently, RSA keys can be used for signing and encryption, while ECC keys can only be used for ECDSA signing (i.e., ECDH, ECSCHNORR, and SM2 are not supported).

Objects of these classes can be used throughout the Botan library to perform cryptographic operations with TPM keys wherever an abstract Botan::Private_Key is expected.

class Botan::TPM2::PublicKey
std::unique_ptr<Public_Key> load_persistent(const std::shared_ptr<Context> &ctx, TPM2_HANDLE persistent_object_handle, const SessionBundle &sessions)

Loads a public key that is persistent in the TPM’s NVRAM given a persistent_object_handle.

std::unique_ptr<Public_Key> load_transient(const std::shared_ptr<Context> &ctx, std::span<const uint8_t> public_blob, const SessionBundle &sessions)

Loads a public key from the given public_blob which is essentially a serialization of a public key returned from a TPM key pair creation.

std::vector<uint8_t> raw_public_key_bits() const

Returns a serialized representation of the public key. This blob can be loaded back into the TPM as a transient public key.

class Botan::TPM2::PrivateKey
std::unique_ptr<Private_Key> load_persistent(const std::shared_ptr<Context> &ctx, TPM2_HANDLE persistent_object_handle, std::span<const uint8_t> auth_value, const SessionBundle &sessions)

Loads a private key that is persistent in the TPM’s NVRAM given a persistent_object_handle and an auth_value (e.g. a password).

std::unique_ptr<Private_Key> load_transient(const std::shared_ptr<Context> &ctx, std::span<const uint8_t> auth_value, const TPM2::PrivateKey &parent, std::span<const uint8_t> public_blob, std::span<const uint8_t> private_blob, const SessionBundle &sessions)

Loads a private key from the given public_blob and private_blob returned from a TPM key pair creation. To decipher the private_blob, a parent key is needed (the same as the one used to create the key). The auth_value is used to authenticate private operations.

std::unique_ptr<PrivateKey> create_transient_from_template(const std::shared_ptr<Context> &ctx, const SessionBundle &sessions, ESYS_TR parent, const TPMT_PUBLIC &key_template, const TPM2B_SENSITIVE_CREATE &sensitive_data);

Creates a new transient key pair on the TPM using the given key_template and sensitive_data under the given parent key. This is a low-level function, and it assumes that the caller knows how to create valid key_template and sensitive_data structures. Typically, users should resort to using the creation functions in the derived private key classes.

secure_vector<uint8_t> raw_private_key_bits() const

Returns an encrypted “private blob” of the TPM private key if it is a transient key. This blob can only be decrypted by the TPM that created it when loading the key back into the TPM.

Botan provides a set of derived classes for RSA keys, which are used to create and manage RSA keys on the TPM.

class Botan::TPM2::RSA_PrivateKey
std::unique_ptr<TPM2::PrivateKey> create_unrestricted_transient(const std::shared_ptr<Context> &ctx, const SessionBundle &sessions, std::span<const uint8_t> auth_value, const TPM2::PrivateKey &parent, uint16_t keylength, std::optional<uint32_t> exponent);

Creates a new RSA key pair on the TPM with the given keylength and an optional exponent. Typical users should not specify the exponent, as support for any but the default exponent (65537) is optional in the TPM v2.0 specification.

Keys generated with this function are not restricted in their usage. They may be used both for signing and data encryption with various padding schemes. Furthermore, they are transient, i.e. they are not stored in the TPM’s NVRAM and must be loaded from their public and private blobs after a reboot.

Similarly, Botan provides a set of derived classes for ECC keys.

class Botan::TPM2::EC_PrivateKey
static std::unique_ptr<TPM2::PrivateKey> create_unrestricted_transient(const std::shared_ptr<Context> &ctx, const SessionBundle &sessions, std::span<const uint8_t> auth_value, const TPM2::PrivateKey &parent, const EC_Group &group);

Creates a new ECC key pair on the TPM with the given group. The group must be one of the supported curves by the TPM and currently must be one of the NIST curves (secp192r1, secp224r1, secp256r1, secp384r1, secp521r1).

Keys generated with this function are not restricted in their usage. They may only be used for signing: Currently, Botan only supports creating ECDSA keys. Furthermore, they are transient, i.e. they are not stored in the TPM’s NVRAM and must be loaded from their public and private blobs after a reboot.

Once a transient key pair was created on the TPM, it can be persisted into the TPM’s NVRAM to make it available across reboots independently of the “private blob”. This is done by passing the key pair to the Context::persist method.

Botan as a TPM2-TSS Crypto Backend

The TPM2-TSS library (4.0 and later) provides a callback API to override its default crypto backend (OpenSSL or mbedtls). Botan can optionally use this API to provide a Botan-based crypto backend for TPM2-TSS and thus allowing to avoid a dependency on another cryptographic library in applications.

Once a Context is created, the Botan-based crypto backend may be enabled for it via the Context::use_botan_crypto_backend method. This will only succeed if the method Context::supports_botan_crypto_backend returns true.

TPM 2.0 Example

The following example demonstrates how to create a TPM key pair and sign a Certificate Signing Request (CSR) with it. This may be useful if one wants to host a private key for TLS client authentication in a TPM, for example.

#include <iostream>

#include <botan/build.h>

#if defined(BOTAN_HAS_TPM2)

   #include <botan/auto_rng.h>
   #include <botan/hex.h>

   #include <botan/pkcs10.h>
   #include <botan/pkix_types.h>
   #include <botan/x509_ext.h>
   #include <botan/x509_key.h>

   #include <botan/tpm2_context.h>
   #include <botan/tpm2_rng.h>
   #include <botan/tpm2_rsa.h>
   #include <botan/tpm2_session.h>

std::span<const uint8_t> as_byteview(std::string_view str) {
   return {reinterpret_cast<const uint8_t*>(str.data()), str.size()};
}

int main() {
   // This TCTI configuration is just an example, adjust as needed!
   constexpr auto tcti_nameconf = "tabrmd:bus_name=net.randombit.botan.tabrmd,bus_type=session";
   constexpr auto private_key_auth = "notguessable";
   constexpr size_t key_length = 2048;

   // Set up connection to TPM
   auto ctx = Botan::TPM2::Context::create(std::string(tcti_nameconf));

   // Create a TPM-backed RNG
   auto tpm_rng = Botan::TPM2::RandomNumberGenerator(ctx);

   if(ctx->supports_botan_crypto_backend()) {
      ctx->use_botan_crypto_backend([&] {
         // We need an RNG that is functionally independent from the TPM, to use
         // in the crypto backend. Also, it is crucial not to use the TPM-backed
         // RNG as the underlying source for the software RNG. This could lead
         // to TPM command sequence errors when the software RNG decides to
         // transparently pull new entropy from the TPM while another TPM
         // command is being processed in the crypto backend.
         //
         // Nevertheless, periodic reseeds from the TPM-backed RNG as shown
         // below is fine, as this serializes the TPM commands properly. In this
         // example we leave it at a single up-front reseed.
         auto software_rng = std::make_shared<Botan::AutoSeeded_RNG>();
         software_rng->reseed_from_rng(tpm_rng);
         return software_rng;
      }());
      std::cout << "Botan crypto backend enabled\n";
   }

   // Create an encrypted and "authenticated" session to the TPM using the SRK
   // This assumes that the SRK is a persistent object, that is accessible
   // without authentication.
   auto storage_root_key = ctx->storage_root_key({}, {});
   auto session = Botan::TPM2::Session::authenticated_session(ctx, *storage_root_key);

   // Create a private key and persist it into the TPM
   auto cert_private_key = Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(
      ctx, session, as_byteview(private_key_auth), *storage_root_key, key_length);
   const auto persistent_handle = ctx->persist(*cert_private_key, session, as_byteview(private_key_auth));
   std::cout << "New private key created\n";
   std::cout << "  Persistent handle: 0x" << std::hex << persistent_handle << '\n';

   // To access the key in the future, load it from the TPM as seen below.
   // For now, we still have the key in memory and can use it directly.
   //
   //   auto loaded_private_key =
   //      Botan::TPM2::PrivateKey::load_persistent(ctx,
   //                                               persistent_handle,
   //                                               as_byteview(private_key_auth),
   //                                               session);

   // Create a Certificate Signing Request (CSR)
   const Botan::X509_DN dn({
      {"X520.CommonName", "TPM-hosted test"},
      {"X520.Country", "DE"},
      {"X520.Organization", "Rohde & Schwarz"},
      {"X520.OrganizationalUnit", "GB11"},
   });

   // Set up relevant extensions
   Botan::Extensions extensions;
   extensions.add_new(std::make_unique<Botan::Cert_Extension::Basic_Constraints>(false /* not a CA */));
   extensions.add_new(std::make_unique<Botan::Cert_Extension::Key_Usage>(
      Botan::Key_Constraints(Botan::Key_Constraints::DigitalSignature | Botan::Key_Constraints::KeyEncipherment)));
   extensions.add_new(std::make_unique<Botan::Cert_Extension::Extended_Key_Usage>(
      std::vector{Botan::OID::from_name("PKIX.ServerAuth").value()}));
   extensions.add_new(std::make_unique<Botan::Cert_Extension::Subject_Alternative_Name>([] {
      Botan::AlternativeName alt_name;
      alt_name.add_dns("rohde-schwarz.com");
      alt_name.add_email("rene.meusel@rohde-schwarz.com");
      return alt_name;
   }()));
   extensions.add_new(
      std::make_unique<Botan::Cert_Extension::Subject_Key_ID>(cert_private_key->public_key_bits(), "SHA-256"));

   // All done, create the CSR
   auto csr = Botan::PKCS10_Request::create(*cert_private_key, dn, extensions, "SHA-256", tpm_rng, "PSS(SHA-256)");

   // Print results
   std::cout << '\n';
   std::cout << "New Certificate Signing Request:\n";
   std::cout << csr.PEM_encode() << '\n';

   return 0;
}

#else

int main() {
   std::cerr << "TPM2 support not enabled in this build\n";
   return 1;
}

#endif

TPM 1.2 Wrappers

Added in version 1.11.26.

Currently v1.2 TPMs are supported via a wrapper of the TrouSerS (http://trousers.sourceforge.net/) library. However, this wrapper is deprecated and will be removed in a future release. The current code has been tested with an ST TPM running in a Lenovo laptop.

Test for TPM support with the macro BOTAN_HAS_TPM, include <botan/tpm.h>.

First, create a connection to the TPM with a TPM_Context. The context is passed to all other TPM operations, and should remain alive as long as any other TPM object which the context was passed to is still alive, otherwise errors or even an application crash are possible. In the future, the API may change to using shared_ptr to remove this problem.

class TPM_Context
TPM_Context(pin_cb cb, const char *srk_password)

The (somewhat improperly named) pin_cb callback type takes a std::string as an argument, which is an informative message for the user. It should return a string containing the password entered by the user.

Normally the SRK password is null. Use nullptr to signal this.

The TPM contains a RNG of unknown design or quality. If that doesn’t scare you off, you can use it with TPM_RNG which implements the standard RandomNumberGenerator interface.

class TPM_RNG
TPM_RNG(TPM_Context &ctx)

Initialize a TPM RNG object. After initialization, reading from this RNG reads from the hardware? RNG on the TPM.

The v1.2 TPM uses only RSA, but because this key is implemented completely in hardware it uses a different private key type, with a somewhat different API to match the TPM’s behavior.

class TPM_PrivateKey
TPM_PrivateKey(TPM_Context &ctx, size_t bits, const char *key_password)

Create a new RSA key stored on the TPM. The bits should be either 1024 or 2048; the TPM interface hypothetically allows larger keys but in practice no v1.2 TPM hardware supports them.

The TPM processor is not fast, be prepared for this to take a while.

The key_password is the password to the TPM key ?

std::string register_key(TPM_Storage_Type storage_type)

Registers a key with the TPM. The storage_type can be either TPM_Storage_Type::User or TPM_Storage_Type::System. If System, the key is stored on the TPM itself. If User, it is stored on the local hard drive in a database maintained by an intermediate piece of system software (which actual interacts with the physical TPM on behalf of any number of applications calling the TPM API).

The TPM has only some limited space to store private keys and may reject requests to store the key.

In either case the key is encrypted with an RSA key which was generated on the TPM and which it will not allow to be exported. Thus (so goes the theory) without physically attacking the TPM

Returns a UUID which can be passed back to constructor below.

TPM_PrivateKey(TPM_Context &ctx, const std::string &uuid, TPM_Storage_Type storage_type)

Load a registered key. The UUID was returned by the register_key function.

std::vector<uint8_t> export_blob() const

Export the key as an encrypted blob. This blob can later be presented back to the same TPM to load the key.

TPM_PrivateKey(TPM_Context &ctx, const std::vector<uint8_t> &blob)

Load a TPM key previously exported as a blob with export_blob.

std::unique_ptr<Public_Key> public_key() const

Return the public key associated with this TPM private key.

TPM does not store public keys, nor does it support signature verification.

TSS_HKEY handle() const

Returns the bare TSS key handle. Use if you need to call the raw TSS API.

A TPM_PrivateKey can be passed to a PK_Signer constructor and used to sign messages just like any other key. Only PKCS #1 v1.5 signatures are supported by the v1.2 TPM.

std::vector<std::string> TPM_PrivateKey::registered_keys(TPM_Context &ctx)

This static function returns the list of all keys (in URL format) registered with the system