Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature: Support external ESYS_CONTEXT in TPM2 #4430

Merged
merged 6 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions doc/api_ref/ffi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1367,6 +1367,12 @@ TPM 2.0 Functions

An opaque data type for a TPM 2.0 session object. Don't mess with it.

.. cpp:type:: opaque* botan_tpm2_crypto_backend_state_t

An opaque data type to hold the TPM 2.0 crypto backend state when registering
the botan-based crypto backend on a bare ESYS_CONTEXT. When the TPM 2.0
context is managed via Botan botan_tpm2_ctx_t, this state object is maintained
internally.

.. cpp:function:: int botan_tpm2_supports_crypto_backend()

Expand All @@ -1382,10 +1388,30 @@ TPM 2.0 Functions
Initialize a TPM 2.0 context object. The TCTI name and configuration are
passed as separate strings.

.. cpp:function:: int botan_tpm2_ctx_from_esys(botan_tpm2_ctx_t* ctx_out, ESYS_CONTEXT* esys_ctx)

Initialize a TPM 2.0 context object from a pre-existing ``ESYS_CONTEXT`` that
is managed by the application. Destroying this object *will not* finalize the
``ESYS_CONTEXT``, this responsibility remains with the application.

.. 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.
This should be used when the ``ESYS_CONTEXT`` is managed by the TPM 2.0
wrapper provided by Botan (i.e. the application did not explicitly instantiate
the ``ESYS_CONTEXT`` itself).

.. cpp:function:: int botan_tpm2_enable_crypto_backend(botan_tpm2_crypto_backend_state_t* cbs_out, \
ESYS_CONTEXT* esys_ctx, \
botan_rng_t rng)

Enable the Botan-based TPM 2.0 crypto backend on a pre-existing ``ESYS_CONTEXT``
that is managed by the application. Note that the random number generator
passed to this function must not be dependent on the TPM itself.
The crypto backend has to keep internal state. The application is responsible
to keep this state alive and destroy it after the ``ESYS_CONTEXT`` is no longer
used.

.. cpp:function:: int botan_tpm2_unauthenticated_session_init(botan_tpm2_session_t* session_out, botan_tpm2_ctx_t ctx)

Expand All @@ -1408,6 +1434,14 @@ TPM 2.0 Functions

Destroy a TPM 2.0 session object.

.. cpp:function:: int botan_tpm2_crypto_backend_state_destroy(botan_tpm2_crypto_backend_state_t cbs)

Destroy a TPM 2.0 crypto backend state. This is required when registering the
botan-based crypto backend on an ESYS_CONTEXT managed by the application
using botan_tpm2_enable_crypto_backend. When the ESYS_CONTEXT is managed in
the botan wrapper, and botan_tpm2_ctx_enable_crypto_backend was used, this
state is managed within the library and does not need to be cleaned up.

X.509 Certificates
----------------------------------------

Expand Down
16 changes: 16 additions & 0 deletions doc/api_ref/tpm.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ persisting and evicting keys into the TPM's NVRAM.
TCTI. Both values may by empty, in which case the TPM-TSS2 will try to
determine them from default values.

.. cpp:function:: std::shared_ptr<Context> create(ESYS_CONTEXT* ctx)

Create a TPM2 context from an already set up TPM2-TSS ESYS_CONTEXT*
to enable usage of Botan's TPM2 functionalities via an outside
ESYS Context.
If the Botan TPM2 Context was created this way, the destructor will
not finalize the underlying ESYS_CONTEXT.

.. cpp:function:: 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
Expand Down Expand Up @@ -250,6 +258,14 @@ 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.

Alternatively, if one just wants to utilize the backend in a TPM2-TSS ESAPI
application without using Botan's wrappers, free-standing functions are provided
in ``tpm2_crypto_backend.h``. The ``use_botan_crypto_backend`` works similar to
the ``Context::use_botan_crypto_backend`` method but is given an ``ESYS_CONTEXT*``
and returns a ``TPM2::CryptoCallbackState`` that needs to stay alive as long
as the crypto backend is used. This will only succeed if the method
``supports_botan_crypto_backend`` returns true.

TPM 2.0 Example
~~~~~~~~~~~~~~~

Expand Down
44 changes: 44 additions & 0 deletions src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -2265,6 +2265,13 @@ typedef struct botan_tpm2_ctx_struct* botan_tpm2_ctx_t;
*/
typedef struct botan_tpm2_session_struct* botan_tpm2_session_t;

/**
* TPM2 crypto backend state object
*/
typedef struct botan_tpm2_crypto_backend_state_struct* botan_tpm2_crypto_backend_state_t;

struct ESYS_CONTEXT;

/**
* Checks if Botan's TSS2 crypto backend can be used in this build
* @returns 1 if the crypto backend can be enabled
Expand All @@ -2290,6 +2297,17 @@ BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_ctx_init(botan_tpm2_ctx_t* ctx_out, const
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);

/**
* Wrap an existing ESYS_CONTEXT for use in Botan.
* Note that destroying the created botan_tpm2_ctx_t won't
* finalize @p esys_ctx
* @param ctx_out output TPM2 context
* @param esys_ctx ESYS_CONTEXT to wrap
* @return 0 on success
*/
BOTAN_FFI_EXPORT(3, 7)
int botan_tpm2_ctx_from_esys(botan_tpm2_ctx_t* ctx_out, ESYS_CONTEXT* esys_ctx);

/**
* Enable Botan's TSS2 crypto backend that replaces the cryptographic functions
* required for the communication with the TPM with implementations provided
Expand All @@ -2309,6 +2327,32 @@ int botan_tpm2_ctx_enable_crypto_backend(botan_tpm2_ctx_t ctx, botan_rng_t rng);
*/
BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_ctx_destroy(botan_tpm2_ctx_t ctx);

/**
* Use this if you just need Botan's crypto backend but do not want to wrap any
* other ESYS functionality using Botan's TPM2 wrapper.
* A Crypto Backend State is created that the user needs to keep alive for as
* long as the crypto backend is used and needs to be destroyed after.
* 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 esys_ctx.
* @param cbs_out To be created Crypto Backend State
* @param esys_ctx TPM2 context
* @param rng random number generator to be used by the crypto backend
*/
BOTAN_FFI_EXPORT(3, 7)
int botan_tpm2_enable_crypto_backend(botan_tpm2_crypto_backend_state_t* cbs_out,
ESYS_CONTEXT* esys_ctx,
botan_rng_t rng);

/**
* Frees all resouces of a TPM2 Crypto Callback State
* Note that this does not attempt to de-register the crypto backend,
* it just frees the resource pointed to by @p cbs. Use the ESAPI function
* ``Esys_SetCryptoCallbacks(ctx, nullptr)`` to deregister manually.
* @param cbs TPM2 Crypto Callback State
* @return 0 on success
*/
BOTAN_FFI_EXPORT(3, 7) int botan_tpm2_crypto_backend_state_destroy(botan_tpm2_crypto_backend_state_t cbs);

/**
* Initialize a random number generator object via TPM2
* @param rng_out rng object to create
Expand Down
58 changes: 58 additions & 0 deletions src/lib/ffi/ffi_tpm2.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@
#include <botan/tpm2_key.h>
#include <botan/tpm2_rng.h>
#include <botan/tpm2_session.h>

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
#include <botan/tpm2_crypto_backend.h>
#endif
#endif

extern "C" {
Expand All @@ -39,6 +43,10 @@ struct botan_tpm2_session_wrapper {
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);

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
BOTAN_FFI_DECLARE_STRUCT(botan_tpm2_crypto_backend_state_struct, Botan::TPM2::CryptoCallbackState, 0x1AC84DE5);
atreiber94 marked this conversation as resolved.
Show resolved Hide resolved
#endif

} // extern "C"

namespace {
Expand Down Expand Up @@ -123,6 +131,24 @@ int botan_tpm2_ctx_init_ex(botan_tpm2_ctx_t* ctx_out, const char* tcti_name, con
#endif
}

int botan_tpm2_ctx_from_esys(botan_tpm2_ctx_t* ctx_out, ESYS_CONTEXT* esys_ctx) {
#if defined(BOTAN_HAS_TPM2)
return ffi_guard_thunk(__func__, [=]() -> int {
if(ctx_out == nullptr || esys_ctx == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

auto ctx = std::make_unique<botan_tpm2_ctx_wrapper>();
ctx->ctx = Botan::TPM2::Context::create(esys_ctx);
*ctx_out = new botan_tpm2_ctx_struct(std::move(ctx));
return BOTAN_FFI_SUCCESS;
});
#else
BOTAN_UNUSED(ctx_out, esys_ctx);
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 {
Expand Down Expand Up @@ -155,6 +181,38 @@ int botan_tpm2_ctx_destroy(botan_tpm2_ctx_t ctx) {
#endif
}

int botan_tpm2_enable_crypto_backend(botan_tpm2_crypto_backend_state_t* cbs_out,
ESYS_CONTEXT* esys_ctx,
botan_rng_t rng) {
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
return ffi_guard_thunk(__func__, [=]() -> int {
if(cbs_out == nullptr || esys_ctx == nullptr) {
return BOTAN_FFI_ERROR_NULL_POINTER;
}

Botan::RandomNumberGenerator& rng_ref = safe_get(rng);

// 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<Botan::RandomNumberGenerator> rng_ptr(&rng_ref, [](auto*) {});
*cbs_out = new botan_tpm2_crypto_backend_state_struct(Botan::TPM2::use_botan_crypto_backend(esys_ctx, rng_ptr));
return BOTAN_FFI_SUCCESS;
});
#else
BOTAN_UNUSED(cbs_out, esys_ctx, rng);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}

int botan_tpm2_crypto_backend_state_destroy(botan_tpm2_crypto_backend_state_t cbs) {
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
return BOTAN_FFI_CHECKED_DELETE(cbs);
#else
BOTAN_UNUSED(cbs);
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,
Expand Down
91 changes: 71 additions & 20 deletions src/lib/prov/tpm2/tpm2_context.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,8 @@
#include <tss2/tss2_tctildr.h>

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
#include <botan/internal/tpm2_crypto_backend.h>
#include <botan/tpm2_crypto_backend.h>
#include <botan/internal/tpm2_crypto_backend_impl.h>
#endif

namespace Botan::TPM2 {
Expand All @@ -34,54 +35,68 @@ 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 may be owned by the library user (see m_external)
bool m_external;

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
std::unique_ptr<CryptoCallbackState> m_crypto_callback_state;
#endif
};

bool Context::supports_botan_crypto_backend() noexcept {
#if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS) and defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
return true;
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
return Botan::TPM2::supports_botan_crypto_backend();
#else
return false;
#endif
}

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_ARG_CHECK(esys_ctx != nullptr, "provided esys_ctx must not be null");

// 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());
m_impl->m_crypto_callback_state = Botan::TPM2::use_botan_crypto_backend(esys_context(), rng);
#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,45 @@ void Context::evict(std::unique_ptr<TPM2::PrivateKey> key, const SessionBundle&
}

Context::~Context() {
if(m_impl) {
if(!m_impl) {
return;
}

#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
// If this object manages a crypto backend state object and the ESYS context
// will live on, because it was externally provided, we have to de-register
// this state object from the crypto callbacks.
//
// This will prevent the crypto backend callbacks from using a dangling
// pointer and cause graceful errors if the externally provided ESYS context
// is used for any action that would still need the crypto backend state.
//
// We deliberately do not just disable the crypto backend silently, as that
// might give users the false impression that they continue to benefit from
// the crypto backend while in fact they're back to the TSS' default.
if(m_impl->m_external && uses_botan_crypto_backend()) {
try {
set_crypto_callbacks(esys_context(), nullptr /* reset callback state */);
} catch(...) {
// ignore errors in destructor
}
m_impl->m_crypto_callback_state.reset();
}
#endif

// We don't finalize contexts that were provided externally. Those are
// expected to be handled by the library users' applications.
if(!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
Loading
Loading