• 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

95.23
/src/tests/test_tpm2.cpp
1
/*
2
* (C) 2024 Jack Lloyd
3
* (C) 2024 René Meusel, Amos Treiber Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#include <botan/internal/fmt.h>
11
#include <botan/internal/loadstor.h>
12
#include <botan/internal/stl_util.h>
13

14
#if defined(BOTAN_HAS_TPM2)
15
   #include <botan/internal/tpm2_hash.h>
16

17
   #include <botan/pubkey.h>
18
   #include <botan/tpm2_key.h>
19
   #include <botan/tpm2_rng.h>
20
   #include <botan/tpm2_session.h>
21

22
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
23
      #include <botan/tpm2_rsa.h>
24
   #endif
25

26
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
27
      #include <botan/ecdsa.h>
28
      #include <botan/tpm2_ecc.h>
29
   #endif
30

31
   #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
32
      #include <botan/tpm2_crypto_backend.h>
33
   #endif
34

35
   // for testing externally-provided ESYS context
36
   #include <tss2/tss2_esys.h>
37
   #include <tss2/tss2_tctildr.h>
38
#endif
39

40
namespace Botan_Tests {
41

42
#if defined(BOTAN_HAS_TPM2)
43
namespace {
44

45
   #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND) && defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
46
constexpr bool crypto_backend_should_be_available = true;
47
   #else
48
constexpr bool crypto_backend_should_be_available = false;
49
   #endif
50

51
bool validate_context_environment(const std::shared_ptr<Botan::TPM2::Context>& ctx) {
10✔
52
   return (ctx->vendor() == "SW   TPM" && ctx->manufacturer() == "IBM");
10✔
53
}
54

55
std::shared_ptr<Botan::TPM2::Context> get_tpm2_context(std::string_view rng_tag) {
7✔
56
   const auto tcti_name = Test::options().tpm2_tcti_name();
7✔
57
   if(tcti_name.value() == "disabled") {
7✔
58
      // skip the test if the special 'disabled' TCTI is configured
59
      return {};
×
60
   }
61

62
   auto ctx = Botan::TPM2::Context::create(tcti_name, Test::options().tpm2_tcti_conf());
14✔
63
   if(!validate_context_environment(ctx)) {
7✔
64
      return {};
×
65
   }
66

67
   if(ctx->supports_botan_crypto_backend()) {
7✔
68
      ctx->use_botan_crypto_backend(Test::new_rng(rng_tag));
21✔
69
   }
70

71
   return ctx;
7✔
72
}
14✔
73

74
/// RAII helper to manage raw transient resources (ESYS_TR) handles
75
class TR {
76
   private:
77
      ESYS_CONTEXT* m_esys_ctx;
78
      ESYS_TR m_handle;
79

80
   public:
81
      TR(ESYS_CONTEXT* esys_ctx, ESYS_TR handle) : m_esys_ctx(esys_ctx), m_handle(handle) {}
4✔
82

83
      TR(TR&& other) noexcept { *this = std::move(other); }
4✔
84

85
      TR& operator=(TR&& other) noexcept {
4✔
86
         if(this != &other) {
4✔
87
            m_esys_ctx = other.m_esys_ctx;
4✔
88
            m_handle = std::exchange(other.m_handle, ESYS_TR_NONE);
4✔
89
         }
90
         return *this;
4✔
91
      }
92

93
      TR(const TR&) = delete;
94
      TR& operator=(const TR&) = delete;
95

96
      ~TR() {
8✔
97
         if(m_esys_ctx && m_handle != ESYS_TR_NONE) {
4✔
98
            Esys_FlushContext(m_esys_ctx, m_handle);
4✔
99
         }
100
      }
101

102
      constexpr operator ESYS_TR() const { return m_handle; }
3✔
103
};
104

105
struct esys_context_liberator {
106
      void operator()(ESYS_CONTEXT* esys_ctx) {
3✔
107
         TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
3✔
108
         Esys_GetTcti(esys_ctx, &tcti_ctx);  // ignore error in destructor
3✔
109
         if(tcti_ctx != nullptr) {
3✔
110
            Tss2_TctiLdr_Finalize(&tcti_ctx);
3✔
111
         }
112
         Esys_Finalize(&esys_ctx);
3✔
113
      }
3✔
114
};
115

116
auto get_external_tpm2_context() -> std::unique_ptr<ESYS_CONTEXT, esys_context_liberator> {
3✔
117
   const auto tcti_name = Test::options().tpm2_tcti_name();
3✔
118
   const auto tcti_conf = Test::options().tpm2_tcti_conf();
3✔
119
   if(tcti_name.value() == "disabled") {
3✔
120
      // skip the test if the special 'disabled' TCTI is configured
121
      return nullptr;
×
122
   }
123

124
   TSS2_RC rc;
3✔
125
   TSS2_TCTI_CONTEXT* tcti_ctx;
3✔
126
   std::unique_ptr<ESYS_CONTEXT, esys_context_liberator> esys_ctx;
3✔
127

128
   rc = Tss2_TctiLdr_Initialize_Ex(tcti_name->c_str(), tcti_conf->c_str(), &tcti_ctx);
3✔
129
   if(rc != TSS2_RC_SUCCESS) {
3✔
130
      throw Test_Error("failed to initialize external TCTI");
×
131
   }
132

133
   rc = Esys_Initialize(Botan::out_ptr(esys_ctx), tcti_ctx, nullptr /* ABI version */);
3✔
134
   if(rc != TSS2_RC_SUCCESS) {
3✔
135
      throw Test_Error("failed to initialize external ESYS");
×
136
   }
137

138
   // This TPM2::Context is created for environment validation only.
139
   // It is transient, but the 'externally provided' ESYS_CONTEXT will live on!
140
   auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
3✔
141
   if(!validate_context_environment(ctx)) {
3✔
142
      return nullptr;
×
143
   }
144

145
   return esys_ctx;
3✔
146
}
9✔
147

148
void bail_out(Test::Result& result, std::optional<std::string> reason = {}) {
×
149
   if(reason.has_value()) {
×
150
      result.test_note(reason.value());
×
151
   } else if(Test::options().tpm2_tcti_name() == "disabled") {
×
152
      result.test_note("TPM2 tests are disabled.");
×
153
   } else {
154
      result.test_failure("Not sure we're on a simulated TPM2, cautiously refusing any action.");
×
155
   }
156
}
×
157

158
Test::Result bail_out() {
×
159
   Test::Result result("TPM2 test bail out");
×
160
   bail_out(result);
×
161
   return result;
×
162
}
×
163

164
bool not_zero_64(std::span<const uint8_t> in) {
6✔
165
   Botan::BufferSlicer bs(in);
6✔
166

167
   while(bs.remaining() > 8) {
78✔
168
      if(Botan::load_be(bs.take<8>()) == 0) {
144✔
169
         return false;
170
      }
171
   }
172
   // Ignore remaining bytes
173

174
   return true;
175
}
176

177
std::vector<Test::Result> test_tpm2_properties() {
1✔
178
   auto ctx = get_tpm2_context(__func__);
1✔
179
   if(!ctx) {
1✔
180
      return {bail_out()};
×
181
   }
182

183
   return {
1✔
184
      CHECK("Vendor and Manufacturer",
185
            [&](Test::Result& result) {
1✔
186
               result.test_eq("Vendor", ctx->vendor(), "SW   TPM");
2✔
187
               result.test_eq("Manufacturer", ctx->manufacturer(), "IBM");
2✔
188
            }),
1✔
189

190
      CHECK("Max random bytes per request",
191
            [&](Test::Result& result) {
1✔
192
               const auto prop = ctx->max_random_bytes_per_request();
1✔
193
               result.test_gte("at least as long as SHA-256", prop, 32);
1✔
194
               result.test_lte("at most as long as SHA-512", prop, 64);
1✔
195
            }),
1✔
196

197
      CHECK("Supports basic algorithms",
198
            [&](Test::Result& result) {
1✔
199
               result.confirm("RSA is supported", ctx->supports_algorithm("RSA"));
2✔
200
               result.confirm("AES-128 is supported", ctx->supports_algorithm("AES-128"));
2✔
201
               result.confirm("AES-256 is supported", ctx->supports_algorithm("AES-256"));
2✔
202
               result.confirm("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
2✔
203
               result.confirm("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
2✔
204
               result.confirm("OFB(AES-128) is supported", ctx->supports_algorithm("OFB(AES-128)"));
2✔
205
               result.confirm("OFB is supported", ctx->supports_algorithm("OFB"));
2✔
206
            }),
1✔
207

208
      CHECK("Unsupported algorithms aren't supported",
209
            [&](Test::Result& result) {
1✔
210
               result.confirm("Enigma is not supported", !ctx->supports_algorithm("Enigma"));
2✔
211
               result.confirm("MD5 is not supported", !ctx->supports_algorithm("MD5"));
2✔
212
               result.confirm("DES is not supported", !ctx->supports_algorithm("DES"));
2✔
213
               result.confirm("OAEP(Keccak) is not supported", !ctx->supports_algorithm("OAEP(Keccak)"));
2✔
214
            }),
1✔
215
   };
5✔
216
}
2✔
217

218
std::vector<Test::Result> test_tpm2_context() {
1✔
219
   auto ctx = get_tpm2_context(__func__);
1✔
220
   if(!ctx) {
1✔
221
      return {bail_out()};
×
222
   }
223

224
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
225

226
   return {
1✔
227
      CHECK("Persistent handles",
228
            [&](Test::Result& result) {
1✔
229
               const auto handles = ctx->persistent_handles();
1✔
230
               result.confirm("At least one persistent handle", !handles.empty());
2✔
231
               result.confirm("SRK is in the list", Botan::value_exists(handles, 0x81000001));
3✔
232
               result.confirm("Test private key is in the list", Botan::value_exists(handles, persistent_key_id));
3✔
233
               result.confirm("Test persistence location is not in the list",
1✔
234
                              !Botan::value_exists(handles, persistent_key_id + 1));
3✔
235
            }),
1✔
236

237
         CHECK("Crypto backend",
238
               [&](Test::Result& result) {
1✔
239
                  const bool backend_supported = ctx->supports_botan_crypto_backend();
1✔
240
                  const bool backend_used = ctx->uses_botan_crypto_backend();
1✔
241
                  result.require("Crypto backend availability",
1✔
242
                                 backend_supported == crypto_backend_should_be_available);
243
                  result.require("Crypto backend is used in the tests, if it is available",
1✔
244
                                 backend_used == backend_supported);
245

246
                  if(backend_used) {
1✔
247
                     result.test_throws<Botan::Invalid_State>(
3✔
248
                        "If the backend is already in use, we cannot enable it once more",
249
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng(__func__)); });
4✔
250
                  }
251

252
                  if(!backend_supported) {
1✔
253
                     result.test_throws<Botan::Not_Implemented>(
×
254
                        "If the backend is not supported, we cannot enable it",
255
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng(__func__)); });
×
256
                  }
257
               }),
1✔
258

259
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
260
         // TODO: Since SRK is always RSA in start_tpm2_simulator.sh, the test always requires the RSA adapter?
261
         CHECK("Fetch Storage Root Key RSA", [&](Test::Result& result) {
1✔
262
            auto srk = ctx->storage_root_key({}, {});
2✔
263
            result.require("SRK is not null", srk != nullptr);
1✔
264
            result.test_eq("Algo", srk->algo_name(), "RSA");
2✔
265
            result.test_eq("Key size", srk->key_length(), 2048);
1✔
266
            result.confirm("Has persistent handle", srk->handles().has_persistent_handle());
2✔
267
         }),
1✔
268
   #endif
269
   };
4✔
270
}
2✔
271

272
std::vector<Test::Result> test_external_tpm2_context() {
1✔
273
   auto raw_start_session = [](ESYS_CONTEXT* esys_ctx) -> std::pair<TR, TSS2_RC> {
5✔
274
      const TPMT_SYM_DEF sym_spec{
4✔
275
         .algorithm = TPM2_ALG_AES,
276
         .keyBits = {.sym = 256},
277
         .mode = {.sym = TPM2_ALG_CFB},
278
      };
279
      ESYS_TR session;
4✔
280

281
      auto rc = Esys_StartAuthSession(esys_ctx,
4✔
282
                                      ESYS_TR_NONE,
283
                                      ESYS_TR_NONE,
284
                                      ESYS_TR_NONE,
285
                                      ESYS_TR_NONE,
286
                                      ESYS_TR_NONE,
287
                                      nullptr,
288
                                      TPM2_SE_HMAC,
289
                                      &sym_spec,
290
                                      TPM2_ALG_SHA256,
291
                                      &session);
4✔
292

293
      if(rc == TSS2_RC_SUCCESS) {
4✔
294
         const auto session_attributes = TPMA_SESSION_CONTINUESESSION | TPMA_SESSION_DECRYPT | TPMA_SESSION_ENCRYPT;
3✔
295
         rc = Esys_TRSess_SetAttributes(esys_ctx, session, session_attributes, 0xFF);
3✔
296
      }
297

298
      return {TR{esys_ctx, session}, rc};
4✔
299
   };
300

301
   auto raw_get_random_bytes = [](ESYS_CONTEXT* esys_ctx, uint16_t bytes, ESYS_TR session = ESYS_TR_NONE) {
4✔
302
      Botan::TPM2::unique_esys_ptr<TPM2B_DIGEST> random_bytes;
3✔
303
      const auto rc =
3✔
304
         Esys_GetRandom(esys_ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, bytes, Botan::out_ptr(random_bytes));
3✔
305
      return std::make_pair(std::move(random_bytes), rc);
3✔
306
   };
3✔
307

308
   return {
1✔
309
      CHECK("ESYS context is still functional after TPM2::Context destruction",
310
            [&](Test::Result& result) {
1✔
311
               auto esys_ctx = get_external_tpm2_context();
1✔
312
               if(!esys_ctx) {
1✔
313
                  bail_out(result);
×
314
                  return;
×
315
               }
316

317
               {
1✔
318
                  // Do some TPM2-stuff via the Botan wrappers
319

320
                  auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
1✔
321
                  auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
322
                  auto rng = Botan::TPM2::RandomNumberGenerator(ctx, session);
3✔
323

324
                  auto bytes = rng.random_vec(16);
1✔
325
                  result.test_eq("some random bytes generated", bytes.size(), 16);
2✔
326

327
                  // All Botan-wrapped things go out of scope...
328
               }
2✔
329

330
               auto [raw_session, rc_session] = raw_start_session(esys_ctx.get());
1✔
331
               Botan::TPM2::check_rc("session creation successful", rc_session);
1✔
332

333
               auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
334
               Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
335
               result.test_eq_sz("some raw random bytes generated", bytes->size, 16);
2✔
336
            }),
2✔
337

338
         CHECK("TPM2::Context-managed crypto backend fails gracefully after TPM2::Context destruction",
339
               [&](Test::Result& result) {
1✔
340
                  auto esys_ctx = get_external_tpm2_context();
1✔
341
                  if(!esys_ctx) {
1✔
342
                     bail_out(result);
×
343
                     return;
×
344
                  }
345

346
                  {
1✔
347
                     auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
1✔
348
                     if(!ctx->supports_botan_crypto_backend()) {
1✔
349
                        bail_out(result, "skipping, because botan-based crypto backend is not supported");
×
350
                        return;
×
351
                     }
352

353
                     ctx->use_botan_crypto_backend(Test::new_rng(__func__));
3✔
354
                  }
×
355

356
                  auto [session, session_rc1] = raw_start_session(esys_ctx.get());
1✔
357

358
                  // After the destruction of the TPM2::Context in the anonymous
359
                  // scope above the botan-based TSS crypto callbacks aren't able
360
                  // to access the state that was managed by the TPM2::Context.
361
                  result.require("expected error", session_rc1 == TSS2_ESYS_RC_BAD_REFERENCE);
1✔
362

363
   #if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
364
                  // Manually resetting the crypto callbacks (in retrospect) fixes this
365
                  const auto callbacks_rc = Esys_SetCryptoCallbacks(esys_ctx.get(), nullptr);
1✔
366
                  Botan::TPM2::check_rc("resetting crypto callbacks", callbacks_rc);
1✔
367

368
                  auto [raw_session, session_rc2] = raw_start_session(esys_ctx.get());
1✔
369
                  Botan::TPM2::check_rc("session creation successful", session_rc2);
1✔
370

371
                  auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
372
                  Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
373
                  result.test_eq_sz("some raw random bytes generated", bytes->size, 16);
2✔
374
   #endif
375
               }),
3✔
376

377
   #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
378
         CHECK("free-standing crypto backend", [&](Test::Result& result) {
1✔
379
            if(!Botan::TPM2::supports_botan_crypto_backend()) {
1✔
380
               bail_out(result, "botan crypto backend is not supported");
×
381
               return;
×
382
            }
383

384
            auto esys_ctx = get_external_tpm2_context();
1✔
385
            if(!esys_ctx) {
1✔
386
               bail_out(result);
×
387
               return;
×
388
            }
389

390
            auto cb_state = Botan::TPM2::use_botan_crypto_backend(esys_ctx.get(), Test::new_rng(__func__));
3✔
391

392
            auto [raw_session, session_rc2] = raw_start_session(esys_ctx.get());
1✔
393
            Botan::TPM2::check_rc("session creation successful", session_rc2);
1✔
394

395
            auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
396
            Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
397
            result.test_eq_sz("some raw random bytes generated", bytes->size, 16);
2✔
398
         }),
3✔
399
   #endif
400
   };
4✔
401
}
1✔
402

403
std::vector<Test::Result> test_tpm2_sessions() {
1✔
404
   auto ctx = get_tpm2_context(__func__);
1✔
405
   if(!ctx) {
1✔
406
      return {bail_out()};
×
407
   }
408

409
   auto ok = [](Test::Result& result, std::string_view name, const std::shared_ptr<Botan::TPM2::Session>& session) {
13✔
410
      result.require(Botan::fmt("Session '{}' is non-null", name), session != nullptr);
12✔
411
      result.confirm(Botan::fmt("Session '{}' has a valid handle", name), session->handle() != ESYS_TR_NONE);
24✔
412
      result.confirm(Botan::fmt("Session '{}' has a non-empty nonce", name), !session->tpm_nonce().empty());
24✔
413
   };
12✔
414

415
   return {
1✔
416
      CHECK("Unauthenticated sessions",
417
            [&](Test::Result& result) {
1✔
418
               using Session = Botan::TPM2::Session;
1✔
419

420
               ok(result, "default", Session::unauthenticated_session(ctx));
1✔
421
               ok(result, "CFB(AES-128)", Session::unauthenticated_session(ctx, "CFB(AES-128)"));
1✔
422
               ok(result, "CFB(AES-128),SHA-384", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-384"));
1✔
423
               ok(result, "CFB(AES-128),SHA-1", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-1"));
1✔
424
            }),
1✔
425

426
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
427
         CHECK(
428
            "Authenticated sessions SRK",
429
            [&](Test::Result& result) {
1✔
430
               using Session = Botan::TPM2::Session;
1✔
431

432
               auto srk = ctx->storage_root_key({}, {});
2✔
433
               ok(result, "default", Session::authenticated_session(ctx, *srk));
1✔
434
               ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *srk, "CFB(AES-128)"));
1✔
435
               ok(result, "CFB(AES-128),SHA-384", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-384"));
1✔
436
               ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-1"));
2✔
437
            }),
1✔
438
   #endif
439

440
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
441
         CHECK("Authenticated sessions ECC", [&](Test::Result& result) {
1✔
442
            using Session = Botan::TPM2::Session;
1✔
443
            const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
444

445
            auto ecc_key = Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, {}, {});
2✔
446
            result.require("EK is not null", ecc_key != nullptr);
1✔
447
            result.test_eq("Algo", ecc_key->algo_name(), "ECDSA");
2✔
448
            result.confirm("Has persistent handle", ecc_key->handles().has_persistent_handle());
2✔
449

450
            ok(result, "default", Session::authenticated_session(ctx, *ecc_key));
1✔
451
            ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)"));
1✔
452
            ok(result,
1✔
453
               "CFB(AES-128),SHA-384",
454
               Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-384"));
1✔
455
            ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-1"));
2✔
456
         }),
1✔
457
   #endif
458
   };
4✔
459
}
2✔
460

461
std::vector<Test::Result> test_tpm2_rng() {
1✔
462
   auto ctx = get_tpm2_context(__func__);
1✔
463
   if(!ctx) {
1✔
464
      return {bail_out()};
×
465
   }
466

467
   auto rng = Botan::TPM2::RandomNumberGenerator(ctx, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
468

469
   return {
1✔
470
      CHECK("Basic functionalities",
471
            [&](Test::Result& result) {
1✔
472
               result.confirm("Accepts input", rng.accepts_input());
2✔
473
               result.confirm("Is seeded", rng.is_seeded());
2✔
474
               result.test_eq("Right name", rng.name(), "TPM2_RNG");
2✔
475

476
               result.test_no_throw("Clear", [&] { rng.clear(); });
2✔
477
            }),
1✔
478

479
      CHECK("Random number generation",
480
            [&](Test::Result& result) {
1✔
481
               std::array<uint8_t, 8> buf1 = {};
1✔
482
               rng.randomize(buf1);
1✔
483
               result.confirm("Is at least not 0 (8)", not_zero_64(buf1));
2✔
484

485
               std::array<uint8_t, 15> buf2 = {};
1✔
486
               rng.randomize(buf2);
1✔
487
               result.confirm("Is at least not 0 (15)", not_zero_64(buf2));
2✔
488

489
               std::array<uint8_t, 256> buf3 = {};
1✔
490
               rng.randomize(buf3);
1✔
491
               result.confirm("Is at least not 0 (256)", not_zero_64(buf3));
2✔
492
            }),
1✔
493

494
      CHECK("Randomize with inputs",
495
            [&](Test::Result& result) {
1✔
496
               std::array<uint8_t, 9> buf1 = {};
1✔
497
               rng.randomize_with_input(buf1, std::array<uint8_t, 30>{});
1✔
498
               result.confirm("Randomized with inputs is at least not 0 (9)", not_zero_64(buf1));
2✔
499

500
               std::array<uint8_t, 66> buf2 = {};
1✔
501
               rng.randomize_with_input(buf2, std::array<uint8_t, 64>{});
1✔
502
               result.confirm("Randomized with inputs is at least not 0 (66)", not_zero_64(buf2));
2✔
503

504
               std::array<uint8_t, 256> buf3 = {};
1✔
505
               rng.randomize_with_input(buf3, std::array<uint8_t, 196>{});
1✔
506
               result.confirm("Randomized with inputs is at least not 0 (256)", not_zero_64(buf3));
2✔
507
            }),
1✔
508
   };
4✔
509
}
3✔
510

511
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
512

513
template <typename KeyT>
514
auto load_persistent(Test::Result& result,
32✔
515
                     const std::shared_ptr<Botan::TPM2::Context>& ctx,
516
                     uint32_t persistent_key_id,
517
                     std::span<const uint8_t> auth_value,
518
                     const std::shared_ptr<Botan::TPM2::Session>& session) {
519
   const auto persistent_handles = ctx->persistent_handles();
32✔
520
   result.confirm(
64✔
521
      "Persistent key available",
522
      std::find(persistent_handles.begin(), persistent_handles.end(), persistent_key_id) != persistent_handles.end());
32✔
523

524
   auto key = [&] {
64✔
525
      if constexpr(std::same_as<Botan::TPM2::RSA_PublicKey, KeyT>) {
526
         return KeyT::load_persistent(ctx, persistent_key_id, session);
10✔
527
      } else {
528
         return KeyT::load_persistent(ctx, persistent_key_id, auth_value, session);
54✔
529
      }
530
   }();
531

532
   result.test_eq("Algo", key->algo_name(), "RSA" /* TODO ECC support*/);
64✔
533
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
64✔
534
   return key;
32✔
535
}
32✔
536

537
std::vector<Test::Result> test_tpm2_rsa() {
1✔
538
   auto ctx = get_tpm2_context(__func__);
1✔
539
   if(!ctx) {
1✔
540
      return {bail_out()};
×
541
   }
542

543
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
544

545
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
546
   const auto password = Test::options().tpm2_persistent_auth_value();
1✔
547

548
   return {
1✔
549
      CHECK("RSA and its helpers are supported",
550
            [&](Test::Result& result) {
1✔
551
               result.confirm("RSA is supported", ctx->supports_algorithm("RSA"));
2✔
552
               result.confirm("PKCS1 is supported", ctx->supports_algorithm("PKCS1v15"));
2✔
553
               result.confirm("PKCS1 with hash is supported", ctx->supports_algorithm("PKCS1v15(SHA-1)"));
2✔
554
               result.confirm("OAEP is supported", ctx->supports_algorithm("OAEP"));
2✔
555
               result.confirm("OAEP with hash is supported", ctx->supports_algorithm("OAEP(SHA-256)"));
2✔
556
               result.confirm("PSS is supported", ctx->supports_algorithm("PSS"));
2✔
557
               result.confirm("PSS with hash is supported", ctx->supports_algorithm("PSS(SHA-256)"));
2✔
558
            }),
1✔
559

560
      CHECK("Load the private key multiple times",
561
            [&](Test::Result& result) {
1✔
562
               for(size_t i = 0; i < 20; ++i) {
21✔
563
                  auto key =
20✔
564
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
20✔
565
                  result.test_eq(Botan::fmt("Key loaded successfully ({})", i), key->algo_name(), "RSA");
40✔
566
               }
20✔
567
            }),
1✔
568

569
      CHECK("Sign a message",
570
            [&](Test::Result& result) {
1✔
571
               auto key =
1✔
572
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
573

574
               Botan::Null_RNG null_rng;
1✔
575
               Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
576

577
               // create a message that is larger than the TPM2 max buffer size
578
               const auto message = [] {
3✔
579
                  std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
580
                  for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
581
                     msg[i] = static_cast<uint8_t>(i);
1,029✔
582
                  }
583
                  return msg;
1✔
584
               }();
1✔
585
               const auto signature = signer.sign_message(message, null_rng);
1✔
586
               result.require("signature is not empty", !signature.empty());
1✔
587

588
               auto public_key = key->public_key();
1✔
589
               Botan::PK_Verifier verifier(*public_key, "PSS(SHA-256)");
1✔
590
               result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
591
            }),
5✔
592

593
      CHECK("verify signature",
594
            [&](Test::Result& result) {
1✔
595
               auto sign = [&](std::span<const uint8_t> message) {
2✔
596
                  auto key =
1✔
597
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
598
                  Botan::Null_RNG null_rng;
1✔
599
                  Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
600
                  return signer.sign_message(message, null_rng);
1✔
601
               };
2✔
602

603
               auto verify = [&](std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
4✔
604
                  auto key =
3✔
605
                     load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
3✔
606
                  Botan::PK_Verifier verifier(*key, "PSS(SHA-256)");
3✔
607
                  return verifier.verify_message(msg, sig);
3✔
608
               };
6✔
609

610
               const auto message = Botan::hex_decode("baadcafe");
1✔
611
               const auto signature = sign(message);
1✔
612

613
               result.confirm("verification successful", verify(message, signature));
2✔
614

615
               // change the message
616
               auto rng = Test::new_rng(__func__);
1✔
617
               auto mutated_message = Test::mutate_vec(message, *rng);
1✔
618
               result.confirm("verification failed", !verify(mutated_message, signature));
2✔
619

620
               // ESAPI manipulates the session attributes internally and does
621
               // not reset them when an error occurs. A failure to validate a
622
               // signature is an error, and hence behaves surprisingly by
623
               // leaving the session attributes in an unexpected state.
624
               // The Botan wrapper has a workaround for this...
625
               const auto attrs = session->attributes();
1✔
626
               result.confirm("encrypt flag was not cleared by ESAPI", attrs.encrypt);
2✔
627

628
               // orignal message again
629
               result.confirm("verification still successful", verify(message, signature));
2✔
630
            }),
4✔
631

632
      CHECK("sign and verify multiple messages with the same Signer/Verifier objects",
633
            [&](Test::Result& result) {
1✔
634
               const std::vector<std::vector<uint8_t>> messages = {
1✔
635
                  Botan::hex_decode("BAADF00D"),
636
                  Botan::hex_decode("DEADBEEF"),
637
                  Botan::hex_decode("CAFEBABE"),
638
               };
4✔
639

640
               // Generate a few signatures, then deallocate the private key.
641
               auto signatures = [&] {
3✔
642
                  auto sk =
1✔
643
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
644
                  Botan::Null_RNG null_rng;
1✔
645
                  Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
646
                  std::vector<std::vector<uint8_t>> sigs;
1✔
647
                  sigs.reserve(messages.size());
1✔
648
                  for(const auto& message : messages) {
4✔
649
                     sigs.emplace_back(signer.sign_message(message, null_rng));
9✔
650
                  }
651
                  return sigs;
2✔
652
               }();
2✔
653

654
               // verify via TPM 2.0
655
               auto pk = load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
656
               Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
1✔
657
               for(size_t i = 0; i < messages.size(); ++i) {
4✔
658
                  result.confirm(Botan::fmt("verification successful ({})", i),
3✔
659
                                 verifier.verify_message(messages[i], signatures[i]));
3✔
660
               }
661

662
               // verify via software
663
               auto soft_pk = Botan::RSA_PublicKey(pk->algorithm_identifier(), pk->public_key_bits());
1✔
664
               Botan::PK_Verifier soft_verifier(soft_pk, "PSS(SHA-256)");
1✔
665
               for(size_t i = 0; i < messages.size(); ++i) {
4✔
666
                  result.confirm(Botan::fmt("software verification successful ({})", i),
3✔
667
                                 soft_verifier.verify_message(messages[i], signatures[i]));
3✔
668
               }
669
            }),
4✔
670

671
      CHECK("Wrong password is not accepted during signing",
672
            [&](Test::Result& result) {
1✔
673
               auto key = load_persistent<Botan::TPM2::RSA_PrivateKey>(
1✔
674
                  result, ctx, persistent_key_id, Botan::hex_decode("deadbeef"), session);
1✔
675

676
               Botan::Null_RNG null_rng;
1✔
677
               Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
678

679
               const auto message = Botan::hex_decode("baadcafe");
1✔
680
               result.test_throws<Botan::TPM2::Error>("Fail with wrong password",
2✔
681
                                                      [&] { signer.sign_message(message, null_rng); });
3✔
682
            }),
2✔
683

684
      CHECK("Encrypt a message",
685
            [&](Test::Result& result) {
1✔
686
               auto pk = load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
687
               auto sk =
1✔
688
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
689

690
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
691

692
               // encrypt a message using the TPM's public key
693
               Botan::Null_RNG null_rng;
1✔
694
               Botan::PK_Encryptor_EME enc(*pk, null_rng, "OAEP(SHA-256)");
1✔
695
               const auto ciphertext = enc.encrypt(plaintext, null_rng);
1✔
696

697
               // decrypt the message using the TPM's private RSA key
698
               Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
699
               const auto decrypted = dec.decrypt(ciphertext);
1✔
700
               result.test_eq("decrypted message", decrypted, plaintext);
2✔
701
            }),
5✔
702

703
      CHECK("Decrypt a message",
704
            [&](Test::Result& result) {
1✔
705
               auto key =
1✔
706
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
707

708
               const auto plaintext = Botan::hex_decode("feedface");
1✔
709

710
               // encrypt a message using a software RSA key for the TPM's private key
711
               auto pk = key->public_key();
1✔
712
               auto rng = Test::new_rng("tpm2 rsa decrypt");
1✔
713
               Botan::PK_Encryptor_EME enc(*pk, *rng, "OAEP(SHA-256)");
1✔
714
               const auto ciphertext = enc.encrypt(plaintext, *rng);
1✔
715

716
               // decrypt the message using the TPM's private key
717
               Botan::Null_RNG null_rng;
1✔
718
               Botan::PK_Decryptor_EME dec(*key, null_rng /* TPM takes care of this */, "OAEP(SHA-256)");
1✔
719
               const auto decrypted = dec.decrypt(ciphertext);
1✔
720
               result.test_eq("decrypted message", decrypted, plaintext);
1✔
721

722
               // corrupt the ciphertext and try to decrypt it
723
               auto mutated_ciphertext = Test::mutate_vec(ciphertext, *rng);
1✔
724
               result.test_throws<Botan::Decoding_Error>("Fail with wrong ciphertext",
3✔
725
                                                         [&] { dec.decrypt(mutated_ciphertext); });
2✔
726
            }),
7✔
727

728
      CHECK("Create a transient key and encrypt/decrypt a message",
729
            [&](Test::Result& result) {
1✔
730
               auto srk = ctx->storage_root_key({}, {});
2✔
731
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
732

733
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
734
               auto sk =
1✔
735
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
736
               auto pk = sk->public_key();
1✔
737

738
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
739

740
               // encrypt a message using the TPM's public key
741
               auto rng = Test::new_rng(__func__);
1✔
742
               Botan::PK_Encryptor_EME enc(*pk, *rng, "OAEP(SHA-256)");
1✔
743
               const auto ciphertext = enc.encrypt(plaintext, *rng);
1✔
744

745
               // decrypt the message using the TPM's private RSA key
746
               Botan::Null_RNG null_rng;
1✔
747
               Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
748
               const auto decrypted = dec.decrypt(ciphertext);
1✔
749
               result.test_eq("decrypted message", decrypted, plaintext);
1✔
750

751
               // encrypt a message using the TPM's public key (using PKCS#1)
752
               Botan::PK_Encryptor_EME enc_pkcs(*pk, *rng, "PKCS1v15");
1✔
753
               const auto ciphertext_pkcs = enc_pkcs.encrypt(plaintext, *rng);
1✔
754

755
               // decrypt the message using the TPM's private RSA key (using PKCS#1)
756
               Botan::PK_Decryptor_EME dec_pkcs(*sk, null_rng, "PKCS1v15");
1✔
757
               const auto decrypted_pkcs = dec_pkcs.decrypt(ciphertext_pkcs);
1✔
758
               result.test_eq("decrypted message", decrypted_pkcs, plaintext);
2✔
759
            }),
10✔
760

761
      CHECK("Cannot export private key blob from persistent key",
762
            [&](Test::Result& result) {
1✔
763
               auto key =
1✔
764
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
765
               result.test_throws<Botan::Not_Implemented>("Export private key blob not implemented",
2✔
766
                                                          [&] { key->private_key_bits(); });
2✔
767
               result.test_throws<Botan::Invalid_State>("Export raw private key blob not implemented",
3✔
768
                                                        [&] { key->raw_private_key_bits(); });
2✔
769
            }),
1✔
770

771
      CHECK("Create a new transient key",
772
            [&](Test::Result& result) {
1✔
773
               auto srk = ctx->storage_root_key({}, {});
2✔
774

775
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
776

777
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
778

779
               auto sk =
1✔
780
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
781

782
               result.require("key was created", sk != nullptr);
1✔
783
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
784
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
785

786
               const auto sk_blob = sk->raw_private_key_bits();
1✔
787
               const auto pk_blob = sk->raw_public_key_bits();
1✔
788
               const auto pk = sk->public_key();
1✔
789

790
               result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
791
               result.confirm("public blob is not empty", !pk_blob.empty());
2✔
792

793
               // Perform a round-trip sign/verify test with the new key pair
794
               std::vector<uint8_t> message = {'h', 'e', 'l', 'l', 'o'};
1✔
795
               Botan::Null_RNG null_rng;
1✔
796
               Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
797
               const auto signature = signer.sign_message(message, null_rng);
1✔
798
               result.require("signature is not empty", !signature.empty());
1✔
799

800
               Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
1✔
801
               result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
802

803
               // Destruct the key and load it again from the encrypted blob
804
               sk.reset();
1✔
805
               auto sk_loaded =
1✔
806
                  Botan::TPM2::PrivateKey::load_transient(ctx, secret, *srk, pk_blob, sk_blob, authed_session);
2✔
807
               result.require("key was loaded", sk_loaded != nullptr);
1✔
808
               result.test_eq("loaded key is RSA", sk_loaded->algo_name(), "RSA");
2✔
809

810
               const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
811
               const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
812

813
               result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
814
               result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
815

816
               // Perform a round-trip sign/verify test with the new key pair
817
               std::vector<uint8_t> message_loaded = {'g', 'u', 't', 'e', 'n', ' ', 't', 'a', 'g'};
1✔
818
               Botan::PK_Signer signer_loaded(*sk_loaded, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
819
               const auto signature_loaded = signer_loaded.sign_message(message_loaded, null_rng);
1✔
820
               result.require("Next signature is not empty", !signature_loaded.empty());
1✔
821
               result.confirm("Existing verifier can validate signature",
2✔
822
                              verifier.verify_message(message_loaded, signature_loaded));
1✔
823

824
               // Load the public portion of the key
825
               auto pk_loaded = Botan::TPM2::PublicKey::load_transient(ctx, pk_blob, {});
2✔
826
               result.require("public key was loaded", pk_loaded != nullptr);
1✔
827

828
               Botan::PK_Verifier verifier_loaded(*pk_loaded, "PSS(SHA-256)");
1✔
829
               result.confirm("TPM-verified signature is valid",
2✔
830
                              verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
831

832
               // Perform a round-trip sign/verify test with the new key pair (PKCS#1)
833
               std::vector<uint8_t> message_pkcs = {'b', 'o', 'n', 'j', 'o', 'u', 'r'};
1✔
834
               Botan::PK_Signer signer_pkcs(*sk_loaded, null_rng /* TPM takes care of this */, "PKCS1v15(SHA-256)");
1✔
835
               const auto signature_pkcs = signer_pkcs.sign_message(message_pkcs, null_rng);
1✔
836
               result.require("Next signature is not empty", !signature_pkcs.empty());
1✔
837
               result.confirm("Existing verifier cannot validate signature",
2✔
838
                              !verifier.verify_message(message_pkcs, signature_pkcs));
1✔
839

840
               // Create a verifier for PKCS#1
841
               Botan::PK_Verifier verifier_pkcs(*pk_loaded, "PKCS1v15(SHA-256)");
1✔
842
               result.confirm("TPM-verified signature is valid",
2✔
843
                              verifier_pkcs.verify_message(message_pkcs, signature_pkcs));
1✔
844
            }),
16✔
845

846
      CHECK("Make a transient key persistent then remove it again",
847
            [&](Test::Result& result) {
1✔
848
               auto srk = ctx->storage_root_key({}, {});
2✔
849

850
               auto sign_verify_roundtrip = [&](const Botan::TPM2::PrivateKey& key) {
3✔
851
                  std::vector<uint8_t> message = {'h', 'e', 'l', 'l', 'o'};
2✔
852
                  Botan::Null_RNG null_rng;
2✔
853
                  Botan::PK_Signer signer(key, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
2✔
854
                  const auto signature = signer.sign_message(message, null_rng);
2✔
855
                  result.require("signature is not empty", !signature.empty());
2✔
856

857
                  auto pk = key.public_key();
2✔
858
                  Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
2✔
859
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
4✔
860
               };
8✔
861

862
               // Create Key
863
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
864

865
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
866
               auto sk =
1✔
867
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
868
               result.require("key was created", sk != nullptr);
1✔
869
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
870
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
871
               result.test_no_throw("use key after creation", [&] { sign_verify_roundtrip(*sk); });
3✔
872

873
               // Make it persistent
874
               const auto handles = ctx->persistent_handles().size();
1✔
875
               const auto new_location = ctx->persist(*sk, authed_session, secret);
2✔
876
               result.test_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
2✔
877
               result.confirm("New location occupied", Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
878
               result.confirm("is persistent", sk->handles().has_persistent_handle());
2✔
879
               result.test_is_eq(
1✔
880
                  "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
881
               result.test_throws<Botan::Invalid_Argument>(
2✔
882
                  "Cannot persist to the same location", [&] { ctx->persist(*sk, authed_session, {}, new_location); });
2✔
883
               result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key",
2✔
884
                                                           [&] { ctx->persist(*sk, authed_session); });
2✔
885
               result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
3✔
886

887
               // Evict it
888
               ctx->evict(std::move(sk), authed_session);
3✔
889
               result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
890
               result.confirm("New location no longer occupied",
1✔
891
                              !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
892
            }),
3✔
893
   };
13✔
894
}
4✔
895

896
   #endif
897

898
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
899
template <typename KeyT>
900
auto load_persistent_ecc(Test::Result& result,
31✔
901
                         const std::shared_ptr<Botan::TPM2::Context>& ctx,
902
                         uint32_t persistent_key_id,
903
                         std::span<const uint8_t> auth_value,
904
                         const std::shared_ptr<Botan::TPM2::Session>& session) {
905
   // TODO: Merge with RSA
906
   const auto persistent_handles = ctx->persistent_handles();
31✔
907
   result.confirm(
62✔
908
      "Persistent key available",
909
      std::find(persistent_handles.begin(), persistent_handles.end(), persistent_key_id) != persistent_handles.end());
31✔
910

911
   auto key = [&] {
62✔
912
      if constexpr(std::same_as<Botan::TPM2::EC_PublicKey, KeyT>) {
913
         return KeyT::load_persistent(ctx, persistent_key_id, session);
10✔
914
      } else {
915
         return KeyT::load_persistent(ctx, persistent_key_id, auth_value, session);
52✔
916
      }
917
   }();
918

919
   result.test_eq("Algo", key->algo_name(), "ECDSA");
62✔
920
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
62✔
921
   return key;
31✔
922
}
31✔
923

924
std::vector<Test::Result> test_tpm2_ecc() {
1✔
925
   //TODO: Merge with RSA?
926
   auto ctx = get_tpm2_context(__func__);
1✔
927
   if(!ctx) {
1✔
928
      return {bail_out()};
×
929
   }
930

931
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
932

933
   const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
934
   const auto password = Test::options().tpm2_persistent_auth_value();
1✔
935

936
   return {
1✔
937
      CHECK("ECC and its helpers are supported",
938
            [&](Test::Result& result) {
1✔
939
               result.confirm("ECC is supported", ctx->supports_algorithm("ECC"));
2✔
940
               result.confirm("ECDSA is supported", ctx->supports_algorithm("ECDSA"));
2✔
941
            }),
1✔
942
         CHECK("Load the private key multiple times",
943
               [&](Test::Result& result) {
1✔
944
                  for(size_t i = 0; i < 20; ++i) {
21✔
945
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
20✔
946
                        result, ctx, persistent_key_id, password, session);
20✔
947
                     result.test_eq(Botan::fmt("Key loaded successfully ({})", i), key->algo_name(), "ECDSA");
40✔
948
                  }
20✔
949
               }),
1✔
950
         CHECK("Sign a message ECDSA",
951
               [&](Test::Result& result) {
1✔
952
                  auto key =
1✔
953
                     load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
954

955
                  Botan::Null_RNG null_rng;
1✔
956
                  Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "SHA-256");
1✔
957

958
                  // create a message that is larger than the TPM2 max buffer size
959
                  const auto message = [] {
3✔
960
                     std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
961
                     for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
962
                        msg[i] = static_cast<uint8_t>(i);
1,029✔
963
                     }
964
                     return msg;
1✔
965
                  }();
1✔
966
                  const auto signature = signer.sign_message(message, null_rng);
1✔
967
                  result.require("signature is not empty", !signature.empty());
1✔
968

969
                  auto public_key = key->public_key();
1✔
970
                  Botan::PK_Verifier verifier(*public_key, "SHA-256");
1✔
971
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
972
               }),
5✔
973
         CHECK("verify signature ECDSA",
974
               [&](Test::Result& result) {
1✔
975
                  auto sign = [&](std::span<const uint8_t> message) {
2✔
976
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
977
                        result, ctx, persistent_key_id, password, session);
1✔
978
                     Botan::Null_RNG null_rng;
1✔
979
                     Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "SHA-256");
1✔
980
                     return signer.sign_message(message, null_rng);
1✔
981
                  };
2✔
982

983
                  auto verify = [&](std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
4✔
984
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(
3✔
985
                        result, ctx, persistent_key_id, password, session);
3✔
986
                     Botan::PK_Verifier verifier(*key, "SHA-256");
3✔
987
                     return verifier.verify_message(msg, sig);
3✔
988
                  };
6✔
989

990
                  const auto message = Botan::hex_decode("baadcafe");
1✔
991
                  const auto signature = sign(message);
1✔
992

993
                  result.confirm("verification successful", verify(message, signature));
2✔
994

995
                  // change the message
996
                  auto rng = Test::new_rng(__func__);
1✔
997
                  auto mutated_message = Test::mutate_vec(message, *rng);
1✔
998
                  result.confirm("verification failed", !verify(mutated_message, signature));
2✔
999

1000
                  // ESAPI manipulates the session attributes internally and does
1001
                  // not reset them when an error occurs. A failure to validate a
1002
                  // signature is an error, and hence behaves surprisingly by
1003
                  // leaving the session attributes in an unexpected state.
1004
                  // The Botan wrapper has a workaround for this...
1005
                  const auto attrs = session->attributes();
1✔
1006
                  result.confirm("encrypt flag was not cleared by ESAPI", attrs.encrypt);
2✔
1007

1008
                  // orignal message again
1009
                  result.confirm("verification still successful", verify(message, signature));
2✔
1010
               }),
4✔
1011

1012
         CHECK("sign and verify multiple messages with the same Signer/Verifier objects",
1013
               [&](Test::Result& result) {
1✔
1014
                  const std::vector<std::vector<uint8_t>> messages = {
1✔
1015
                     Botan::hex_decode("BAADF00D"),
1016
                     Botan::hex_decode("DEADBEEF"),
1017
                     Botan::hex_decode("CAFEBABE"),
1018
                  };
4✔
1019

1020
                  // Generate a few signatures, then deallocate the private key.
1021
                  auto signatures = [&] {
3✔
1022
                     auto sk = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
1023
                        result, ctx, persistent_key_id, password, session);
1✔
1024
                     Botan::Null_RNG null_rng;
1✔
1025
                     Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "SHA-256");
1✔
1026
                     std::vector<std::vector<uint8_t>> sigs;
1✔
1027
                     sigs.reserve(messages.size());
1✔
1028
                     for(const auto& message : messages) {
4✔
1029
                        sigs.emplace_back(signer.sign_message(message, null_rng));
9✔
1030
                     }
1031
                     return sigs;
2✔
1032
                  }();
2✔
1033

1034
                  // verify via TPM 2.0
1035
                  auto pk =
1✔
1036
                     load_persistent_ecc<Botan::TPM2::EC_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
1037
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
1✔
1038
                  for(size_t i = 0; i < messages.size(); ++i) {
4✔
1039
                     result.confirm(Botan::fmt("verification successful ({})", i),
3✔
1040
                                    verifier.verify_message(messages[i], signatures[i]));
3✔
1041
                  }
1042

1043
                  // verify via software
1044
                  auto soft_pk =
1✔
1045
                     load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session)
1✔
1046
                        ->public_key();
1✔
1047
                  Botan::PK_Verifier soft_verifier(*soft_pk, "SHA-256");
1✔
1048
                  for(size_t i = 0; i < messages.size(); ++i) {
4✔
1049
                     result.confirm(Botan::fmt("software verification successful ({})", i),
3✔
1050
                                    soft_verifier.verify_message(messages[i], signatures[i]));
3✔
1051
                  }
1052
               }),
5✔
1053

1054
         CHECK("Wrong password is not accepted during ECDSA signing",
1055
               [&](Test::Result& result) {
1✔
1056
                  auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
1057
                     result, ctx, persistent_key_id, Botan::hex_decode("deadbeef"), session);
1✔
1058

1059
                  Botan::Null_RNG null_rng;
1✔
1060
                  Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "SHA-256");
1✔
1061

1062
                  const auto message = Botan::hex_decode("baadcafe");
1✔
1063
                  result.test_throws<Botan::TPM2::Error>("Fail with wrong password",
2✔
1064
                                                         [&] { signer.sign_message(message, null_rng); });
3✔
1065
               }),
2✔
1066

1067
      // SRK is an RSA key, so we can only test with the RSA adapter
1068
      #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
1069
         CHECK("Create a transient ECDSA key and sign/verify a message",
1070
               [&](Test::Result& result) {
1✔
1071
                  auto srk = ctx->storage_root_key({}, {});
2✔
1072
                  auto ecc_session_key =
1✔
1073
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
1074
                  auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
1075

1076
                  const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1077
                  auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1078
                     ctx, authed_session, secret, *srk, Botan::EC_Group::from_name("secp521r1"));
2✔
1079
                  auto pk = sk->public_key();
1✔
1080

1081
                  const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
1082

1083
                  Botan::Null_RNG null_rng;
1✔
1084
                  Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "SHA-256");
1✔
1085

1086
                  // create a message that is larger than the TPM2 max buffer size
1087
                  const auto message = [] {
3✔
1088
                     std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
1089
                     for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
1090
                        msg[i] = static_cast<uint8_t>(i);
1,029✔
1091
                     }
1092
                     return msg;
1✔
1093
                  }();
1✔
1094
                  const auto signature = signer.sign_message(message, null_rng);
1✔
1095
                  result.require("signature is not empty", !signature.empty());
1✔
1096

1097
                  auto public_key = sk->public_key();
1✔
1098
                  Botan::PK_Verifier verifier(*public_key, "SHA-256");
1✔
1099
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
1100
               }),
10✔
1101

1102
         CHECK("Create a new transient ECDSA key",
1103
               [&](Test::Result& result) {
1✔
1104
                  auto srk = ctx->storage_root_key({}, {});
2✔
1105
                  auto ecc_session_key =
1✔
1106
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
1107

1108
                  auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
1109

1110
                  const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1111

1112
                  auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1113
                     ctx, authed_session, secret, *srk, Botan::EC_Group::from_name("secp384r1"));
2✔
1114

1115
                  result.require("key was created", sk != nullptr);
1✔
1116
                  result.confirm("is transient", sk->handles().has_transient_handle());
2✔
1117
                  result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
1118

1119
                  const auto sk_blob = sk->raw_private_key_bits();
1✔
1120
                  const auto pk_blob = sk->raw_public_key_bits();
1✔
1121
                  const auto pk = sk->public_key();
1✔
1122

1123
                  result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
1124
                  result.confirm("public blob is not empty", !pk_blob.empty());
2✔
1125

1126
                  // Perform a round-trip sign/verify test with the new key pair
1127
                  std::vector<uint8_t> message = {'h', 'e', 'l', 'l', 'o'};
1✔
1128
                  Botan::Null_RNG null_rng;
1✔
1129
                  Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "SHA-256");
1✔
1130
                  const auto signature = signer.sign_message(message, null_rng);
1✔
1131
                  result.require("signature is not empty", !signature.empty());
1✔
1132

1133
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
1✔
1134
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
1135

1136
                  // Destruct the key and load it again from the encrypted blob
1137
                  sk.reset();
1✔
1138
                  auto sk_loaded =
1✔
1139
                     Botan::TPM2::PrivateKey::load_transient(ctx, secret, *srk, pk_blob, sk_blob, authed_session);
2✔
1140
                  result.require("key was loaded", sk_loaded != nullptr);
1✔
1141
                  result.test_eq("loaded key is ECDSA", sk_loaded->algo_name(), "ECDSA");
2✔
1142

1143
                  const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
1144
                  const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
1145

1146
                  result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
1147
                  result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
1148

1149
                  // Perform a round-trip sign/verify test with the new key pair
1150
                  std::vector<uint8_t> message_loaded = {'g', 'u', 't', 'e', 'n', ' ', 't', 'a', 'g'};
1✔
1151
                  Botan::PK_Signer signer_loaded(*sk_loaded, null_rng /* TPM takes care of this */, "SHA-256");
1✔
1152
                  const auto signature_loaded = signer_loaded.sign_message(message_loaded, null_rng);
1✔
1153
                  result.require("Next signature is not empty", !signature_loaded.empty());
1✔
1154
                  result.confirm("Existing verifier can validate signature",
2✔
1155
                                 verifier.verify_message(message_loaded, signature_loaded));
1✔
1156

1157
                  // Load the public portion of the key
1158
                  auto pk_loaded = Botan::TPM2::PublicKey::load_transient(ctx, pk_blob, {});
2✔
1159
                  result.require("public key was loaded", pk_loaded != nullptr);
1✔
1160

1161
                  Botan::PK_Verifier verifier_loaded(*pk_loaded, "SHA-256");
1✔
1162
                  result.confirm("TPM-verified signature is valid",
2✔
1163
                                 verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
1164
               }),
15✔
1165

1166
         CHECK(
1167
            "Make a transient ECDSA key persistent then remove it again",
1168
            [&](Test::Result& result) {
1✔
1169
               auto srk = ctx->storage_root_key({}, {});
2✔
1170
               auto ecc_session_key = Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
1171

1172
               auto sign_verify_roundtrip = [&](const Botan::TPM2::PrivateKey& key) {
3✔
1173
                  std::vector<uint8_t> message = {'h', 'e', 'l', 'l', 'o'};
2✔
1174
                  Botan::Null_RNG null_rng;
2✔
1175
                  Botan::PK_Signer signer(key, null_rng /* TPM takes care of this */, "SHA-256");
2✔
1176
                  const auto signature = signer.sign_message(message, null_rng);
2✔
1177
                  result.require("signature is not empty", !signature.empty());
2✔
1178

1179
                  auto pk = key.public_key();
2✔
1180
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
2✔
1181
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
4✔
1182
               };
8✔
1183

1184
               // Create Key
1185
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
1186

1187
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1188
               auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1189
                  ctx, authed_session, secret, *srk, Botan::EC_Group::from_name("secp192r1"));
2✔
1190
               result.require("key was created", sk != nullptr);
1✔
1191
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
1192
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
1193
               result.test_no_throw("use key after creation", [&] { sign_verify_roundtrip(*sk); });
3✔
1194

1195
               // Make it persistent
1196
               const auto handles = ctx->persistent_handles().size();
1✔
1197
               const auto new_location = ctx->persist(*sk, authed_session, secret);
2✔
1198
               result.test_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
2✔
1199
               result.confirm("New location occupied", Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
1200
               result.confirm("is persistent", sk->handles().has_persistent_handle());
2✔
1201
               result.test_is_eq(
1✔
1202
                  "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
1203
               result.test_throws<Botan::Invalid_Argument>(
2✔
1204
                  "Cannot persist to the same location", [&] { ctx->persist(*sk, authed_session, {}, new_location); });
2✔
1205
               result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key",
2✔
1206
                                                           [&] { ctx->persist(*sk, authed_session); });
2✔
1207
               result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
3✔
1208

1209
               // Evict it
1210
               ctx->evict(std::move(sk), authed_session);
3✔
1211
               result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
1212
               result.confirm("New location no longer occupied",
1✔
1213
                              !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
1214
            }),
4✔
1215
      #endif
1216

1217
         CHECK("Read a software public key from a TPM serialization", [&](Test::Result& result) {
1✔
1218
            auto pk = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
1219
            result.test_no_throw("Botan can read serialized ECC public key", [&] {
2✔
1220
               auto pk_sw = Botan::ECDSA_PublicKey(pk->algorithm_identifier(), pk->public_key_bits());
2✔
1221
            });
1✔
1222

1223
            auto sk =
1✔
1224
               load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
1225
            result.test_no_throw("Botan can read serialized public key from ECC private key", [&] {
3✔
1226
               auto sk_sw = Botan::ECDSA_PublicKey(sk->algorithm_identifier(), sk->public_key_bits());
2✔
1227
            });
1✔
1228
         }),
2✔
1229
   };
11✔
1230
}
4✔
1231
   #endif
1232

1233
std::vector<Test::Result> test_tpm2_hash() {
1✔
1234
   auto ctx = get_tpm2_context(__func__);
1✔
1235
   if(!ctx) {
1✔
1236
      return {bail_out()};
×
1237
   }
1238

1239
   auto test = [&](Test::Result& result, std::string_view algo) {
8✔
1240
      auto tpm_hash = [&]() -> std::unique_ptr<Botan::TPM2::HashFunction> {
21✔
1241
         try {
7✔
1242
            return std::make_unique<Botan::TPM2::HashFunction>(
7✔
1243
               ctx, algo, ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
7✔
1244
         } catch(const Botan::Lookup_Error&) {
3✔
1245
            return {};
3✔
1246
         }
3✔
1247
      }();
7✔
1248
      auto soft_hash = Botan::HashFunction::create(algo);
7✔
1249

1250
      if(!tpm_hash) {
7✔
1251
         result.test_note(Botan::fmt("Skipping {}, TPM 2.0 does not support it", algo));
3✔
1252
         return;
3✔
1253
      }
1254

1255
      if(!soft_hash) {
4✔
1256
         result.test_note(Botan::fmt("Skipping {}, no software equivalent available", algo));
×
1257
         return;
×
1258
      }
1259

1260
      result.test_eq("Name", tpm_hash->name(), soft_hash->name());
8✔
1261
      result.test_eq("Output length", tpm_hash->output_length(), soft_hash->output_length());
4✔
1262

1263
      // multiple update calls
1264
      tpm_hash->update("Hello, ");
4✔
1265
      tpm_hash->update("world!");
4✔
1266
      result.test_eq("digest (multi-update)", tpm_hash->final(), soft_hash->process("Hello, world!"));
12✔
1267

1268
      // single process call
1269
      result.test_eq("digest (single-process)", tpm_hash->process("Hallo, Welt."), soft_hash->process("Hallo, Welt."));
12✔
1270

1271
      // create a message that is larger than the TPM2 max buffer size
1272
      const auto long_message = [] {
×
1273
         std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
4✔
1274
         for(size_t i = 0; i < msg.size(); ++i) {
4,120✔
1275
            msg[i] = static_cast<uint8_t>(i);
4,116✔
1276
         }
1277
         return msg;
4✔
1278
      }();
4✔
1279

1280
      tpm_hash->update(long_message);
4✔
1281
      result.test_eq("digest (long msg via update)", tpm_hash->final(), soft_hash->process(long_message));
12✔
1282
      result.test_eq(
12✔
1283
         "digest (long msg via process)", tpm_hash->process(long_message), soft_hash->process(long_message));
8✔
1284

1285
      // test clear
1286
      tpm_hash->update("Hello");
4✔
1287
      tpm_hash->clear();
4✔
1288
      tpm_hash->update("Bonjour");
4✔
1289
      result.test_eq("digest (clear)", tpm_hash->final(), soft_hash->process("Bonjour"));
12✔
1290

1291
      // new_object
1292
      auto new_tpm_hash = tpm_hash->new_object();
4✔
1293
      result.test_eq("Name (new_object)", new_tpm_hash->name(), tpm_hash->name());
8✔
1294
      result.test_eq("Output length (new_object)", new_tpm_hash->output_length(), tpm_hash->output_length());
4✔
1295
      result.test_eq("digest (new object)",
12✔
1296
                     new_tpm_hash->process("Salut tout le monde!"),
8✔
1297
                     soft_hash->process("Salut tout le monde!"));
8✔
1298
   };
15✔
1299

1300
   return {
1✔
1301
      CHECK("Hashes are supported",
1302
            [&](Test::Result& result) {
1✔
1303
               result.confirm("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
2✔
1304
               result.confirm("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
2✔
1305
               result.confirm("SHA-384 is supported", ctx->supports_algorithm("SHA-384"));
2✔
1306
               result.confirm("SHA-512 is supported", ctx->supports_algorithm("SHA-512"));
2✔
1307
            }),
1✔
1308

1309
      CHECK("SHA-1", [&](Test::Result& result) { test(result, "SHA-1"); }),
1✔
1310
      CHECK("SHA-256", [&](Test::Result& result) { test(result, "SHA-256"); }),
1✔
1311
      CHECK("SHA-384", [&](Test::Result& result) { test(result, "SHA-384"); }),
1✔
1312
      CHECK("SHA-512", [&](Test::Result& result) { test(result, "SHA-512"); }),
1✔
1313
      CHECK("SHA-3(256)", [&](Test::Result& result) { test(result, "SHA-3(256)"); }),
1✔
1314
      CHECK("SHA-3(384)", [&](Test::Result& result) { test(result, "SHA-3(384)"); }),
1✔
1315
      CHECK("SHA-3(512)", [&](Test::Result& result) { test(result, "SHA-3(512)"); }),
1✔
1316

1317
      CHECK("lookup error",
1318
            [&](Test::Result& result) {
1✔
1319
               result.test_throws<Botan::Lookup_Error>(
2✔
1320
                  "Lookup error", [&] { [[maybe_unused]] auto _ = Botan::TPM2::HashFunction(ctx, "MD-5"); });
7✔
1321
            }),
1✔
1322

1323
      CHECK("copy_state is not implemented",
1324
            [&](Test::Result& result) {
1✔
1325
               auto tpm_hash = Botan::TPM2::HashFunction(ctx, "SHA-256");
3✔
1326
               result.test_throws<Botan::Not_Implemented>("TPM2 hash does not support copy_state",
2✔
1327
                                                          [&] { [[maybe_unused]] auto _ = tpm_hash.copy_state(); });
2✔
1328
            }),
1✔
1329

1330
      CHECK("validation ticket",
1331
            [&](Test::Result& result) {
1✔
1332
               // using the NULL hierarchy essentially disables the validation ticket
1333
               auto tpm_hash_null = Botan::TPM2::HashFunction(
1✔
1334
                  ctx, "SHA-256", ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1335
               tpm_hash_null.update("Hola mundo!");
1✔
1336
               const auto [digest_null, ticket_null] = tpm_hash_null.final_with_ticket();
1✔
1337
               result.require("digest is set", digest_null != nullptr);
1✔
1338
               result.require("ticket is set", ticket_null != nullptr);
1✔
1339
               result.confirm("ticket is empty", ticket_null->digest.size == 0);
2✔
1340

1341
               // using the OWNER hierarchy (for instance) enables the validation ticket
1342
               auto tpm_hash_owner = Botan::TPM2::HashFunction(
1✔
1343
                  ctx, "SHA-256", ESYS_TR_RH_OWNER, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1344
               tpm_hash_owner.update("Hola mundo!");
1✔
1345
               const auto [digest_owner, ticket_owner] = tpm_hash_owner.final_with_ticket();
1✔
1346
               result.require("digest is set", digest_owner != nullptr);
1✔
1347
               result.require("ticket is set", ticket_owner != nullptr);
1✔
1348
               result.confirm("ticket is not empty", ticket_owner->digest.size > 0);
2✔
1349

1350
               const auto digest_vec = Botan::TPM2::copy_into<Botan::secure_vector<uint8_t>>(*digest_owner);
1✔
1351
               result.test_eq("digest",
2✔
1352
                              digest_vec,
1353
                              Botan::hex_decode("1e479f4d871e59e9054aad62105a259726801d5f494acbfcd40591c82f9b3136"));
1✔
1354

1355
               result.test_eq("digests are the same, regardless of ticket",
2✔
1356
                              Botan::TPM2::copy_into<std::vector<uint8_t>>(*digest_null),
2✔
1357
                              digest_vec);
1358
            }),
1✔
1359
   };
12✔
1360
}
2✔
1361

1362
}  // namespace
1363

1364
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_props", test_tpm2_properties);
1365
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ctx", test_tpm2_context);
1366
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_external_ctx", test_external_tpm2_context);
1367
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_sessions", test_tpm2_sessions);
1368
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rng", test_tpm2_rng);
1369
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
1370
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rsa", test_tpm2_rsa);
1371
   #endif
1372
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
1373
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ecc", test_tpm2_ecc);
1374
   #endif
1375
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_hash", test_tpm2_hash);
1376

1377
#endif
1378

1379
}  // namespace Botan_Tests
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