From 6aeb44ba4c76c232020a147cd5897ca77851b2ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Meusel?= Date: Wed, 9 Oct 2024 11:41:40 +0200 Subject: [PATCH] Basic support for TPM 2.0 in the FFI This merely exposes enough functionality to establish an RNG backed by the TPM. Explicitly enabling the usage of Botan's crypto primitives for the communication with the TPM is also included. --- doc/api_ref/ffi.rst | 59 +++++++++++ src/lib/ffi/ffi.cpp | 4 +- src/lib/ffi/ffi.h | 85 ++++++++++++++++ src/lib/ffi/ffi_tpm2.cpp | 205 +++++++++++++++++++++++++++++++++++++++ src/tests/test_ffi.cpp | 53 ++++++++++ 5 files changed, 405 insertions(+), 1 deletion(-) create mode 100644 src/lib/ffi/ffi_tpm2.cpp diff --git a/doc/api_ref/ffi.rst b/doc/api_ref/ffi.rst index e4309b0d88b..b328d5ecb6e 100644 --- a/doc/api_ref/ffi.rst +++ b/doc/api_ref/ffi.rst @@ -177,6 +177,10 @@ The following enum values are defined in the FFI header: calling :cpp:func:`botan_hash_destroy` on a ``botan_rng_t`` object will cause this error. +.. cpp:enumerator:: BOTAN_FFI_TPM_ERROR = -78 + + An error occured when performing TPM2 interactions. + .. cpp:enumerator:: BOTAN_FFI_ERROR_UNKNOWN_ERROR = -100 Something bad happened, but we are not sure why or how. @@ -1337,6 +1341,61 @@ Public Key Encapsulation Destroy the operation, freeing memory + +TPM 2.0 Functions +---------------------------------------- + +.. versionadded:: 3.6.0 + +.. cpp:type:: opaque* botan_tpm2_ctx_t + + An opaque data type for a TPM 2.0 context object. Don't mess with it. + +.. cpp:type:: opaque* botan_tpm2_session_t + + An opaque data type for a TPM 2.0 session object. Don't mess with it. + + +.. cpp:function:: int botan_tpm2_supports_crypto_backend() + + Returns 1 if the Botan-based TPM 2.0 crypto backend is available, 0 otherwise. + +.. cpp:function:: int botan_tpm2_ctx_init(botan_tpm2_ctx_t* ctx_out, const char* tcti_nameconf) + + Initialize a TPM 2.0 context object. The TCTI name and configuration are + mangled into a single string separated by a colon. for instance "device:/dev/tpm0". + +.. cpp:function:: int botan_tpm2_ctx_init_ex(botan_tpm2_ctx_t* ctx_out, const char* tcti_name, const char* tcti_conf) + + Initialize a TPM 2.0 context object. The TCTI name and configuration are + passed as separate strings. + +.. cpp:function:: int botan_tpm2_ctx_enable_crypto_backend(botan_tpm2_ctx_t ctx, botan_rng_t rng) + + Enable the Botan-based TPM 2.0 crypto backend. Note that the random number + generator passed to this function must not be dependent on the TPM itself. + +.. cpp:function:: int botan_tpm2_unauthenticated_session_init(botan_tpm2_session_t* session_out, botan_tpm2_ctx_t ctx) + + Initialize an unauthenticated session that can be used to encrypt the + communication between your application and the TPM. + +.. cpp:function:: int botan_tpm2_rng_init(botan_rng_t* rng_out, \ + botan_tpm2_ctx_t ctx, \ + botan_tpm2_session_t s1, \ + botan_tpm2_session_t s2, \ + botan_tpm2_session_t s3) + + Initialize a random number generator that uses the TPM as a source of entropy. + +.. cpp:function:: int botan_tpm2_ctx_destroy(botan_tpm2_ctx_t ctx) + + Destroy a TPM 2.0 context object. + +.. cpp:function:: int botan_tpm2_session_destroy(botan_tpm2_session_t session) + + Destroy a TPM 2.0 session object. + X.509 Certificates ---------------------------------------- diff --git a/src/lib/ffi/ffi.cpp b/src/lib/ffi/ffi.cpp index 94468cf6aa6..876c29b8eae 100644 --- a/src/lib/ffi/ffi.cpp +++ b/src/lib/ffi/ffi.cpp @@ -67,13 +67,15 @@ int ffi_map_error_type(Botan::ErrorType err) { case Botan::ErrorType::IoError: case Botan::ErrorType::Pkcs11Error: case Botan::ErrorType::CommonCryptoError: - case Botan::ErrorType::TPMError: case Botan::ErrorType::ZlibError: case Botan::ErrorType::Bzip2Error: case Botan::ErrorType::LzmaError: case Botan::ErrorType::DatabaseError: return BOTAN_FFI_ERROR_SYSTEM_ERROR; + case Botan::ErrorType::TPMError: + return BOTAN_FFI_ERROR_TPM_ERROR; + case Botan::ErrorType::NotImplemented: return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; case Botan::ErrorType::OutOfMemory: diff --git a/src/lib/ffi/ffi.h b/src/lib/ffi/ffi.h index 874408acb18..20afe6a74a8 100644 --- a/src/lib/ffi/ffi.h +++ b/src/lib/ffi/ffi.h @@ -138,6 +138,7 @@ enum BOTAN_FFI_ERROR { BOTAN_FFI_ERROR_TLS_ERROR = -75, BOTAN_FFI_ERROR_HTTP_ERROR = -76, BOTAN_FFI_ERROR_ROUGHTIME_ERROR = -77, + BOTAN_FFI_ERROR_TPM_ERROR = -78, BOTAN_FFI_ERROR_UNKNOWN_ERROR = -100, }; @@ -2201,6 +2202,90 @@ BOTAN_FFI_EXPORT(3, 0) int botan_zfec_decode( size_t K, size_t N, const size_t* indexes, uint8_t* const* inputs, size_t shareSize, uint8_t** outputs); +/** +* TPM2 context +*/ +typedef struct botan_tpm2_ctx_struct* botan_tpm2_ctx_t; + +/** +* TPM2 session +*/ +typedef struct botan_tpm2_session_struct* botan_tpm2_session_t; + +/** +* Checks if Botan's TSS2 crypto backend can be used in this build +* @returns 1 if the crypto backend can be enabled +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_supports_crypto_backend(); + +/** +* Initialize a TPM2 context +* @param ctx_out output TPM2 context +* @param tcti_nameconf TCTI config (may be nullptr) +* @return 0 on success +*/ +BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_ctx_init(botan_tpm2_ctx_t* ctx_out, const char* tcti_nameconf); + +/** +* Initialize a TPM2 context +* @param ctx_out output TPM2 context +* @param tcti_name TCTI name (may be nullptr) +* @param tcti_conf TCTI config (may be nullptr) +* @return 0 on success +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_ctx_init_ex(botan_tpm2_ctx_t* ctx_out, const char* tcti_name, const char* tcti_conf); + +/** +* Enable Botan's TSS2 crypto backend that replaces the cryptographic functions +* required for the communication with the TPM with implementations provided +* by Botan instead of using TSS' defaults OpenSSL or mbedTLS. +* Note that the provided @p rng should not be dependent on the TPM and the +* caller must ensure that it remains usable for the lifetime of the @p ctx. +* @param ctx TPM2 context +* @param rng random number generator to be used by the crypto backend +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_ctx_enable_crypto_backend(botan_tpm2_ctx_t ctx, botan_rng_t rng); + +/** +* Frees all resouces of a TPM2 context +* @param ctx TPM2 context +* @return 0 on success +*/ +BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_ctx_destroy(botan_tpm2_ctx_t ctx); + +/** +* Initialize a random number generator object via TPM2 +* @param rng_out rng object to create +* @param ctx TPM2 context +* @param s1 the first session to use (optional, may be nullptr) +* @param s2 the second session to use (optional, may be nullptr) +* @param s3 the third session to use (optional, may be nullptr) +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_rng_init(botan_rng_t* rng_out, + botan_tpm2_ctx_t ctx, + botan_tpm2_session_t s1, + botan_tpm2_session_t s2, + botan_tpm2_session_t s3); + +/** +* Create an unauthenticated session for use with TPM2 +* @param session_out the session object to create +* @param ctx TPM2 context +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_unauthenticated_session_init(botan_tpm2_session_t* session_out, botan_tpm2_ctx_t ctx); + +/** +* Create an unauthenticated session for use with TPM2 +* @param session the session object to destroy +*/ +BOTAN_FFI_EXPORT(3, 6) +int botan_tpm2_session_destroy(botan_tpm2_session_t session); + #ifdef __cplusplus } #endif diff --git a/src/lib/ffi/ffi_tpm2.cpp b/src/lib/ffi/ffi_tpm2.cpp new file mode 100644 index 00000000000..c5cb5fbd665 --- /dev/null +++ b/src/lib/ffi/ffi_tpm2.cpp @@ -0,0 +1,205 @@ +/* +* (C) 2024 Jack Lloyd +* (C) 2024 René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity GmbH +* +* Botan is released under the Simplified BSD License (see license.txt) +*/ + +#include + +#include +#include +#include + +#if defined(BOTAN_HAS_TPM2) + #include + #include + #include + #include +#endif + +extern "C" { + +using namespace Botan_FFI; + +#if defined(BOTAN_HAS_TPM2) + +// These wrappers are required since BOTAN_FFI_DECLARE_STRUCT internally +// produces a unique pointer, but the TPM types are meant to be used as +// shared pointers. + +struct botan_tpm2_ctx_wrapper { + std::shared_ptr ctx; +}; + +struct botan_tpm2_session_wrapper { + std::shared_ptr session; +}; + +BOTAN_FFI_DECLARE_STRUCT(botan_tpm2_ctx_struct, botan_tpm2_ctx_wrapper, 0xD2B95E15); +BOTAN_FFI_DECLARE_STRUCT(botan_tpm2_session_struct, botan_tpm2_session_wrapper, 0x9ACCAB52); + +} // extern "C" + +namespace { + +Botan::TPM2::SessionBundle sessions(botan_tpm2_session_t s1, botan_tpm2_session_t s2, botan_tpm2_session_t s3) { + return Botan::TPM2::SessionBundle((s1 != nullptr) ? safe_get(s1).session : nullptr, + (s2 != nullptr) ? safe_get(s2).session : nullptr, + (s3 != nullptr) ? safe_get(s3).session : nullptr); +} + +} // namespace + +extern "C" { + +#endif + +int botan_tpm2_supports_crypto_backend() { +#if defined(BOTAN_HAS_TPM2) + return Botan::TPM2::Context::supports_botan_crypto_backend() ? 1 : 0; +#else + return 0; +#endif +} + +int botan_tpm2_ctx_init(botan_tpm2_ctx_t* ctx_out, const char* tcti_nameconf) { +#if defined(BOTAN_HAS_TPM2) + return ffi_guard_thunk(__func__, [=]() -> int { + if(ctx_out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + auto ctx = std::make_unique(); + + auto tcti = [=]() -> std::optional { + if(tcti_nameconf == nullptr) { + return {}; + } else { + return std::string(tcti_nameconf); + } + }(); + + ctx->ctx = Botan::TPM2::Context::create(std::move(tcti)); + *ctx_out = new botan_tpm2_ctx_struct(std::move(ctx)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ctx_out, tcti_nameconf); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_tpm2_ctx_init_ex(botan_tpm2_ctx_t* ctx_out, const char* tcti_name, const char* tcti_conf) { +#if defined(BOTAN_HAS_TPM2) + return ffi_guard_thunk(__func__, [=]() -> int { + if(ctx_out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + auto ctx = std::make_unique(); + + auto tcti_name_str = [=]() -> std::optional { + if(tcti_name == nullptr) { + return {}; + } else { + return std::string(tcti_name); + } + }(); + + auto tcti_conf_str = [=]() -> std::optional { + if(tcti_conf == nullptr) { + return {}; + } else { + return std::string(tcti_conf); + } + }(); + + ctx->ctx = Botan::TPM2::Context::create(std::move(tcti_name_str), std::move(tcti_conf_str)); + *ctx_out = new botan_tpm2_ctx_struct(std::move(ctx)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ctx_out, tcti_name, tcti_conf); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_tpm2_ctx_enable_crypto_backend(botan_tpm2_ctx_t ctx, botan_rng_t rng) { +#if defined(BOTAN_HAS_TPM2) + return BOTAN_FFI_VISIT(ctx, [=](botan_tpm2_ctx_wrapper& ctx_wrapper) -> int { + Botan::RandomNumberGenerator& rng_ref = safe_get(rng); + + // The lifetime of the RNG used for the crypto backend should be managed + // by the TPM2::Context. Here, we just need to trust the user that they + // keep the passed-in RNG instance intact for the lifetime of the context. + std::shared_ptr rng_ptr(&rng_ref, [](auto*) {}); + ctx_wrapper.ctx->use_botan_crypto_backend(rng_ptr); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(ctx, rng); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +/** + * Frees all resouces of a TPM2 context + * @param ctx TPM2 context + * @return 0 on success + */ +int botan_tpm2_ctx_destroy(botan_tpm2_ctx_t ctx) { +#if defined(BOTAN_HAS_TPM2) + return BOTAN_FFI_CHECKED_DELETE(ctx); +#else + BOTAN_UNUSED(ctx); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_tpm2_rng_init(botan_rng_t* rng_out, + botan_tpm2_ctx_t ctx, + botan_tpm2_session_t s1, + botan_tpm2_session_t s2, + botan_tpm2_session_t s3) { +#if defined(BOTAN_HAS_TPM2) + return BOTAN_FFI_VISIT(ctx, [=](botan_tpm2_ctx_wrapper& ctx_wrapper) -> int { + if(rng_out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + *rng_out = new botan_rng_struct( + std::make_unique(ctx_wrapper.ctx, sessions(s1, s2, s3))); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(rng_out, ctx, s1, s2, s3); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_tpm2_unauthenticated_session_init(botan_tpm2_session_t* session_out, botan_tpm2_ctx_t ctx) { +#if defined(BOTAN_HAS_TPM2) + return BOTAN_FFI_VISIT(ctx, [=](botan_tpm2_ctx_wrapper& ctx_wrapper) -> int { + if(session_out == nullptr) { + return BOTAN_FFI_ERROR_NULL_POINTER; + } + + auto session = std::make_unique(); + session->session = Botan::TPM2::Session::unauthenticated_session(ctx_wrapper.ctx); + *session_out = new botan_tpm2_session_struct(std::move(session)); + return BOTAN_FFI_SUCCESS; + }); +#else + BOTAN_UNUSED(session_out, ctx); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} + +int botan_tpm2_session_destroy(botan_tpm2_session_t session) { +#if defined(BOTAN_HAS_TPM2) + return BOTAN_FFI_CHECKED_DELETE(session); +#else + BOTAN_UNUSED(session); + return BOTAN_FFI_ERROR_NOT_IMPLEMENTED; +#endif +} +} diff --git a/src/tests/test_ffi.cpp b/src/tests/test_ffi.cpp index a576e31f4b2..b6b8cf06966 100644 --- a/src/tests/test_ffi.cpp +++ b/src/tests/test_ffi.cpp @@ -281,6 +281,10 @@ class FFI_RNG_Test final : public FFI_Test { botan_rng_t hwrng_rng = nullptr; botan_rng_t null_rng; botan_rng_t custom_rng; + botan_rng_t tpm2_rng = nullptr; + + botan_tpm2_ctx_t tpm2_ctx; + botan_tpm2_session_t tpm2_session; TEST_FFI_FAIL("invalid rng type", botan_rng_init, (&rng, "invalid_type")); @@ -367,6 +371,55 @@ class FFI_RNG_Test final : public FFI_Test { result.test_eq("custom_destroy_cb called", cb_counter, 5); } + const auto tcti_name = Test::options().tpm2_tcti_name().value_or(""); + const auto tcti_conf = Test::options().tpm2_tcti_conf().value_or(""); + if(tcti_name != "disabled" && + TEST_FFI_INIT(botan_tpm2_ctx_init_ex, (&tpm2_ctx, tcti_name.c_str(), tcti_conf.c_str()))) { + if(botan_tpm2_supports_crypto_backend() == 1) { + TEST_FFI_OK(botan_tpm2_ctx_enable_crypto_backend, (tpm2_ctx, system_rng)); + result.test_note("TPM2 crypto backend enabled"); + } else { + result.test_note("TPM2 crypto backend not supported"); + } + + // Create and use an RNG without a TPM2 session + // (communication between application and TPM won't be encrypted) + if(TEST_FFI_INIT(botan_tpm2_rng_init, (&tpm2_rng, tpm2_ctx, nullptr, nullptr, nullptr))) { + Botan::clear_mem(outbuf.data(), outbuf.size()); + + TEST_FFI_OK(botan_rng_get, (tpm2_rng, outbuf.data(), outbuf.size())); + TEST_FFI_OK(botan_rng_reseed, (tpm2_rng, 256)); + + TEST_FFI_OK(botan_rng_reseed_from_rng, (tpm2_rng, system_rng, 256)); + + uint8_t not_really_entropy[32] = {0}; + TEST_FFI_OK(botan_rng_add_entropy, (tpm2_rng, not_really_entropy, 32)); + TEST_FFI_OK(botan_rng_destroy, (tpm2_rng)); + } + + // Create an anonymous TPM2 session + if(TEST_FFI_INIT(botan_tpm2_unauthenticated_session_init, (&tpm2_session, tpm2_ctx))) { + // Create and use an RNG with an anonymous TPM2 session + // (communication between application and TPM will be encrypted) + if(TEST_FFI_INIT(botan_tpm2_rng_init, (&tpm2_rng, tpm2_ctx, tpm2_session, nullptr, nullptr))) { + Botan::clear_mem(outbuf.data(), outbuf.size()); + + TEST_FFI_OK(botan_rng_get, (tpm2_rng, outbuf.data(), outbuf.size())); + TEST_FFI_OK(botan_rng_reseed, (tpm2_rng, 256)); + + TEST_FFI_OK(botan_rng_reseed_from_rng, (tpm2_rng, system_rng, 256)); + + uint8_t not_really_entropy[32] = {0}; + TEST_FFI_OK(botan_rng_add_entropy, (tpm2_rng, not_really_entropy, 32)); + TEST_FFI_OK(botan_rng_destroy, (tpm2_rng)); + } + + TEST_FFI_OK(botan_tpm2_session_destroy, (tpm2_session)); + } + + TEST_FFI_OK(botan_tpm2_ctx_destroy, (tpm2_ctx)); + } + TEST_FFI_OK(botan_rng_destroy, (rng)); TEST_FFI_OK(botan_rng_destroy, (null_rng)); TEST_FFI_OK(botan_rng_destroy, (system_rng));