diff --git a/src/lib/prov/tpm2/tpm2_context.cpp b/src/lib/prov/tpm2/tpm2_context.cpp index 1c3ca4e6d76..a1bcaec09dc 100644 --- a/src/lib/prov/tpm2/tpm2_context.cpp +++ b/src/lib/prov/tpm2/tpm2_context.cpp @@ -34,8 +34,8 @@ constexpr TPM2_HANDLE storage_root_key_handle = TPM2_HR_PERSISTENT + 1; } // namespace struct Context::Impl { - TSS2_TCTI_CONTEXT* m_tcti_ctx; - ESYS_CONTEXT* m_ctx; + ESYS_CONTEXT* m_ctx; /// m_ctx is owned by the library user + bool m_external; #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND) std::unique_ptr m_crypto_callback_state; @@ -51,29 +51,44 @@ bool Context::supports_botan_crypto_backend() noexcept { } std::shared_ptr Context::create(const std::string& tcti_nameconf) { + const auto nameconf_ptr = tcti_nameconf.c_str(); + + TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; + ESYS_CONTEXT* esys_ctx = nullptr; + check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(nameconf_ptr, &tcti_ctx)); + BOTAN_ASSERT_NONNULL(tcti_ctx); + check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */)); + BOTAN_ASSERT_NONNULL(esys_ctx); + // We cannot std::make_shared as the constructor is private - return std::shared_ptr(new Context(tcti_nameconf.c_str())); + return std::shared_ptr(new Context(esys_ctx, false /* context is managed by us */)); } std::shared_ptr Context::create(std::optional tcti, std::optional conf) { const auto tcti_ptr = tcti.has_value() ? tcti->c_str() : nullptr; const auto conf_ptr = conf.has_value() ? conf->c_str() : nullptr; + TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; + ESYS_CONTEXT* esys_ctx = nullptr; + check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize_Ex(tcti_ptr, conf_ptr, &tcti_ctx)); + BOTAN_ASSERT_NONNULL(tcti_ctx); + check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */)); + BOTAN_ASSERT_NONNULL(esys_ctx); + // We cannot std::make_shared as the constructor is private - return std::shared_ptr(new Context(tcti_ptr, conf_ptr)); + return std::shared_ptr(new Context(esys_ctx, false /* context is managed by us */)); } -Context::Context(const char* tcti_nameconf) : m_impl(std::make_unique()) { - check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(tcti_nameconf, &m_impl->m_tcti_ctx)); - BOTAN_ASSERT_NONNULL(m_impl->m_tcti_ctx); - check_rc("TPM2 Initialization", Esys_Initialize(&m_impl->m_ctx, m_impl->m_tcti_ctx, nullptr /* ABI version */)); - BOTAN_ASSERT_NONNULL(m_impl->m_ctx); +std::shared_ptr Context::create(ESYS_CONTEXT* esys_ctx) { + BOTAN_ASSERT_NONNULL(esys_ctx); + + // We cannot std::make_shared as the constructor is private + return std::shared_ptr(new Context(esys_ctx, true /* context is managed externally */)); } -Context::Context(const char* tcti_name, const char* tcti_conf) : m_impl(std::make_unique()) { - check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize_Ex(tcti_name, tcti_conf, &m_impl->m_tcti_ctx)); - BOTAN_ASSERT_NONNULL(m_impl->m_tcti_ctx); - check_rc("TPM2 Initialization", Esys_Initialize(&m_impl->m_ctx, m_impl->m_tcti_ctx, nullptr /* ABI version */)); +Context::Context(ESYS_CONTEXT* ctx, bool external) : m_impl(std::make_unique()) { + m_impl->m_ctx = ctx; + m_impl->m_external = external; BOTAN_ASSERT_NONNULL(m_impl->m_ctx); } @@ -81,7 +96,7 @@ void Context::use_botan_crypto_backend(const std::shared_ptrm_crypto_callback_state = std::make_unique(rng); - enable_crypto_callbacks(shared_from_this()); + set_crypto_callbacks(esys_context(), m_impl->m_crypto_callback_state.get()); #else BOTAN_UNUSED(rng); throw Not_Implemented("This build of botan does not provide the TPM2 crypto backend"); @@ -397,9 +412,24 @@ void Context::evict(std::unique_ptr key, const SessionBundle& } Context::~Context() { - if(m_impl) { + if(uses_botan_crypto_backend()) { + set_crypto_callbacks(esys_context(), nullptr); + m_impl->m_crypto_callback_state.reset(); + } + + // We don't finalize contexts that were provided externally. Those are + // expected to be handled by the library users' applications. + if(m_impl && !m_impl->m_external) { + // If the TCTI context was initialized explicitly, Esys_GetTcti() will + // return a pointer to the TCTI context that then has to be finalized + // explicitly. See ESAPI Specification Section 6.3 "Esys_GetTcti". + TSS2_TCTI_CONTEXT* tcti_ctx = nullptr; + Esys_GetTcti(m_impl->m_ctx, &tcti_ctx); // ignore error in destructor + if(tcti_ctx != nullptr) { + Tss2_TctiLdr_Finalize(&tcti_ctx); + } + Esys_Finalize(&m_impl->m_ctx); - Tss2_TctiLdr_Finalize(&m_impl->m_tcti_ctx); } } diff --git a/src/lib/prov/tpm2/tpm2_context.h b/src/lib/prov/tpm2/tpm2_context.h index 8670652788f..d2b44bbff88 100644 --- a/src/lib/prov/tpm2/tpm2_context.h +++ b/src/lib/prov/tpm2/tpm2_context.h @@ -52,6 +52,19 @@ class BOTAN_PUBLIC_API(3, 6) Context final : public std::enable_shared_from_this static std::shared_ptr create(std::optional tcti = {}, std::optional conf = {}); + /** + * Create a TPM2::Context from an externally sourced TPM2-TSS ESYS + * Context. Note that the input contexts need to remain alive for the + * lifetime of the entire TPM2::Context! This allows to use Botan's TPM2 + * functionality within an exising ESAPI application. + * + * Note that Botan won't finalize an externally provided ESYS context, + * this responsibility remains with the caller in this case. + * + * @param ctx the already set up ESYS_CONTEXT* + */ + static std::shared_ptr create(ESYS_CONTEXT* ctx); + Context(const Context&) = delete; Context(Context&& ctx) noexcept = default; ~Context(); @@ -130,8 +143,7 @@ class BOTAN_PUBLIC_API(3, 6) Context final : public std::enable_shared_from_this const SessionBundle& sessions); private: - Context(const char* tcti_nameconf); - Context(const char* tcti_name, const char* tcti_conf); + Context(ESYS_CONTEXT* ctx, bool external); #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND) friend void enable_crypto_callbacks(const std::shared_ptr&); diff --git a/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.cpp b/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.cpp index d6a15c2c05b..dc2f1b4d2fc 100644 --- a/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.cpp +++ b/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.cpp @@ -864,7 +864,7 @@ namespace Botan::TPM2 { * * TSS2_ESYS_RC_NOT_SUPPORTED: algorithm identifier not mapped to Botan * * TSS2_ESYS_RC_NOT_IMPLEMENTED: algorithm not available (e.g. disabled) */ -void enable_crypto_callbacks(const std::shared_ptr& ctx) { +void set_crypto_callbacks(ESYS_CONTEXT* ctx, CryptoCallbackState* callback_state) { #if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS) BOTAN_ASSERT_NONNULL(ctx); @@ -884,7 +884,7 @@ void enable_crypto_callbacks(const std::shared_ptr& ctx) { .aes_encrypt = &aes_encrypt, .aes_decrypt = &aes_decrypt, .init = &init, - .userdata = &ctx->crypto_callback_state(), + .userdata = callback_state, #if defined(BOTAN_TSS2_SUPPORTS_SM4_IN_CRYPTO_CALLBACKS) .sm4_encrypt = &sm4_encrypt, .sm4_decrypt = &sm4_decrypt, @@ -892,9 +892,9 @@ void enable_crypto_callbacks(const std::shared_ptr& ctx) { }; // clang-format on - check_rc("Esys_SetCryptoCallbacks", Esys_SetCryptoCallbacks(*ctx, &callbacks)); + check_rc("Esys_SetCryptoCallbacks", Esys_SetCryptoCallbacks(ctx, &callbacks)); #else - BOTAN_UNUSED(ctx); + BOTAN_UNUSED(ctx, callback_state); throw Not_Implemented( "This build of botan was compiled with a TSS2 version lower than 4.0.0, " "which does not support custom runtime crypto backends"); diff --git a/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h b/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h index b5161eaf2fd..69228b722e5 100644 --- a/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h +++ b/src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h @@ -11,14 +11,14 @@ #include +struct ESYS_CONTEXT; + namespace Botan { class RandomNumberGenerator; } namespace Botan::TPM2 { -class Context; - /** * This state object is available to all crypto callbacks. * Its lifetime is managed by the TPM2::Context. @@ -33,7 +33,7 @@ struct CryptoCallbackState { * Enable Botan's crypto callbacks in the TPM2-TSS for the given @p context. * @throws Not_Implemented if the TPM2-TSS does not support crypto callbacks. */ -void enable_crypto_callbacks(const std::shared_ptr& context); +void set_crypto_callbacks(ESYS_CONTEXT* context, CryptoCallbackState* callback_state); } // namespace Botan::TPM2 diff --git a/src/tests/test_tpm2.cpp b/src/tests/test_tpm2.cpp index 8584de6bdb9..80b22361fe9 100644 --- a/src/tests/test_tpm2.cpp +++ b/src/tests/test_tpm2.cpp @@ -27,6 +27,10 @@ #include #include #endif + + // for testing externally-provided ESYS context + #include + #include #endif namespace Botan_Tests { @@ -40,6 +44,10 @@ constexpr bool crypto_backend_should_be_available = true; constexpr bool crypto_backend_should_be_available = false; #endif +bool validate_context_environment(std::shared_ptr ctx) { + return (ctx->vendor() == "SW TPM" && ctx->manufacturer() == "IBM"); +} + std::shared_ptr get_tpm2_context(std::string_view rng_tag) { const auto tcti_name = Test::options().tpm2_tcti_name(); if(tcti_name.value() == "disabled") { @@ -48,7 +56,7 @@ std::shared_ptr get_tpm2_context(std::string_view rng_tag) } auto ctx = Botan::TPM2::Context::create(tcti_name, Test::options().tpm2_tcti_conf()); - if(ctx->vendor() != "SW TPM" || ctx->manufacturer() != "IBM") { + if(!validate_context_environment(ctx)) { return {}; } @@ -59,6 +67,38 @@ std::shared_ptr get_tpm2_context(std::string_view rng_tag) return ctx; } +ESYS_CONTEXT* get_external_tpm2_context() { + const auto tcti_name = Test::options().tpm2_tcti_name(); + const auto tcti_conf = Test::options().tpm2_tcti_conf(); + if(tcti_name.value() == "disabled") { + // skip the test if the special 'disabled' TCTI is configured + return {}; + } + + TSS2_RC rc; + TSS2_TCTI_CONTEXT* tcti_ctx; + ESYS_CONTEXT* esys_ctx; + + rc = Tss2_TctiLdr_Initialize_Ex(tcti_name->c_str(), tcti_conf->c_str(), &tcti_ctx); + if(rc != TSS2_RC_SUCCESS) { + throw Test_Error("failed to initialize external TCTI"); + } + + rc = Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */); + if(rc != TSS2_RC_SUCCESS) { + throw Test_Error("failed to initialize external ESYS"); + } + + auto ctx = Botan::TPM2::Context::create(esys_ctx); + if(!validate_context_environment(ctx)) { + return {}; + } + + // This will destruct the TPM2::Context created for environment validation, + // but the 'externally provided' ESYS_CONTEXT will live on! + return esys_ctx; +} + Test::Result bail_out() { Test::Result result("TPM2 test bail out"); @@ -179,6 +219,47 @@ std::vector test_tpm2_context() { }; } +std::vector test_external_tpm2_context() { + ESYS_CONTEXT* esys_ctx = get_external_tpm2_context(); + + return { + CHECK("TPM2::Context-managed crypto backend fails gracefully after TPM2::Context destruction", + [&](Test::Result& result) { + { + auto ctx = Botan::TPM2::Context::create(esys_ctx); + if(!ctx->supports_botan_crypto_backend()) { + result.test_note("skipping, because botan-based crypto backend is not supported"); + return; + } + + ctx->use_botan_crypto_backend(Test::new_rng(__func__)); + } + + TSS2_RC rc; + + TPMT_SYM_DEF sym_spec{ + .algorithm = TPM2_ALG_AES, + .keyBits = {.sym = 128}, + .mode = {.sym = TPM2_ALG_OFB}, + }; + ESYS_TR session; + + rc = Esys_StartAuthSession(esys_ctx, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + nullptr, + TPM2_SE_HMAC, + &sym_spec, + TPM2_ALG_SHA256, + &session); + result.require("expected error", rc == TSS2_ESYS_RC_BAD_REFERENCE); + }), + }; +} + std::vector test_tpm2_sessions() { auto ctx = get_tpm2_context(__func__); if(!ctx) { @@ -1142,6 +1223,7 @@ std::vector test_tpm2_hash() { BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_props", test_tpm2_properties); BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ctx", test_tpm2_context); +BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_external_ctx", test_external_tpm2_context); BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_sessions", test_tpm2_sessions); BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rng", test_tpm2_rng); #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)