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

Add TPM2 RNG #4117

Open
wants to merge 14 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 13 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
2 changes: 1 addition & 1 deletion configure.py
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,7 @@ def add_enable_disable_pair(group, what, default, msg=optparse.SUPPRESS_HELP):
'disable building of deprecated features and modules')

# Should be derived from info.txt but this runs too early
third_party = ['boost', 'bzip2', 'lzma', 'commoncrypto', 'sqlite3', 'zlib', 'tpm']
third_party = ['boost', 'bzip2', 'lzma', 'commoncrypto', 'sqlite3', 'zlib', 'tpm', 'tpm2']

for mod in third_party:
mods_group.add_option('--with-%s' % (mod),
Expand Down
4 changes: 4 additions & 0 deletions doc/api_ref/ffi.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.. cpp:enumerator:: BOTAN_FFI_TPM_ERROR = -78
.. cpp:enumerator:: BOTAN_FFI_TPM2_ERROR = -78

... we've been specific basically everywhere. I think we should always state TPM2.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #4117 (comment) - this FFI error translates the ErrorType::TPMError, which is also the error type for TPM2_Error. Or should we just introduce a new TPM2 ErrorType and keep them separated entirely?


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.
Expand Down
4 changes: 3 additions & 1 deletion src/lib/ffi/ffi.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down
28 changes: 28 additions & 0 deletions src/lib/ffi/ffi.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,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,
};
Expand Down Expand Up @@ -259,6 +260,33 @@ BOTAN_FFI_EXPORT(2, 3) int botan_base64_decode(const char* base64_str, size_t in
*/
typedef struct botan_rng_struct* botan_rng_t;

/**
* TPM2 context
*/
typedef struct botan_tpm2_ctx_struct* botan_tpm2_ctx_t;

/**
* Initialize a TPM2 context
* @param ctx_out output TPM2 context
* @param tcti_nameconf TCTI config (may be nullpointer)
* @return 0 on success
*/
BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_ctx_init(botan_tpm2_ctx_t* ctx_out, const char* tcti_nameconf);

/**
* 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
*/
BOTAN_FFI_EXPORT(3, 6) int botan_tpm2_rng_init(botan_rng_t* rng_out, botan_tpm2_ctx_t ctx);

/**
* Initialize a random number generator object
* @param rng rng object
Expand Down
89 changes: 89 additions & 0 deletions src/lib/ffi/ffi_tpm2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* (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 <botan/ffi.h>

#include <botan/internal/ffi_rng.h>
#include <botan/internal/ffi_util.h>

#if defined(BOTAN_HAS_TPM2)
#include <botan/tpm2.h>
#include <botan/tpm2_rng.h>
#endif

extern "C" {

using namespace Botan_FFI;

#if defined(BOTAN_HAS_TPM2)
/**
* This wrapper is required since BOTAN_FFI_DECLARE_STRUCT internally produces a unique pointer,
* but the TPM2_Context is meant to be used as a shared pointer.
*/
struct botan_tpm2_ctx_wrapper {
std::shared_ptr<Botan::TPM2_Context> ctx;
};

BOTAN_FFI_DECLARE_STRUCT(botan_tpm2_ctx_struct, botan_tpm2_ctx_wrapper, 0xD2B95E15);
#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<botan_tpm2_ctx_wrapper>();

auto tcti = [=]() -> std::optional<std::string> {
if(tcti_nameconf == nullptr) {
return {};
} else {
return std::string(tcti_nameconf);
}
}();

ctx->ctx = Botan::TPM2_Context::create(std::move(tcti));
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If an exception is throwing here due to for instance no hardware being available at runtime, the caller will get BOTAN_FFI_ERROR_EXCEPTION_THROWN which is not particularly helpful. Maybe catch this and then translate an error to something more specific?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe even just reusing BOTAN_FFI_ERROR_NOT_IMPLEMENTED if the hardware is unavailable? IDK

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good point. I don't think reusing BOTAN_FFI_ERROR_NOT_IMPLEMENTED is all that helpful since the function would be implemented and TPM2 usage desired in that case (as the build is configured with --with-tpm2).

The exception that would be thrown here is TPM2_Error with error type ErrorType::TPMError. I added a corresponding FFI error type such that the caller will now get that error translated within ffi_guard_thunk instead of BOTAN_FFI_ERROR_EXCEPTION_THROWN. That way, one can distinguish between a runtime TPM error when configured with TPM and a not implemented error when TPM is not configured.

*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
}

/**
* 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) {
#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<Botan::TPM2_RNG>(ctx_wrapper.ctx));
return BOTAN_FFI_SUCCESS;
});
#else
BOTAN_UNUSED(rng_out, ctx);
return BOTAN_FFI_ERROR_NOT_IMPLEMENTED;
#endif
}
}
27 changes: 27 additions & 0 deletions src/lib/prov/tpm2/info.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<defines>
TPM2 -> 20240610
</defines>

<module_info>
name -> "TPM2"
brief -> "Wrappers and Utilites to interact with TPM2"
</module_info>

load_on vendor

<libs>
all -> tss2-esys,tss2-rc,tss2-tctildr
</libs>

<requires>
rng
</requires>

<header:internal>
tpm2_util.h
</header:internal>

<header:public>
tpm2.h
tpm2_rng.h
</header:public>
58 changes: 58 additions & 0 deletions src/lib/prov/tpm2/tpm2.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* TPM 2 interface
* (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 <botan/tpm2.h>

#include <botan/internal/fmt.h>
#include <botan/internal/tpm2_util.h>

#include <tss2/tss2_esys.h>
#include <tss2/tss2_tcti.h>
#include <tss2/tss2_tctildr.h>

namespace Botan {

TPM2_Error::TPM2_Error(std::string_view location, uint32_t rc) :
Exception(fmt("TPM2 Exception in {}: Code {} ({})", location, rc, Tss2_RC_Decode(rc))), m_rc(rc) {}

std::string TPM2_Error::error_message() const {
return Tss2_RC_Decode(m_rc);
}

struct TPM2_Context::Impl {
TSS2_TCTI_CONTEXT* m_tcti_ctx;
ESYS_CONTEXT* m_ctx;
};

std::shared_ptr<TPM2_Context> TPM2_Context::create(std::optional<std::string> tcti_nameconf) {
const auto tcti_nameconf_ptr = [&]() -> const char* {
if(tcti_nameconf.has_value()) {
return tcti_nameconf->c_str();
} else {
return nullptr;
}
}();
// We cannot std::make_shared as the constructor is private
return std::shared_ptr<TPM2_Context>(new TPM2_Context(tcti_nameconf_ptr));
atreiber94 marked this conversation as resolved.
Show resolved Hide resolved
}

TPM2_Context::TPM2_Context(const char* tcti_nameconf) : m_impl(std::make_unique<Impl>()) {
check_tss2_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(tcti_nameconf, &m_impl->m_tcti_ctx));
check_tss2_rc("TPM2 Initialization", Esys_Initialize(&m_impl->m_ctx, m_impl->m_tcti_ctx, nullptr /* ABI version */));
}

void* TPM2_Context::get() {
return m_impl->m_ctx;
}

TPM2_Context::~TPM2_Context() {
Esys_Finalize(&m_impl->m_ctx);
Tss2_TctiLdr_Finalize(&m_impl->m_tcti_ctx);
}

} // namespace Botan
65 changes: 65 additions & 0 deletions src/lib/prov/tpm2/tpm2.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* TPM 2 interface
* (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)
*/

#ifndef BOTAN_TPM2_H_
#define BOTAN_TPM2_H_

#include <botan/exceptn.h>

#include <memory>
#include <optional>
atreiber94 marked this conversation as resolved.
Show resolved Hide resolved

namespace Botan {
class BOTAN_PUBLIC_API(3, 6) TPM2_Error final : public Exception {
public:
TPM2_Error(std::string_view location, uint32_t rc);

ErrorType error_type() const noexcept override { return ErrorType::TPMError; }

uint32_t code() const { return m_rc; }

int error_code() const noexcept override {
// RC is uint32 but the maximum value is within int32 range as per tss2_common.h
return static_cast<int>(m_rc);
}

std::string error_message() const;

private:
uint32_t m_rc;
};

class BOTAN_PUBLIC_API(3, 6) TPM2_Context final {
public:
/**
* @param tcti_nameconf if set this is passed to Tss2_TctiLdr_Initialize verbatim
* otherwise a nullptr is passed.
*/
static std::shared_ptr<TPM2_Context> create(std::optional<std::string> tcti_nameconf = {});

TPM2_Context(const TPM2_Context&) = delete;
TPM2_Context(TPM2_Context&& ctx) noexcept = default;
~TPM2_Context();

TPM2_Context& operator=(const TPM2_Context&) = delete;
TPM2_Context& operator=(TPM2_Context&& ctx) noexcept = default;

/// @return an ESYS_CONTEXT* for use in other TPM2 functions.
void* get();
atreiber94 marked this conversation as resolved.
Show resolved Hide resolved

private:
TPM2_Context(const char* tcti_nameconf);

private:
struct Impl; // PImpl to avoid TPM2-TSS includes in this header
std::unique_ptr<Impl> m_impl;
};

} // namespace Botan

#endif
54 changes: 54 additions & 0 deletions src/lib/prov/tpm2/tpm2_rng.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
/*
* TPM 2 RNG interface
* (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 <botan/tpm2_rng.h>

#include <botan/internal/stl_util.h>
#include <botan/internal/tpm2_util.h>

#include <source_location>
#include <tss2/tss2_esys.h>

namespace Botan {

void TPM2_RNG::fill_bytes_with_input(std::span<uint8_t> output, std::span<const uint8_t> input) {
constexpr size_t MAX_STIR_RANDOM_SIZE = 128; // From specification of tpm2-tool's tpm2_stirrandom

BufferSlicer in(input);

while(!in.empty()) {
TPM2B_SENSITIVE_DATA data;
data.size = std::min(in.remaining(), MAX_STIR_RANDOM_SIZE);
in.copy_into({data.buffer, data.size});

check_tss2_rc(
"StirRandom",
Esys_StirRandom(static_cast<ESYS_CONTEXT*>(m_ctx->get()), ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, &data));
atreiber94 marked this conversation as resolved.
Show resolved Hide resolved
}

BufferStuffer out(output);
while(!out.full()) {
TPM2B_DIGEST* digest = nullptr;
const auto requested_bytes = std::min(sizeof(digest->buffer), out.remaining_capacity());
check_tss2_rc("GetRandom",
Esys_GetRandom(static_cast<ESYS_CONTEXT*>(m_ctx->get()),
ESYS_TR_NONE,
ESYS_TR_NONE,
ESYS_TR_NONE,
requested_bytes,
&digest));

// Ensure Esys_Free(digest) is called even if assertions fail and we leave this block
auto clean_buffer = scoped_cleanup([&digest] { Esys_Free(digest); });

BOTAN_ASSERT_NOMSG(digest->size == requested_bytes);
out.append({digest->buffer, digest->size});
}
}

} // namespace Botan
Loading
Loading