From 18a9a1677d5d006ed964361ff4c8dfb4d4c93ccf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Thu, 5 Sep 2024 16:05:41 +0200 Subject: [PATCH] Add documentation for the TPM 2.0 support Co-Authored-By: Amos Treiber --- doc/api_ref/providers.rst | 10 ++ doc/api_ref/rng.rst | 6 +- doc/api_ref/tpm.rst | 249 +++++++++++++++++++++++++++- doc/building.rst | 11 +- doc/dev_ref/contributing.rst | 10 +- doc/dev_ref/todo.rst | 1 + readme.rst | 2 +- src/examples/pkcs10_csr_on_tpm2.cpp | 119 +++++++++++++ 8 files changed, 391 insertions(+), 17 deletions(-) create mode 100644 src/examples/pkcs10_csr_on_tpm2.cpp diff --git a/doc/api_ref/providers.rst b/doc/api_ref/providers.rst index 0ea4683184f..a748a84bf56 100644 --- a/doc/api_ref/providers.rst +++ b/doc/api_ref/providers.rst @@ -23,6 +23,16 @@ The TPM 1.2 standard is a specification for a hardware device which provides cryptographic algorithms. Botan ships a :doc:`TPM provider ` for interacting with TPM devices. It is disabled by default. +TPM 2.0 +^^^^^^^^^^^^^ + +Botan ships a :doc:`TPM 2.0 provider ` for interacting with TPM 2.0 devices. +Access to the TPM is implemented via the TPM Software Stack (TSS) and is tested using +the open source `tpm2-tss implementation `__. +Botan allows to hook into the crypto callbacks of tpm2-tss (requires 4.0 or later) to +avoid pulling in another crypto library as a transitive dependency. +This provider is disabled by default. + CommonCrypto ^^^^^^^^^^^^^ diff --git a/doc/api_ref/rng.rst b/doc/api_ref/rng.rst index c286e1370ca..cfd1b7f4aee 100644 --- a/doc/api_ref/rng.rst +++ b/doc/api_ref/rng.rst @@ -223,10 +223,10 @@ on POWER ``darn``. If the relevant instruction is not available, the constructor of the class will throw at runtime. You can test beforehand by checking the result of ``Processor_RNG::available()``. -TPM_RNG -^^^^^^^^^^^^^^^^^ +TPM_RNG & TPM2_RNG +^^^^^^^^^^^^^^^^^^ -This RNG type allows using the RNG exported from a TPM chip. +These RNG types allow using the RNG exported from a TPM chip. PKCS11_RNG ^^^^^^^^^^^^^^^^^ diff --git a/doc/api_ref/tpm.rst b/doc/api_ref/tpm.rst index 7598c4bd817..c278cbba12b 100644 --- a/doc/api_ref/tpm.rst +++ b/doc/api_ref/tpm.rst @@ -1,8 +1,6 @@ Trusted Platform Module (TPM) ========================================== -.. versionadded:: 1.11.26 - 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 @@ -11,10 +9,249 @@ authenticating a boot sequence). The TPM NVRAM and PCR APIs are not supported by Botan at this time, patches welcome. -Currently only v1.2 TPMs are supported, and the only TPM library supported is -TrouSerS (http://trousers.sourceforge.net/). Hopefully both of these limitations -will be removed in a future release, in order to support newer TPM v2.0 systems. -The current code has been tested with an ST TPM running in a Lenovo laptop. +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 +---------------- + +.. versionadded:: 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. + +.. cpp:class:: Botan::TPM2::Context + + .. cpp:function:: std::shared_ptr 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,...]``. + + .. cpp:function:: std::shared_ptr create(std::optional tcti, std::optional 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. + + .. cpp:function:: TPM2_HANDLE persist(TPM2::PrivateKey& key, const SessionBundle& sessions, std::span auth_value, std::optional 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. + + .. cpp:function:: void evict(std::unique_ptr 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. + + .. cpp:function:: 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). + + .. cpp:function:: void use_botan_crypto_backend(std::shared_ptr 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. + + .. cpp:function:: 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. + +.. cpp:class:: Botan::TPM2::Session + + .. cpp:function:: std::shared_ptr unauthenticated_session(const std::shared_ptr& 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. + + .. cpp:function:: std::shared_ptr authenticated_session(const std::shared_ptr& 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. + +.. cpp:class:: Botan::TPM2::RandomNumberGenerator + + .. cpp:function:: RandomNumberGenerator(std::shared_ptr 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. ECC +is not supported at this time, but could be added in the future. + +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. + +.. cpp:class:: Botan::TPM2::PublicKey + + .. cpp:function:: std::unique_ptr load_persistent(const std::shared_ptr& 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``. + + .. cpp:function:: std::unique_ptr load_transient(const std::shared_ptr& ctx, std::span 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. + + .. cpp:function:: std::vector 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. + +.. cpp:class:: Botan::TPM2::PrivateKey + + .. cpp:function:: std::unique_ptr load_persistent(const std::shared_ptr& ctx, TPM2_HANDLE persistent_object_handle, std::span 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). + + .. cpp:function:: std::unique_ptr load_transient(const std::shared_ptr& ctx, std::span auth_value, const TPM2::PrivateKey& parent, std::span public_blob, std::span 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. + + .. cpp:function:: std::unique_ptr create_transient_from_template(const std::shared_ptr& 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. + + .. cpp:function:: secure_vector 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. + +.. cpp:class:: Botan::TPM2::RSA_PrivateKey + + .. cpp:function:: std::unique_ptr create_unrestricted_transient(const std::shared_ptr& ctx, const SessionBundle& sessions, std::span auth_value, const TPM2::PrivateKey& parent, uint16_t keylength, std::optional 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. + +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. + +.. literalinclude:: /../src/examples/pkcs10_csr_on_tpm2.cpp + :language: cpp + + +TPM 1.2 Wrappers +---------------- + +.. versionadded:: 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 ````. diff --git a/doc/building.rst b/doc/building.rst index 5abc50c2f38..5b6e9759146 100644 --- a/doc/building.rst +++ b/doc/building.rst @@ -420,7 +420,9 @@ by the user using - ``--with-sqlite3`` enables using sqlite3 databases in various contexts (TLS session cache, PSK database, etc). - - ``--with-tpm`` adds support for using TPM hardware via the TrouSerS library. + - ``--with-tpm`` adds support for TPM 1.2 hardware via the TrouSerS library. + + - ``--with-tpm2`` adds support for TPM 2.0 hardware via the TSS2 library. - ``--with-boost`` enables using some Boost libraries. In particular Boost.Filesystem is used for a few operations (but on most platforms, a @@ -1070,7 +1072,12 @@ Enable using sqlite3 for data storage ``--with-tpm`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ -Enable support for TPM +Enable support for TPM 1.2 + +``--with-tpm2`` +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +Enable support for TPM 2.0 ``--program-suffix=SUFFIX`` ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/dev_ref/contributing.rst b/doc/dev_ref/contributing.rst index 11240e86c18..ddb3acd3a17 100644 --- a/doc/dev_ref/contributing.rst +++ b/doc/dev_ref/contributing.rst @@ -310,11 +310,11 @@ need this functionality, and it can be done in the library for less than that, then it makes sense to just write the code. Yup. Currently the (optional) external dependencies of the library are several -compression libraries (zlib, bzip2, lzma), sqlite3 database, Trousers (TPM -integration), plus various operating system utilities like basic filesystem -operations. These provide major pieces of functionality which seem worth the -trouble of maintaining an integration with. +compression libraries (zlib, bzip2, lzma), sqlite3 database, Trousers (TPM 1.2 +integration), TSS2 (TPM 2.0 integration) plus various operating system utilities +like basic filesystem operations. These provide major pieces of functionality +which seem worth the trouble of maintaining an integration with. At this point the most plausible examples of an appropriate new external dependency are all deeper integrations with system level cryptographic -interfaces (CommonCrypto, CryptoAPI, /dev/crypto, iOS keychain, TPM 2.0, etc) +interfaces (CommonCrypto, CryptoAPI, /dev/crypto, iOS keychain, etc) diff --git a/doc/dev_ref/todo.rst b/doc/dev_ref/todo.rst index 6e456debfab..d616b194205 100644 --- a/doc/dev_ref/todo.rst +++ b/doc/dev_ref/todo.rst @@ -64,6 +64,7 @@ External Providers * Windows CryptoNG provider (ciphers, hashes) * Extend Apple CommonCrypto provider (HMAC, CMAC, RSA, ECDSA, ECDH) * Add support for iOS keychain access +* Extend support for TPM 2.0 (ECC keys, PCR, NVRAM, Policies, etc) TLS ---------------------------------------- diff --git a/readme.rst b/readme.rst index 3ee1fe5af46..78a84229463 100644 --- a/readme.rst +++ b/readme.rst @@ -125,7 +125,7 @@ Other Useful Things ---------------------------------------- * Full C++ PKCS #11 API wrapper -* Interfaces for TPM v1.2 device access +* Interfaces for TPM v1.2 and v2.0 device access * Simple compression API wrapping zlib, bzip2, and lzma libraries * RNG wrappers for system RNG and hardware RNGs * HMAC_DRBG and entropy collection system for userspace RNGs diff --git a/src/examples/pkcs10_csr_on_tpm2.cpp b/src/examples/pkcs10_csr_on_tpm2.cpp new file mode 100644 index 00000000000..b4979a866d0 --- /dev/null +++ b/src/examples/pkcs10_csr_on_tpm2.cpp @@ -0,0 +1,119 @@ +#include + +#include + +#if defined(BOTAN_HAS_TPM2) + + #include + #include + + #include + #include + #include + #include + + #include + #include + #include + #include + +std::span as_byteview(std::string_view str) { + return {reinterpret_cast(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(); + 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(false /* not a CA */)); + extensions.add_new(std::make_unique( + Botan::Key_Constraints(Botan::Key_Constraints::DigitalSignature | Botan::Key_Constraints::KeyEncipherment))); + extensions.add_new(std::make_unique( + std::vector{Botan::OID::from_name("PKIX.ServerAuth").value()})); + extensions.add_new(std::make_unique([] { + 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(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