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

randombit / botan / 12370093224

17 Dec 2024 09:27AM UTC coverage: 91.262% (+0.003%) from 91.259%
12370093224

Pull #4478

github

web-flow
Merge b307ce8bd into 694344901
Pull Request #4478: Fix msvc warnings

93398 of 102340 relevant lines covered (91.26%)

11403200.06 hits per line

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

87.24
/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/int_utils.h>
16
#include <botan/internal/loadstor.h>
17
#include <botan/internal/stl_util.h>
18
#include <botan/internal/tpm2_algo_mappings.h>
19
#include <botan/internal/tpm2_util.h>
20

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

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

30
namespace Botan::TPM2 {
31

32
namespace {
33

34
constexpr TPM2_HANDLE storage_root_key_handle = TPM2_HR_PERSISTENT + 1;
35

36
}  // namespace
37

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

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

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

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

58
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
×
59
   ESYS_CONTEXT* esys_ctx = nullptr;
×
60
   check_rc("TCTI Initialization", Tss2_TctiLdr_Initialize(nameconf_ptr, &tcti_ctx));
×
61
   BOTAN_ASSERT_NONNULL(tcti_ctx);
×
62
   check_rc("TPM2 Initialization", Esys_Initialize(&esys_ctx, tcti_ctx, nullptr /* ABI version */));
×
63
   BOTAN_ASSERT_NONNULL(esys_ctx);
×
64

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

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

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

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

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

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

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

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

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

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

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

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

129
namespace {
130

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

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

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

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

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

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

204
   return properties;
173✔
205
}
×
206

207
}  // namespace
208

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

214
   BufferStuffer bs(vendor_string);
11✔
215

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

328
   BOTAN_ASSERT_UNREACHABLE();
×
329
}
2✔
330

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

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

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

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

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

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

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

386
   BOTAN_ASSERT_NOMSG(handles.has_persistent_handle());
2✔
387
   BOTAN_ASSERT_EQUAL(new_persistent_handle, handles.persistent_handle(), "key was persisted at the correct location");
2✔
388

389
   return new_persistent_handle;
2✔
390
}
391

392
void Context::evict(std::unique_ptr<TPM2::PrivateKey> key, const SessionBundle& sessions) {
2✔
393
   BOTAN_ASSERT_NONNULL(key);
2✔
394

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

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

413
   // 2. The persistent key was deleted and the transient key was flushed by
414
   //    Esys_EvictControl().
415
   handles._disengage();
2✔
416
}
2✔
417

418
Context::~Context() {
28✔
419
   if(!m_impl) {
14✔
420
      return;
×
421
   }
422

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

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

457
      Esys_Finalize(&m_impl->m_ctx);
8✔
458
   }
459
}
42✔
460

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

© 2025 Coveralls, Inc