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

randombit / botan / 18444482102

12 Oct 2025 01:15PM UTC coverage: 90.666% (-0.005%) from 90.671%
18444482102

Pull #5118

github

web-flow
Merge 63a425379 into ea34eb789
Pull Request #5118: Resolve FIXME: Enforce explicit conversions in TPM2 Session Header File

100395 of 110731 relevant lines covered (90.67%)

12283703.21 hits per line

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

94.85
/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/mem_utils.h>
13
#include <botan/internal/stl_util.h>
14

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

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

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

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

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

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

41
namespace Botan_Tests {
42

43
#if defined(BOTAN_HAS_TPM2)
44
namespace {
45

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

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

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

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

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

72
   return ctx;
7✔
73
}
14✔
74

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

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

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

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

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

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

103
      // NOLINTNEXTLINE(*-explicit-conversions) FIXME
104
      constexpr operator ESYS_TR() const { return m_handle; }
3✔
105
};
106

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

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

126
   TSS2_RC rc = 0;
3✔
127
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
3✔
128
   std::unique_ptr<ESYS_CONTEXT, esys_context_liberator> esys_ctx;
3✔
129

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

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

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

147
   return esys_ctx;
3✔
148
}
9✔
149

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

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

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

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

176
   return true;
177
}
178

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

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

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

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

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

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

226
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
227

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

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

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

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

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

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

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

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

300
      return {TR{esys_ctx, session}, rc};
4✔
301
   };
302

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

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

319
               {
1✔
320
                  // Do some TPM2-stuff via the Botan wrappers
321

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

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

329
                  // All Botan-wrapped things go out of scope...
330
               }
2✔
331

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

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

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

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

355
                     ctx->use_botan_crypto_backend(Test::new_rng("tpm2_backend_context_test"));
3✔
356
                  }
×
357

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

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

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

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

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

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

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

392
            auto cb_state = Botan::TPM2::use_botan_crypto_backend(esys_ctx.get(), Test::new_rng("tpm2_crypto_backend"));
3✔
393

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

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

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

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

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

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

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

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

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

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

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

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

470
   auto rng = Botan::TPM2::RandomNumberGenerator(
1✔
471
      ctx, Botan::TPM2::SessionBundle(Botan::TPM2::Session::unauthenticated_session(ctx)));
3✔
472

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

480
               result.test_no_throw("Clear", [&] { rng.clear(); });
2✔
481
            }),
1✔
482

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

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

493
               std::array<uint8_t, 256> buf3 = {};
1✔
494
               rng.randomize(buf3);
1✔
495
               result.confirm("Is at least not 0 (256)", not_zero_64(buf3));
2✔
496
            }),
1✔
497

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

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

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

515
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
516

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

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

536
   result.test_eq("Algo", key->algo_name(), "RSA" /* TODO ECC support*/);
64✔
537
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
64✔
538
   return key;
32✔
539
}
32✔
540

541
std::vector<Test::Result> test_tpm2_rsa() {
1✔
542
   auto ctx = get_tpm2_context(__func__);
1✔
543
   if(!ctx) {
1✔
544
      return {bail_out()};
×
545
   }
546

547
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
548

549
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
550
   const auto password = Botan::as_span_of_bytes(Test::options().tpm2_persistent_auth_value());
1✔
551

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

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

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

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

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

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

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

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

614
               const auto message = Botan::hex_decode("baadcafe");
1✔
615
               const auto signature = sign(message);
1✔
616

617
               result.confirm("verification successful", verify(message, signature));
2✔
618

619
               // change the message
620
               auto rng = Test::new_rng("tpm2_verify_message");
1✔
621
               auto mutated_message = Test::mutate_vec(message, *rng);
1✔
622
               result.confirm("verification failed", !verify(mutated_message, signature));
2✔
623

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

632
               // original message again
633
               result.confirm("verification still successful", verify(message, signature));
2✔
634
            }),
4✔
635

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

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

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

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

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

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

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

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

694
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
695

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

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

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

712
               const auto plaintext = Botan::hex_decode("feedface");
1✔
713

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

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

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

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

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

742
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
743

744
               // encrypt a message using the TPM's public key
745
               auto rng = Test::new_rng("tpm2_transient_key_encrypt");
1✔
746
               Botan::PK_Encryptor_EME enc(*pk, *rng, "OAEP(SHA-256)");
1✔
747
               const auto ciphertext = enc.encrypt(plaintext, *rng);
1✔
748

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

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

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

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

775
      CHECK("Create a new transient key",
776
            [&](Test::Result& result) {
1✔
777
               auto srk = ctx->storage_root_key({}, {});
3✔
778

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

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

783
               auto sk = Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(
1✔
784
                  ctx, Botan::TPM2::SessionBundle(authed_session), secret, *srk, 2048);
2✔
785

786
               result.require("key was created", sk != nullptr);
1✔
787
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
788
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
789

790
               const auto sk_blob = sk->raw_private_key_bits();
1✔
791
               const auto pk_blob = sk->raw_public_key_bits();
1✔
792
               const auto pk = sk->public_key();
1✔
793

794
               result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
795
               result.confirm("public blob is not empty", !pk_blob.empty());
2✔
796

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

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

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

814
               const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
815
               const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
816

817
               result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
818
               result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
819

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

828
               // Load the public portion of the key
829
               auto pk_loaded = Botan::TPM2::PublicKey::load_transient(ctx, pk_blob, {});
3✔
830
               result.require("public key was loaded", pk_loaded != nullptr);
1✔
831

832
               Botan::PK_Verifier verifier_loaded(*pk_loaded, "PSS(SHA-256)");
1✔
833
               result.confirm("TPM-verified signature is valid",
2✔
834
                              verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
835

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

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

850
      CHECK("Make a transient key persistent then remove it again",
851
            [&](Test::Result& result) {
1✔
852
               auto srk = ctx->storage_root_key({}, {});
3✔
853

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

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

866
               // Create Key
867
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
868

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

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

893
               // Evict it
894
               ctx->evict(std::move(sk), Botan::TPM2::SessionBundle(authed_session));
3✔
895
               result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
896
               result.confirm("New location no longer occupied",
1✔
897
                              !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
898
            }),
3✔
899
   };
13✔
900
}
3✔
901

902
   #endif
903

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

917
   auto key = [&] {
62✔
918
      if constexpr(std::same_as<Botan::TPM2::EC_PublicKey, KeyT>) {
919
         return KeyT::load_persistent(ctx, persistent_key_id, Botan::TPM2::SessionBundle(session));
10✔
920
      } else {
921
         return KeyT::load_persistent(ctx, persistent_key_id, auth_value, Botan::TPM2::SessionBundle(session));
52✔
922
      }
923
   }();
924

925
   result.test_eq("Algo", key->algo_name(), "ECDSA");
62✔
926
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
62✔
927
   return key;
31✔
928
}
31✔
929

930
std::vector<Test::Result> test_tpm2_ecc() {
1✔
931
   //TODO: Merge with RSA?
932
   auto ctx = get_tpm2_context(__func__);
1✔
933
   if(!ctx) {
1✔
934
      return {bail_out()};
×
935
   }
936

937
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
938

939
   const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
940
   const auto password = Botan::as_span_of_bytes(Test::options().tpm2_persistent_auth_value());
1✔
941

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

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

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

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

989
                  auto verify = [&](std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
4✔
990
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(
3✔
991
                        result, ctx, persistent_key_id, password, session);
3✔
992
                     Botan::PK_Verifier verifier(*key, "SHA-256");
3✔
993
                     return verifier.verify_message(msg, sig);
3✔
994
                  };
6✔
995

996
                  const auto message = Botan::hex_decode("baadcafe");
1✔
997
                  const auto signature = sign(message);
1✔
998

999
                  result.confirm("verification successful", verify(message, signature));
2✔
1000

1001
                  // change the message
1002
                  auto rng = Test::new_rng("tpm2_verify_ecdsa");
1✔
1003
                  auto mutated_message = Test::mutate_vec(message, *rng);
1✔
1004
                  result.confirm("verification failed", !verify(mutated_message, signature));
2✔
1005

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

1014
                  // original message again
1015
                  result.confirm("verification still successful", verify(message, signature));
2✔
1016
               }),
4✔
1017

1018
         CHECK("sign and verify multiple messages with the same Signer/Verifier objects",
1019
               [&](Test::Result& result) {
1✔
1020
                  const std::vector<std::vector<uint8_t>> messages = {
1✔
1021
                     Botan::hex_decode("BAADF00D"),
1022
                     Botan::hex_decode("DEADBEEF"),
1023
                     Botan::hex_decode("CAFEBABE"),
1024
                  };
4✔
1025

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

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

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

1060
         CHECK("Wrong password is not accepted during ECDSA signing",
1061
               [&](Test::Result& result) {
1✔
1062
                  auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
1063
                     result, ctx, persistent_key_id, Botan::hex_decode("deadbeef"), session);
1✔
1064

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

1068
                  const auto message = Botan::hex_decode("baadcafe");
1✔
1069
                  result.test_throws<Botan::TPM2::Error>("Fail with wrong password",
2✔
1070
                                                         [&] { signer.sign_message(message, null_rng); });
3✔
1071
               }),
2✔
1072

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

1082
                  const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1083
                  auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1084
                     ctx,
1085
                     Botan::TPM2::SessionBundle(authed_session),
2✔
1086
                     secret,
1087
                     *srk,
1✔
1088
                     Botan::EC_Group::from_name("secp521r1"));
2✔
1089
                  auto pk = sk->public_key();
1✔
1090

1091
                  const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
1092

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

1096
                  // create a message that is larger than the TPM2 max buffer size
1097
                  const auto message = [] {
3✔
1098
                     std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
1099
                     for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
1100
                        msg[i] = static_cast<uint8_t>(i);
1,029✔
1101
                     }
1102
                     return msg;
1✔
1103
                  }();
1✔
1104
                  const auto signature = signer.sign_message(message, null_rng);
1✔
1105
                  result.require("signature is not empty", !signature.empty());
1✔
1106

1107
                  auto public_key = sk->public_key();
1✔
1108
                  Botan::PK_Verifier verifier(*public_key, "SHA-256");
1✔
1109
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
1110
               }),
10✔
1111

1112
         CHECK("Create a new transient ECDSA key",
1113
               [&](Test::Result& result) {
1✔
1114
                  auto srk = ctx->storage_root_key({}, {});
3✔
1115
                  auto ecc_session_key =
1✔
1116
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
3✔
1117

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

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

1122
                  auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1123
                     ctx,
1124
                     Botan::TPM2::SessionBundle(authed_session),
2✔
1125
                     secret,
1126
                     *srk,
1✔
1127
                     Botan::EC_Group::from_name("secp384r1"));
2✔
1128

1129
                  result.require("key was created", sk != nullptr);
1✔
1130
                  result.confirm("is transient", sk->handles().has_transient_handle());
2✔
1131
                  result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
1132

1133
                  const auto sk_blob = sk->raw_private_key_bits();
1✔
1134
                  const auto pk_blob = sk->raw_public_key_bits();
1✔
1135
                  const auto pk = sk->public_key();
1✔
1136

1137
                  result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
1138
                  result.confirm("public blob is not empty", !pk_blob.empty());
2✔
1139

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

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

1150
                  // Destruct the key and load it again from the encrypted blob
1151
                  sk.reset();
1✔
1152
                  auto sk_loaded = Botan::TPM2::PrivateKey::load_transient(
1✔
1153
                     ctx, secret, *srk, pk_blob, sk_blob, Botan::TPM2::SessionBundle(authed_session));
2✔
1154
                  result.require("key was loaded", sk_loaded != nullptr);
1✔
1155
                  result.test_eq("loaded key is ECDSA", sk_loaded->algo_name(), "ECDSA");
2✔
1156

1157
                  const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
1158
                  const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
1159

1160
                  result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
1161
                  result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
1162

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

1171
                  // Load the public portion of the key
1172
                  auto pk_loaded = Botan::TPM2::PublicKey::load_transient(ctx, pk_blob, {});
3✔
1173
                  result.require("public key was loaded", pk_loaded != nullptr);
1✔
1174

1175
                  Botan::PK_Verifier verifier_loaded(*pk_loaded, "SHA-256");
1✔
1176
                  result.confirm("TPM-verified signature is valid",
2✔
1177
                                 verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
1178
               }),
15✔
1179

1180
         CHECK("Make a transient ECDSA key persistent then remove it again",
1181
               [&](Test::Result& result) {
1✔
1182
                  auto srk = ctx->storage_root_key({}, {});
3✔
1183
                  auto ecc_session_key =
1✔
1184
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
3✔
1185

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

1193
                     auto pk = key.public_key();
2✔
1194
                     Botan::PK_Verifier verifier(*pk, "SHA-256");
2✔
1195
                     result.confirm("Signature is valid", verifier.verify_message(message, signature));
4✔
1196
                  };
8✔
1197

1198
                  // Create Key
1199
                  auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
1200

1201
                  const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1202
                  auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1203
                     ctx,
1204
                     Botan::TPM2::SessionBundle(authed_session),
2✔
1205
                     secret,
1206
                     *srk,
1✔
1207
                     Botan::EC_Group::from_name("secp192r1"));
2✔
1208
                  result.require("key was created", sk != nullptr);
1✔
1209
                  result.confirm("is transient", sk->handles().has_transient_handle());
2✔
1210
                  result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
1211
                  result.test_no_throw("use key after creation", [&] { sign_verify_roundtrip(*sk); });
3✔
1212

1213
                  // Make it persistent
1214
                  const auto handles = ctx->persistent_handles().size();
1✔
1215
                  const auto new_location = ctx->persist(*sk, Botan::TPM2::SessionBundle(authed_session), secret);
2✔
1216
                  result.test_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
2✔
1217
                  result.confirm("New location occupied", Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
1218
                  result.confirm("is persistent", sk->handles().has_persistent_handle());
2✔
1219
                  result.test_is_eq(
1✔
1220
                     "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
1221
                  result.test_throws<Botan::Invalid_Argument>("Cannot persist to the same location", [&] {
2✔
1222
                     ctx->persist(*sk, Botan::TPM2::SessionBundle(authed_session), {}, new_location);
1✔
1223
                  });
×
1224
                  result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key", [&] {
2✔
1225
                     ctx->persist(*sk, Botan::TPM2::SessionBundle(authed_session));
1✔
1226
                  });
×
1227
                  result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
3✔
1228

1229
                  // Evict it
1230
                  ctx->evict(std::move(sk), Botan::TPM2::SessionBundle(authed_session));
3✔
1231
                  result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
1232
                  result.confirm("New location no longer occupied",
1✔
1233
                                 !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
1234
               }),
4✔
1235
      #endif
1236

1237
         CHECK("Read a software public key from a TPM serialization", [&](Test::Result& result) {
1✔
1238
            auto pk = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
1239
            result.test_no_throw("Botan can read serialized ECC public key", [&] {
2✔
1240
               std::ignore = Botan::ECDSA_PublicKey(pk->algorithm_identifier(), pk->public_key_bits());
1✔
1241
            });
1✔
1242

1243
            auto sk =
1✔
1244
               load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
1245
            result.test_no_throw("Botan can read serialized public key from ECC private key", [&] {
3✔
1246
               std::ignore = Botan::ECDSA_PublicKey(sk->algorithm_identifier(), sk->public_key_bits());
1✔
1247
            });
1✔
1248
         }),
2✔
1249
   };
11✔
1250
}
3✔
1251
   #endif
1252

1253
std::vector<Test::Result> test_tpm2_hash() {
1✔
1254
   auto ctx = get_tpm2_context(__func__);
1✔
1255
   if(!ctx) {
1✔
1256
      return {bail_out()};
×
1257
   }
1258

1259
   auto test = [&](Test::Result& result, std::string_view algo) {
8✔
1260
      auto tpm_hash = [&]() -> std::unique_ptr<Botan::TPM2::HashFunction> {
21✔
1261
         try {
7✔
1262
            return std::make_unique<Botan::TPM2::HashFunction>(
7✔
1263
               ctx,
1264
               algo,
1265
               ESYS_TR_RH_NULL,
16✔
1266
               Botan::TPM2::SessionBundle(Botan::TPM2::Session::unauthenticated_session(ctx)));
14✔
1267
         } catch(const Botan::Lookup_Error&) {
3✔
1268
            return {};
3✔
1269
         }
3✔
1270
      }();
7✔
1271
      auto soft_hash = Botan::HashFunction::create(algo);
7✔
1272

1273
      if(!tpm_hash) {
7✔
1274
         result.test_note(Botan::fmt("Skipping {}, TPM 2.0 does not support it", algo));
3✔
1275
         return;
3✔
1276
      }
1277

1278
      if(!soft_hash) {
4✔
1279
         result.test_note(Botan::fmt("Skipping {}, no software equivalent available", algo));
×
1280
         return;
×
1281
      }
1282

1283
      result.test_eq("Name", tpm_hash->name(), soft_hash->name());
8✔
1284
      result.test_eq("Output length", tpm_hash->output_length(), soft_hash->output_length());
4✔
1285

1286
      // multiple update calls
1287
      tpm_hash->update("Hello, ");
4✔
1288
      tpm_hash->update("world!");
4✔
1289
      result.test_eq("digest (multi-update)", tpm_hash->final(), soft_hash->process("Hello, world!"));
16✔
1290

1291
      // single process call
1292
      result.test_eq("digest (single-process)", tpm_hash->process("Hallo, Welt."), soft_hash->process("Hallo, Welt."));
20✔
1293

1294
      // create a message that is larger than the TPM2 max buffer size
1295
      const auto long_message = [] {
×
1296
         std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
4✔
1297
         for(size_t i = 0; i < msg.size(); ++i) {
4,120✔
1298
            msg[i] = static_cast<uint8_t>(i);
4,116✔
1299
         }
1300
         return msg;
4✔
1301
      }();
4✔
1302

1303
      tpm_hash->update(long_message);
4✔
1304
      result.test_eq("digest (long msg via update)", tpm_hash->final(), soft_hash->process(long_message));
12✔
1305
      result.test_eq(
8✔
1306
         "digest (long msg via process)", tpm_hash->process(long_message), soft_hash->process(long_message));
8✔
1307

1308
      // test clear
1309
      tpm_hash->update("Hello");
4✔
1310
      tpm_hash->clear();
4✔
1311
      tpm_hash->update("Bonjour");
4✔
1312
      result.test_eq("digest (clear)", tpm_hash->final(), soft_hash->process("Bonjour"));
16✔
1313

1314
      // new_object
1315
      auto new_tpm_hash = tpm_hash->new_object();
4✔
1316
      result.test_eq("Name (new_object)", new_tpm_hash->name(), tpm_hash->name());
8✔
1317
      result.test_eq("Output length (new_object)", new_tpm_hash->output_length(), tpm_hash->output_length());
4✔
1318
      result.test_eq("digest (new object)",
16✔
1319
                     new_tpm_hash->process("Salut tout le monde!"),
12✔
1320
                     soft_hash->process("Salut tout le monde!"));
12✔
1321
   };
15✔
1322

1323
   return {
1✔
1324
      CHECK("Hashes are supported",
1325
            [&](Test::Result& result) {
1✔
1326
               result.confirm("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
2✔
1327
               result.confirm("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
2✔
1328
               result.confirm("SHA-384 is supported", ctx->supports_algorithm("SHA-384"));
2✔
1329
               result.confirm("SHA-512 is supported", ctx->supports_algorithm("SHA-512"));
2✔
1330
            }),
1✔
1331

1332
      CHECK("SHA-1", [&](Test::Result& result) { test(result, "SHA-1"); }),
1✔
1333
      CHECK("SHA-256", [&](Test::Result& result) { test(result, "SHA-256"); }),
1✔
1334
      CHECK("SHA-384", [&](Test::Result& result) { test(result, "SHA-384"); }),
1✔
1335
      CHECK("SHA-512", [&](Test::Result& result) { test(result, "SHA-512"); }),
1✔
1336
      CHECK("SHA-3(256)", [&](Test::Result& result) { test(result, "SHA-3(256)"); }),
1✔
1337
      CHECK("SHA-3(384)", [&](Test::Result& result) { test(result, "SHA-3(384)"); }),
1✔
1338
      CHECK("SHA-3(512)", [&](Test::Result& result) { test(result, "SHA-3(512)"); }),
1✔
1339

1340
      CHECK("lookup error",
1341
            [&](Test::Result& result) {
1✔
1342
               result.test_throws<Botan::Lookup_Error>(
2✔
1343
                  "Lookup error", [&] { [[maybe_unused]] auto _ = Botan::TPM2::HashFunction(ctx, "MD-5"); });
9✔
1344
            }),
1✔
1345

1346
      CHECK("copy_state is not implemented",
1347
            [&](Test::Result& result) {
1✔
1348
               auto tpm_hash = Botan::TPM2::HashFunction(ctx, "SHA-256");
4✔
1349
               result.test_throws<Botan::Not_Implemented>("TPM2 hash does not support copy_state",
2✔
1350
                                                          [&] { [[maybe_unused]] auto _ = tpm_hash.copy_state(); });
2✔
1351
            }),
1✔
1352

1353
      CHECK("validation ticket",
1354
            [&](Test::Result& result) {
1✔
1355
               // using the NULL hierarchy essentially disables the validation ticket
1356
               auto tpm_hash_null = Botan::TPM2::HashFunction(
1✔
1357
                  ctx,
1358
                  "SHA-256",
1359
                  ESYS_TR_RH_NULL,
1360
                  Botan::TPM2::SessionBundle(Botan::TPM2::Session::unauthenticated_session(ctx)));
3✔
1361
               tpm_hash_null.update("Hola mundo!");
1✔
1362
               const auto [digest_null, ticket_null] = tpm_hash_null.final_with_ticket();
1✔
1363
               result.require("digest is set", digest_null != nullptr);
1✔
1364
               result.require("ticket is set", ticket_null != nullptr);
1✔
1365
               result.confirm("ticket is empty", ticket_null->digest.size == 0);
2✔
1366

1367
               // using the OWNER hierarchy (for instance) enables the validation ticket
1368
               auto tpm_hash_owner = Botan::TPM2::HashFunction(
1✔
1369
                  ctx,
1370
                  "SHA-256",
1371
                  ESYS_TR_RH_OWNER,
1372
                  Botan::TPM2::SessionBundle(Botan::TPM2::Session::unauthenticated_session(ctx)));
3✔
1373
               tpm_hash_owner.update("Hola mundo!");
1✔
1374
               const auto [digest_owner, ticket_owner] = tpm_hash_owner.final_with_ticket();
1✔
1375
               result.require("digest is set", digest_owner != nullptr);
1✔
1376
               result.require("ticket is set", ticket_owner != nullptr);
1✔
1377
               result.confirm("ticket is not empty", ticket_owner->digest.size > 0);
2✔
1378

1379
               const auto digest_vec = Botan::TPM2::copy_into<Botan::secure_vector<uint8_t>>(*digest_owner);
1✔
1380
               result.test_eq("digest",
2✔
1381
                              digest_vec,
1382
                              Botan::hex_decode("1e479f4d871e59e9054aad62105a259726801d5f494acbfcd40591c82f9b3136"));
1✔
1383

1384
               result.test_eq("digests are the same, regardless of ticket",
2✔
1385
                              Botan::TPM2::copy_into<std::vector<uint8_t>>(*digest_null),
2✔
1386
                              digest_vec);
1387
            }),
1✔
1388
   };
12✔
1389
}
2✔
1390

1391
}  // namespace
1392

1393
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_props", test_tpm2_properties);
1394
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ctx", test_tpm2_context);
1395
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_external_ctx", test_external_tpm2_context);
1396
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_sessions", test_tpm2_sessions);
1397
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rng", test_tpm2_rng);
1398
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
1399
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rsa", test_tpm2_rsa);
1400
   #endif
1401
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
1402
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ecc", test_tpm2_ecc);
1403
   #endif
1404
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_hash", test_tpm2_hash);
1405

1406
#endif
1407

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

© 2026 Coveralls, Inc