• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

randombit / botan / 21712952425

05 Feb 2026 01:16PM UTC coverage: 90.076% (+0.005%) from 90.071%
21712952425

Pull #5287

github

web-flow
Merge 1b320c06e into 8c9623340
Pull Request #5287: Split out BufferSlicer and BufferStuffer to their own headers

102242 of 113507 relevant lines covered (90.08%)

11534589.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

88.02
/src/lib/prov/tpm2/tpm2_context.cpp
1
/*
2
* TPM 2 interface
3
* (C) 2024 Jack Lloyd
4
* (C) 2024 René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity GmbH, financed by LANCOM Systems GmbH
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include <botan/tpm2_context.h>
10

11
#include <botan/tpm2_key.h>
12
#include <botan/tpm2_session.h>
13

14
#include <botan/internal/buffer_stuffer.h>
15
#include <botan/internal/fmt.h>
16
#include <botan/internal/int_utils.h>
17
#include <botan/internal/loadstor.h>
18
#include <botan/internal/stl_util.h>
19
#include <botan/internal/tpm2_algo_mappings.h>
20
#include <botan/internal/tpm2_util.h>
21

22
#include <tss2/tss2_esys.h>
23
#include <tss2/tss2_tcti.h>
24
#include <tss2/tss2_tctildr.h>
25

26
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
27
   #include <botan/tpm2_crypto_backend.h>
28
   #include <botan/internal/tpm2_crypto_backend_impl.h>
29
#endif
30

31
namespace Botan::TPM2 {
32

33
namespace {
34

35
constexpr TPM2_HANDLE storage_root_key_handle = TPM2_HR_PERSISTENT + 1;
36

37
}  // namespace
38

39
struct Context::Impl {
37✔
40
      ESYS_CONTEXT* m_ctx{};  /// m_ctx may be owned by the library user (see m_external)
41
      bool m_external{};
42

43
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
44
      std::unique_ptr<CryptoCallbackState> m_crypto_callback_state;
45
#endif
46
};
47

48
bool Context::supports_botan_crypto_backend() noexcept {
12✔
49
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
50
   return Botan::TPM2::supports_botan_crypto_backend();
12✔
51
#else
52
   return false;
53
#endif
54
}
55

56
std::shared_ptr<Context> Context::create(const std::string& tcti_nameconf) {
×
57
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
×
58
   ESYS_CONTEXT* esys_ctx = nullptr;
×
59
   check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(tcti_nameconf.c_str(), &tcti_ctx));
×
60
   BOTAN_ASSERT_NONNULL(tcti_ctx);
×
61
   check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */));
×
62
   BOTAN_ASSERT_NONNULL(esys_ctx);
×
63

64
   // We cannot std::make_shared as the constructor is private
65
   return std::shared_ptr<Context>(new Context(esys_ctx, false /* context is managed by us */));
×
66
}
67

68
std::shared_ptr<Context> Context::create(std::optional<std::string> tcti, std::optional<std::string> conf) {
9✔
69
   const char* const tcti_ptr = tcti.has_value() ? tcti->c_str() : nullptr;
9✔
70
   const char* const conf_ptr = conf.has_value() ? conf->c_str() : nullptr;
9✔
71

72
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
9✔
73
   ESYS_CONTEXT* esys_ctx = nullptr;
9✔
74
   check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize_Ex(tcti_ptr, conf_ptr, &tcti_ctx));
9✔
75
   BOTAN_ASSERT_NONNULL(tcti_ctx);
9✔
76
   check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */));
9✔
77
   BOTAN_ASSERT_NONNULL(esys_ctx);
9✔
78

79
   // We cannot std::make_shared as the constructor is private
80
   return std::shared_ptr<Context>(new Context(esys_ctx, false /* context is managed by us */));
9✔
81
}
82

83
std::shared_ptr<Context> Context::create(ESYS_CONTEXT* esys_ctx) {
6✔
84
   BOTAN_ARG_CHECK(esys_ctx != nullptr, "provided esys_ctx must not be null");
6✔
85

86
   // We cannot std::make_shared as the constructor is private
87
   return std::shared_ptr<Context>(new Context(esys_ctx, true /* context is managed externally */));
6✔
88
}
89

90
Context::Context(ESYS_CONTEXT* ctx, bool external) : m_impl(std::make_unique<Impl>()) {
15✔
91
   m_impl->m_ctx = ctx;
15✔
92
   m_impl->m_external = external;
15✔
93
   BOTAN_ASSERT_NONNULL(m_impl->m_ctx);
15✔
94
}
15✔
95

96
Context::Context(Context&&) noexcept = default;
×
97
Context& Context::operator=(Context&&) noexcept = default;
×
98

99
void Context::use_botan_crypto_backend(const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
11✔
100
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
101
   BOTAN_STATE_CHECK(!uses_botan_crypto_backend());
11✔
102
   m_impl->m_crypto_callback_state = Botan::TPM2::use_botan_crypto_backend(esys_context(), rng);
10✔
103
#else
104
   BOTAN_UNUSED(rng);
105
   throw Not_Implemented("This build of botan does not provide the TPM2 crypto backend");
106
#endif
107
}
10✔
108

109
bool Context::uses_botan_crypto_backend() const noexcept {
18✔
110
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
111
   return m_impl->m_crypto_callback_state != nullptr;
18✔
112
#else
113
   return false;
114
#endif
115
}
116

117
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
118
CryptoCallbackState& Context::crypto_callback_state() {
×
119
   BOTAN_ASSERT_NONNULL(m_impl->m_crypto_callback_state);
×
120
   return *m_impl->m_crypto_callback_state;
×
121
}
122
#endif
123

124
ESYS_CONTEXT* Context::esys_context() noexcept {
1,411✔
125
   return m_impl->m_ctx;
1,411✔
126
}
127

128
namespace {
129

130
uint32_t get_tpm_property(ESYS_CONTEXT* ctx, TPM2_PT property) {
64✔
131
   // We expect to retrieve a single piece of information, not a list.
132
   constexpr uint32_t property_count = 1;
64✔
133
   constexpr TPM2_CAP capability = TPM2_CAP_TPM_PROPERTIES;
64✔
134

135
   unique_esys_ptr<TPMS_CAPABILITY_DATA> capability_data;
64✔
136
   check_rc("Esys_GetCapability",
64✔
137
            Esys_GetCapability(ctx,
64✔
138
                               ESYS_TR_NONE,
139
                               ESYS_TR_NONE,
140
                               ESYS_TR_NONE,
141
                               capability,
142
                               property,
143
                               property_count,
144
                               nullptr /* more data? - we don't care here */,
145
                               out_ptr(capability_data)));
×
146
   BOTAN_ASSERT_NONNULL(capability_data);
64✔
147
   BOTAN_ASSERT_NOMSG(capability_data->capability == capability);
64✔
148
   BOTAN_ASSERT_NOMSG(capability_data->data.tpmProperties.count == property_count);
64✔
149
   BOTAN_ASSERT_NOMSG(capability_data->data.tpmProperties.tpmProperty[0].property == property);
64✔
150

151
   return capability_data->data.tpmProperties.tpmProperty[0].value;
64✔
152
}
64✔
153

154
template <TPM2_CAP capability, typename ReturnT>
155
[[nodiscard]] std::vector<ReturnT> get_tpm_property_list(ESYS_CONTEXT* ctx, TPM2_PT property, uint32_t count) {
173✔
156
   auto extract = [](const TPMU_CAPABILITIES& caps, uint32_t max_count) {
173✔
157
      std::vector<ReturnT> result;
173✔
158
      if constexpr(capability == TPM2_CAP_HANDLES) {
159
         const auto to_read = std::min(caps.handles.count, max_count);
153✔
160
         result.reserve(to_read);
153✔
161
         for(size_t i = 0; i < to_read; ++i) {
924✔
162
            result.push_back(caps.handles.handle[i]);
771✔
163
         }
164
      } else if constexpr(capability == TPM2_CAP_ALGS) {
165
         const auto to_read = std::min(caps.algorithms.count, max_count);
20✔
166
         result.reserve(to_read);
20✔
167
         for(size_t i = 0; i < to_read; ++i) {
680✔
168
            // TODO: This also contains an algProperties.algProperties bitfield
169
            //       that defines some characteristics of the algorithm.
170
            //       Currently, we don't need that information and ignore it.
171
            result.push_back(caps.algorithms.algProperties[i].alg);
660✔
172
         }
173
      } else {
174
         // TODO: support reading other capability types as needed
175
         static_assert(capability != TPM2_CAP_HANDLES, "Unsupported capability");
176
      }
177
      return result;
173✔
178
   };
×
179

180
   TPMI_YES_NO more_data = TPM2_YES;
173✔
181
   std::vector<ReturnT> properties;
173✔
182
   while(more_data == TPM2_YES && count > 0) {
519✔
183
      unique_esys_ptr<TPMS_CAPABILITY_DATA> capability_data;
173✔
184
      check_rc("Esys_GetCapability",
173✔
185
               Esys_GetCapability(ctx,
173✔
186
                                  ESYS_TR_NONE,
187
                                  ESYS_TR_NONE,
188
                                  ESYS_TR_NONE,
189
                                  capability,
190
                                  property,
191
                                  count,
192
                                  &more_data,
193
                                  out_ptr(capability_data)));
194
      BOTAN_ASSERT_NONNULL(capability_data);
173✔
195
      BOTAN_ASSERT_NOMSG(capability_data->capability == capability);
173✔
196

197
      const auto new_properties = extract(capability_data->data, count);
173✔
198
      BOTAN_ASSERT_NOMSG(new_properties.size() <= count);
173✔
199
      properties.insert(properties.end(), new_properties.begin(), new_properties.end());
173✔
200
      count -= checked_cast_to<uint32_t>(new_properties.size());
173✔
201
   }
202

203
   return properties;
173✔
204
}
×
205

206
}  // namespace
207

208
std::string Context::vendor() const {
11✔
209
   constexpr std::array properties = {
11✔
210
      TPM2_PT_VENDOR_STRING_1, TPM2_PT_VENDOR_STRING_2, TPM2_PT_VENDOR_STRING_3, TPM2_PT_VENDOR_STRING_4};
211
   std::array<uint8_t, properties.size() * 4 + 1 /* ensure zero-termination */> vendor_string{};
11✔
212

213
   BufferStuffer bs(vendor_string);
11✔
214

215
   // The vendor name is transported in several uint32_t fields that are
216
   // loaded as big-endian bytes and concatenated to form the vendor string.
217
   for(auto prop : properties) {
55✔
218
      bs.append(store_be(get_tpm_property(m_impl->m_ctx, prop)));
44✔
219
   }
220

221
   BOTAN_ASSERT_NOMSG(bs.remaining_capacity() == 1);  // the ensured zero-termination
11✔
222
   return std::string(cast_uint8_ptr_to_char(vendor_string.data()));
11✔
223
}
224

225
std::string Context::manufacturer() const {
11✔
226
   std::array<uint8_t, 4 + 1 /* ensure zero termination */> manufacturer_data{};
11✔
227
   store_be(std::span{manufacturer_data}.first<4>(), get_tpm_property(m_impl->m_ctx, TPM2_PT_MANUFACTURER));
11✔
228
   return std::string(cast_uint8_ptr_to_char(manufacturer_data.data()));
11✔
229
}
11✔
230

231
bool Context::supports_algorithm(std::string_view algo_name) const {
24✔
232
   // Go through all the string mappings we have available and check if we
233
   // can find the algorithm name in any of them. If we do, we can check if
234
   // the TPM supports the required algorithms.
235
   const auto required_alg_ids = [&]() -> std::vector<TPM2_ALG_ID> {
72✔
236
      std::vector<TPM2_ALG_ID> result;
24✔
237
      if(auto algo_id = asymmetric_algorithm_botan_to_tss2(algo_name)) {
24✔
238
         result.push_back(algo_id.value());
4✔
239
      }
240

241
      if(auto hash_id = hash_algo_botan_to_tss2(algo_name)) {
24✔
242
         result.push_back(hash_id.value());
6✔
243
      }
244

245
      if(auto block_id = block_cipher_botan_to_tss2(algo_name)) {
24✔
246
         result.push_back(block_id->first);
2✔
247
      }
248

249
      if(auto cipher_mode_id = cipher_mode_botan_to_tss2(algo_name)) {
24✔
250
         result.push_back(cipher_mode_id.value());
1✔
251
      }
252

253
      if(auto cipher_spec = cipher_botan_to_tss2(algo_name)) {
24✔
254
         result.push_back(cipher_spec->algorithm);
1✔
255
         result.push_back(cipher_spec->mode.sym);
1✔
256
      }
257

258
      if(auto sig_padding = rsa_signature_padding_botan_to_tss2(algo_name)) {
24✔
259
         result.push_back(sig_padding.value());
2✔
260
      }
261

262
      if(auto sig = rsa_signature_scheme_botan_to_tss2(algo_name)) {
24✔
263
         result.push_back(sig->scheme);
2✔
264
         result.push_back(sig->details.any.hashAlg);
2✔
265
      }
266

267
      if(auto enc_scheme = rsa_encryption_scheme_botan_to_tss2(algo_name)) {
24✔
268
         result.push_back(enc_scheme->scheme);
3✔
269
         if(enc_scheme->scheme == TPM2_ALG_OAEP) {
3✔
270
            result.push_back(enc_scheme->details.oaep.hashAlg);
1✔
271
         }
272
      }
273

274
      if(auto enc_id = rsa_encryption_padding_botan_to_tss2(algo_name)) {
24✔
275
         result.push_back(enc_id.value());
2✔
276
      }
277

278
      return result;
24✔
279
   }();
24✔
280

281
   if(required_alg_ids.empty()) {
24✔
282
      // The algorithm name is not known to us, so we cannot check for support.
283
      return false;
284
   }
285

286
   const auto algo_caps =
20✔
287
      get_tpm_property_list<TPM2_CAP_ALGS, TPM2_ALG_ID>(m_impl->m_ctx, TPM2_ALG_FIRST, TPM2_MAX_CAP_ALGS);
20✔
288

289
   return std::all_of(
20✔
290
      required_alg_ids.begin(), required_alg_ids.end(), [&](TPM2_ALG_ID id) { return value_exists(algo_caps, id); });
54✔
291
}
24✔
292

293
size_t Context::max_random_bytes_per_request() const {
9✔
294
   return get_tpm_property(m_impl->m_ctx, TPM2_PT_MAX_DIGEST);
9✔
295
}
296

297
std::unique_ptr<TPM2::PrivateKey> Context::storage_root_key(std::span<const uint8_t> auth_value,
8✔
298
                                                            const SessionBundle& sessions) {
299
   return TPM2::PrivateKey::load_persistent(shared_from_this(), storage_root_key_handle, auth_value, sessions);
16✔
300
}
301

302
std::vector<ESYS_TR> Context::transient_handles() const {
×
303
   return get_tpm_property_list<TPM2_CAP_HANDLES, ESYS_TR>(m_impl->m_ctx, TPM2_TRANSIENT_FIRST, TPM2_MAX_CAP_HANDLES);
×
304
}
305

306
std::optional<TPM2_HANDLE> Context::find_free_persistent_handle() const {
2✔
307
   const auto occupied_handles = persistent_handles();
2✔
308

309
   // This is modeled after the implementation in tpm2-tools, which also takes
310
   // "platform persistent" handles into account. We don't do that here, but
311
   // we might need to in the future.
312
   //
313
   // See: https://github.com/tpm2-software/tpm2-tools/blob/bd832d3f79/lib/tpm2_capability.c#L143-L196
314

315
   // all persistent handles are occupied
316
   if(occupied_handles.size() >= TPM2_MAX_CAP_HANDLES) {
2✔
317
      return std::nullopt;
×
318
   }
319

320
   // find the lowest handle that is not occupied
321
   for(TPM2_HANDLE i = TPM2_PERSISTENT_FIRST; i < TPM2_PERSISTENT_LAST; ++i) {
2✔
322
      if(!value_exists(occupied_handles, i)) {
2✔
323
         return i;
2✔
324
      }
325
   }
326

327
   BOTAN_ASSERT_UNREACHABLE();
×
328
}
2✔
329

330
std::vector<TPM2_HANDLE> Context::persistent_handles() const {
153✔
331
   return get_tpm_property_list<TPM2_CAP_HANDLES, TPM2_HANDLE>(
153✔
332
      m_impl->m_ctx, TPM2_PERSISTENT_FIRST, TPM2_MAX_CAP_HANDLES);
153✔
333
}
334

335
TPM2_HANDLE Context::persist(TPM2::PrivateKey& key,
6✔
336
                             const SessionBundle& sessions,
337
                             std::span<const uint8_t> auth_value,
338
                             std::optional<TPM2_HANDLE> persistent_handle) {
339
   auto& handles = key.handles();
6✔
340

341
   BOTAN_ARG_CHECK(!persistent_handle || !value_exists(persistent_handles(), persistent_handle.value()),
10✔
342
                   "Persistent handle already in use");
343
   BOTAN_ARG_CHECK(!handles.has_persistent_handle(), "Key already has a persistent handle assigned");
4✔
344

345
   // 1. Decide on the location to persist the key to.
346
   //    This uses either the handle provided by the caller or a free handle.
347
   const std::optional<TPMI_DH_PERSISTENT> new_persistent_handle =
2✔
348
      persistent_handle.has_value() ? persistent_handle : find_free_persistent_handle();
2✔
349

350
   BOTAN_STATE_CHECK(new_persistent_handle.has_value());
2✔
351

352
   // 2. Persist the transient key in the TPM's NV storage
353
   //    This will flush the transient key handle and replace it with a new
354
   //    transient handle that references the persisted key.
355
   check_rc("Esys_EvictControl",
8✔
356
            Esys_EvictControl(m_impl->m_ctx,
2✔
357
                              ESYS_TR_RH_OWNER /*TODO: hierarchy*/,
358
                              handles.transient_handle(),
359
                              sessions[0],
360
                              sessions[1],
361
                              sessions[2],
362
                              *new_persistent_handle,
2✔
363
                              out_transient_handle(handles)));
4✔
364
   BOTAN_ASSERT_NOMSG(handles.has_transient_handle());
2✔
365

366
   // 3. Reset the auth value of the key object
367
   //    This is necessary to ensure that the key object remains usable after
368
   //    the transient handle was recreated inside Esys_EvictControl().
369
   if(!auth_value.empty()) {
2✔
370
      const auto user_auth = copy_into<TPM2B_AUTH>(auth_value);
2✔
371
      check_rc("Esys_TR_SetAuth", Esys_TR_SetAuth(m_impl->m_ctx, handles.transient_handle(), &user_auth));
2✔
372
   }
373

374
   // 4. Update the key object with the new persistent handle
375
   //    This double-checks that the key was persisted at the correct location,
376
   //    but also brings the key object into a consistent state.
377
   check_rc("Esys_TR_GetTpmHandle",
2✔
378
            Esys_TR_GetTpmHandle(m_impl->m_ctx, handles.transient_handle(), out_persistent_handle(handles)));
4✔
379

380
   BOTAN_ASSERT_NOMSG(handles.has_persistent_handle());
2✔
381
   BOTAN_ASSERT_EQUAL(*new_persistent_handle, handles.persistent_handle(), "key was persisted at the correct location");
2✔
382

383
   return *new_persistent_handle;
2✔
384
}
385

386
void Context::evict(std::unique_ptr<TPM2::PrivateKey> key, const SessionBundle& sessions) {
2✔
387
   BOTAN_ASSERT_NONNULL(key);
2✔
388

389
   auto& handles = key->handles();
2✔
390
   BOTAN_ARG_CHECK(handles.has_persistent_handle(), "Key does not have a persistent handle assigned");
2✔
391

392
   // 1. Evict the key from the TPM's NV storage
393
   //    This will free the persistent handle, but the transient handle will
394
   //    still be valid.
395
   ESYS_TR no_new_handle = ESYS_TR_NONE;
2✔
396
   check_rc("Esys_EvictControl",
8✔
397
            Esys_EvictControl(m_impl->m_ctx,
2✔
398
                              ESYS_TR_RH_OWNER /*TODO: hierarchy*/,
399
                              handles.transient_handle(),
400
                              sessions[0],
401
                              sessions[1],
402
                              sessions[2],
403
                              0,
404
                              &no_new_handle));
405
   BOTAN_ASSERT(no_new_handle == ESYS_TR_NONE, "When deleting a key, no new handle is returned");
2✔
406

407
   // 2. The persistent key was deleted and the transient key was flushed by
408
   //    Esys_EvictControl().
409
   handles._disengage();
2✔
410
}
2✔
411

412
Context::~Context() {
28✔
413
   if(!m_impl) {
14✔
414
      return;
×
415
   }
416

417
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
418
   // If this object manages a crypto backend state object and the ESYS context
419
   // will live on, because it was externally provided, we have to de-register
420
   // this state object from the crypto callbacks.
421
   //
422
   // This will prevent the crypto backend callbacks from using a dangling
423
   // pointer and cause graceful errors if the externally provided ESYS context
424
   // is used for any action that would still need the crypto backend state.
425
   //
426
   // We deliberately do not just disable the crypto backend silently, as that
427
   // might give users the false impression that they continue to benefit from
428
   // the crypto backend while in fact they're back to the TSS' default.
429
   if(m_impl->m_external && uses_botan_crypto_backend()) {
14✔
430
      try {
1✔
431
         set_crypto_callbacks(esys_context(), nullptr /* reset callback state */);
1✔
432
      } catch(...) {
×
433
         // ignore errors in destructor
434
      }
×
435
      m_impl->m_crypto_callback_state.reset();
1✔
436
   }
437
#endif
438

439
   // We don't finalize contexts that were provided externally. Those are
440
   // expected to be handled by the library users' applications.
441
   if(!m_impl->m_external) {
14✔
442
      // If the TCTI context was initialized explicitly, Esys_GetTcti() will
443
      // return a pointer to the TCTI context that then has to be finalized
444
      // explicitly. See ESAPI Specification Section 6.3 "Esys_GetTcti".
445
      TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;   // NOLINT(*-const-correctness) bug in clang-tidy
8✔
446
      Esys_GetTcti(m_impl->m_ctx, &tcti_ctx);  // ignore error in destructor
8✔
447
      if(tcti_ctx != nullptr) {
8✔
448
         Tss2_TctiLdr_Finalize(&tcti_ctx);
8✔
449
      }
450

451
      Esys_Finalize(&m_impl->m_ctx);
8✔
452
   }
453
}
42✔
454

455
}  // namespace Botan::TPM2
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc