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

randombit / botan / 11779157375

11 Nov 2024 01:06PM UTC coverage: 91.065% (-0.008%) from 91.073%
11779157375

push

github

web-flow
Merge pull request #4430 from Rohde-Schwarz/feature/tpm2_external_ctx

Feature: Support external ESYS_CONTEXT in TPM2

90557 of 99442 relevant lines covered (91.07%)

9347784.07 hits per line

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

88.14
/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/fmt.h>
15
#include <botan/internal/loadstor.h>
16
#include <botan/internal/stl_util.h>
17
#include <botan/internal/tpm2_algo_mappings.h>
18
#include <botan/internal/tpm2_util.h>
19

20
#include <tss2/tss2_esys.h>
21
#include <tss2/tss2_tcti.h>
22
#include <tss2/tss2_tctildr.h>
23

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

29
namespace Botan::TPM2 {
30

31
namespace {
32

33
constexpr TPM2_HANDLE storage_root_key_handle = TPM2_HR_PERSISTENT + 1;
34

35
}  // namespace
36

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

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

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

54
std::shared_ptr<Context> Context::create(const std::string& tcti_nameconf) {
×
55
   const auto nameconf_ptr = tcti_nameconf.c_str();
×
56

57
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
×
58
   ESYS_CONTEXT* esys_ctx = nullptr;
×
59
   check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(nameconf_ptr, &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 auto tcti_ptr = tcti.has_value() ? tcti->c_str() : nullptr;
9✔
70
   const auto 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
void Context::use_botan_crypto_backend(const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
11✔
97
#if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
98
   BOTAN_STATE_CHECK(!uses_botan_crypto_backend());
11✔
99
   m_impl->m_crypto_callback_state = Botan::TPM2::use_botan_crypto_backend(esys_context(), rng);
10✔
100
#else
101
   BOTAN_UNUSED(rng);
102
   throw Not_Implemented("This build of botan does not provide the TPM2 crypto backend");
103
#endif
104
}
10✔
105

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

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

121
ESYS_CONTEXT* Context::esys_context() noexcept {
1,411✔
122
   return m_impl->m_ctx;
1,411✔
123
}
124

125
namespace {
126

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

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

148
   return capability_data->data.tpmProperties.tpmProperty[0].value;
64✔
149
}
64✔
150

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

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

194
      const auto new_properties = extract(capability_data->data, count);
173✔
195
      BOTAN_ASSERT_NOMSG(new_properties.size() <= count);
173✔
196
      properties.insert(properties.end(), new_properties.begin(), new_properties.end());
173✔
197
      count -= new_properties.size();
173✔
198
   }
199

200
   return properties;
173✔
201
}
×
202

203
}  // namespace
204

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

210
   BufferStuffer bs(vendor_string);
11✔
211

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

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

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

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

238
      if(auto hash_id = hash_algo_botan_to_tss2(algo_name)) {
24✔
239
         result.push_back(hash_id.value());
6✔
240
      }
241

242
      if(auto block_id = block_cipher_botan_to_tss2(algo_name)) {
24✔
243
         result.push_back(block_id->first);
2✔
244
      }
245

246
      if(auto cipher_mode_id = cipher_mode_botan_to_tss2(algo_name)) {
24✔
247
         result.push_back(cipher_mode_id.value());
1✔
248
      }
249

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

255
      if(auto sig_padding = rsa_signature_padding_botan_to_tss2(algo_name)) {
24✔
256
         result.push_back(sig_padding.value());
2✔
257
      }
258

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

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

271
      if(auto enc_id = rsa_encryption_padding_botan_to_tss2(algo_name)) {
24✔
272
         result.push_back(enc_id.value());
2✔
273
      }
274

275
      return result;
24✔
276
   }();
24✔
277

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

283
   const auto algo_caps =
20✔
284
      get_tpm_property_list<TPM2_CAP_ALGS, TPM2_ALG_ID>(m_impl->m_ctx, TPM2_ALG_FIRST, TPM2_MAX_CAP_ALGS);
20✔
285

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

290
size_t Context::max_random_bytes_per_request() const {
9✔
291
   return get_tpm_property(m_impl->m_ctx, TPM2_PT_MAX_DIGEST);
9✔
292
}
293

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

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

303
std::optional<TPM2_HANDLE> Context::find_free_persistent_handle() const {
2✔
304
   const auto occupied_handles = persistent_handles();
2✔
305

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

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

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

324
   BOTAN_ASSERT_UNREACHABLE();
×
325
}
2✔
326

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

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

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

342
   // 1. Decide on the location to persist the key to.
343
   //    This uses either the handle provided by the caller or a free handle.
344
   const TPMI_DH_PERSISTENT new_persistent_handle = [&] {
6✔
345
      if(persistent_handle.has_value()) {
2✔
346
         return persistent_handle.value();
×
347
      } else {
348
         const auto free_persistent_handle = find_free_persistent_handle();
2✔
349
         BOTAN_STATE_CHECK(free_persistent_handle.has_value());
2✔
350
         return free_persistent_handle.value();
2✔
351
      }
352
   }();
2✔
353

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

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

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

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

385
   return new_persistent_handle;
2✔
386
}
387

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

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

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

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

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

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

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

453
      Esys_Finalize(&m_impl->m_ctx);
8✔
454
   }
455
}
42✔
456

457
}  // 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