Skip to content

Commit

Permalink
WIP: External Ctx
Browse files Browse the repository at this point in the history
  • Loading branch information
atreiber94 committed Nov 8, 2024
1 parent d35b793 commit 779adac
Show file tree
Hide file tree
Showing 5 changed files with 150 additions and 26 deletions.
62 changes: 46 additions & 16 deletions src/lib/prov/tpm2/tpm2_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<CryptoCallbackState> m_crypto_callback_state;
Expand All @@ -51,37 +51,52 @@ bool Context::supports_botan_crypto_backend() noexcept {
}

std::shared_ptr<Context> 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<Context>(new Context(tcti_nameconf.c_str()));
return std::shared_ptr<Context>(new Context(esys_ctx, false /* context is managed by us */));
}

std::shared_ptr<Context> Context::create(std::optional<std::string> tcti, std::optional<std::string> 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<Context>(new Context(tcti_ptr, conf_ptr));
return std::shared_ptr<Context>(new Context(esys_ctx, false /* context is managed by us */));
}

Context::Context(const char* tcti_nameconf) : m_impl(std::make_unique<Impl>()) {
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> 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<Context>(new Context(esys_ctx, true /* context is managed externally */));
}

Context::Context(const char* tcti_name, const char* tcti_conf) : m_impl(std::make_unique<Impl>()) {
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<Impl>()) {
m_impl->m_ctx = ctx;
m_impl->m_external = external;
BOTAN_ASSERT_NONNULL(m_impl->m_ctx);
}

void Context::use_botan_crypto_backend(const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
BOTAN_STATE_CHECK(!uses_botan_crypto_backend());
m_impl->m_crypto_callback_state = std::make_unique<CryptoCallbackState>(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");
Expand Down Expand Up @@ -397,9 +412,24 @@ void Context::evict(std::unique_ptr<TPM2::PrivateKey> 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);
}
}

Expand Down
16 changes: 14 additions & 2 deletions src/lib/prov/tpm2/tpm2_context.h
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,19 @@ class BOTAN_PUBLIC_API(3, 6) Context final : public std::enable_shared_from_this
static std::shared_ptr<Context> create(std::optional<std::string> tcti = {},
std::optional<std::string> 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<Context> create(ESYS_CONTEXT* ctx);

Context(const Context&) = delete;
Context(Context&& ctx) noexcept = default;
~Context();
Expand Down Expand Up @@ -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<Context>&);
Expand Down
8 changes: 4 additions & 4 deletions src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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<Context>& ctx) {
void set_crypto_callbacks(ESYS_CONTEXT* ctx, CryptoCallbackState* callback_state) {
#if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
BOTAN_ASSERT_NONNULL(ctx);

Expand All @@ -884,17 +884,17 @@ void enable_crypto_callbacks(const std::shared_ptr<Context>& 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,
#endif
};
// 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");
Expand Down
6 changes: 3 additions & 3 deletions src/lib/prov/tpm2/tpm2_crypto_backend/tpm2_crypto_backend.h
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,14 @@

#include <memory>

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.
Expand All @@ -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>& context);
void set_crypto_callbacks(ESYS_CONTEXT* context, CryptoCallbackState* callback_state);

} // namespace Botan::TPM2

Expand Down
84 changes: 83 additions & 1 deletion src/tests/test_tpm2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@
#include <botan/ecdsa.h>
#include <botan/tpm2_ecc.h>
#endif

// for testing externally-provided ESYS context
#include <tss2/tss2_esys.h>
#include <tss2/tss2_tctildr.h>
#endif

namespace Botan_Tests {
Expand All @@ -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<Botan::TPM2::Context> ctx) {
return (ctx->vendor() == "SW TPM" && ctx->manufacturer() == "IBM");
}

std::shared_ptr<Botan::TPM2::Context> get_tpm2_context(std::string_view rng_tag) {
const auto tcti_name = Test::options().tpm2_tcti_name();
if(tcti_name.value() == "disabled") {
Expand All @@ -48,7 +56,7 @@ std::shared_ptr<Botan::TPM2::Context> 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 {};
}

Expand All @@ -59,6 +67,38 @@ std::shared_ptr<Botan::TPM2::Context> 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");

Expand Down Expand Up @@ -179,6 +219,47 @@ std::vector<Test::Result> test_tpm2_context() {
};
}

std::vector<Test::Result> 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::Result> test_tpm2_sessions() {
auto ctx = get_tpm2_context(__func__);
if(!ctx) {
Expand Down Expand Up @@ -1142,6 +1223,7 @@ std::vector<Test::Result> 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)
Expand Down

0 comments on commit 779adac

Please sign in to comment.