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

randombit / botan / 26864473078

02 Jun 2026 07:58PM UTC coverage: 89.389% (+0.02%) from 89.37%
26864473078

push

github

web-flow
Merge pull request #5639 from randombit/jack/sm4-hwaes-ks

Add hwaes hook for SM4 key schedule

110434 of 123543 relevant lines covered (89.39%)

11152828.24 hits per line

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

95.02
/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
#if defined(BOTAN_HAS_TPM2)
11
   #include <botan/hex.h>
12
   #include <botan/pubkey.h>
13
   #include <botan/tpm2_key.h>
14
   #include <botan/tpm2_rng.h>
15
   #include <botan/tpm2_session.h>
16
   #include <botan/internal/buffer_slicer.h>
17
   #include <botan/internal/fmt.h>
18
   #include <botan/internal/loadstor.h>
19
   #include <botan/internal/mem_utils.h>
20
   #include <botan/internal/stl_util.h>
21
   #include <botan/internal/tpm2_hash.h>
22
   #include <algorithm>
23

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

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

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

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

42
namespace Botan_Tests {
43

44
namespace {
45

46
#if defined(BOTAN_HAS_TPM2)
47

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

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

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

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

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

74
   return ctx;
7✔
75
}
14✔
76

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

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

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

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

97
      TR(const TR&) = delete;
98
      TR& operator=(const TR&) = delete;
99

100
      ~TR() { flush(); }
4✔
101

102
      void flush() noexcept {
12✔
103
         if(m_esys_ctx != nullptr && m_handle != ESYS_TR_NONE) {
12✔
104
            Esys_FlushContext(m_esys_ctx, m_handle);
×
105
            m_handle = ESYS_TR_NONE;
4✔
106
         }
107
      }
108

109
      // NOLINTNEXTLINE(*-explicit-conversions) FIXME
110
      constexpr operator ESYS_TR() const { return m_handle; }
3✔
111
};
112

113
struct esys_context_liberator {
114
      void operator()(ESYS_CONTEXT* esys_ctx) {
3✔
115
         TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;  // NOLINT(*-const-correctness) clang-tidy bug
3✔
116
         Esys_GetTcti(esys_ctx, &tcti_ctx);      // ignore error in destructor
3✔
117
         if(tcti_ctx != nullptr) {
3✔
118
            Tss2_TctiLdr_Finalize(&tcti_ctx);
3✔
119
         }
120
         Esys_Finalize(&esys_ctx);
3✔
121
      }
3✔
122
};
123

124
auto get_external_tpm2_context() -> std::unique_ptr<ESYS_CONTEXT, esys_context_liberator> {
3✔
125
   const auto tcti_name = Test::options().tpm2_tcti_name();
3✔
126
   const auto tcti_conf = Test::options().tpm2_tcti_conf();
3✔
127
   if(tcti_name.value() == "disabled") {
3✔
128
      // skip the test if the special 'disabled' TCTI is configured
129
      return nullptr;
×
130
   }
131

132
   TSS2_RC rc = 0;
3✔
133
   TSS2_TCTI_CONTEXT* tcti_ctx = nullptr;
3✔
134
   std::unique_ptr<ESYS_CONTEXT, esys_context_liberator> esys_ctx;
3✔
135

136
   rc = Tss2_TctiLdr_Initialize_Ex(tcti_name->c_str(), tcti_conf->c_str(), &tcti_ctx);
3✔
137
   if(rc != TSS2_RC_SUCCESS) {
3✔
138
      throw Test_Error("failed to initialize external TCTI");
×
139
   }
140

141
   rc = Esys_Initialize(Botan::out_ptr(esys_ctx), tcti_ctx, nullptr /* ABI version */);
3✔
142
   if(rc != TSS2_RC_SUCCESS) {
3✔
143
      throw Test_Error("failed to initialize external ESYS");
×
144
   }
145

146
   // This TPM2::Context is created for environment validation only.
147
   // It is transient, but the 'externally provided' ESYS_CONTEXT will live on!
148
   auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
3✔
149
   if(!validate_context_environment(ctx)) {
3✔
150
      return nullptr;
×
151
   }
152

153
   return esys_ctx;
3✔
154
}
9✔
155

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

166
Test::Result bail_out() {
×
167
   Test::Result result("TPM2 test bail out");
×
168
   bail_out(result);
×
169
   return result;
×
170
}
×
171

172
bool not_zero_64(std::span<const uint8_t> in) {
6✔
173
   Botan::BufferSlicer bs(in);
6✔
174

175
   while(bs.remaining() > 8) {
78✔
176
      if(Botan::load_be(bs.take<8>()) == 0) {
144✔
177
         return false;
178
      }
179
   }
180
   // Ignore remaining bytes
181

182
   return true;
183
}
184

185
std::vector<Test::Result> test_tpm2_properties() {
1✔
186
   auto ctx = get_tpm2_context(__func__);
1✔
187
   if(!ctx) {
1✔
188
      return {bail_out()};
×
189
   }
190

191
   return {
1✔
192
      CHECK("Vendor and Manufacturer",
193
            [&](Test::Result& result) {
1✔
194
               result.test_str_eq("Vendor", ctx->vendor(), "SW   TPM");
1✔
195
               result.test_str_eq("Manufacturer", ctx->manufacturer(), "IBM");
1✔
196
            }),
1✔
197

198
      CHECK("Max random bytes per request",
199
            [&](Test::Result& result) {
1✔
200
               const auto prop = ctx->max_random_bytes_per_request();
1✔
201
               result.test_sz_gte("at least as long as SHA-256", prop, 32);
1✔
202
               result.test_sz_lte("at most as long as SHA-512", prop, 64);
1✔
203
            }),
1✔
204

205
      CHECK("Supports basic algorithms",
206
            [&](Test::Result& result) {
1✔
207
               result.test_is_true("RSA is supported", ctx->supports_algorithm("RSA"));
1✔
208
               result.test_is_true("AES-128 is supported", ctx->supports_algorithm("AES-128"));
1✔
209
               result.test_is_true("AES-256 is supported", ctx->supports_algorithm("AES-256"));
1✔
210
               result.test_is_true("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
1✔
211
               result.test_is_true("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
1✔
212
               result.test_is_true("OFB(AES-128) is supported", ctx->supports_algorithm("OFB(AES-128)"));
1✔
213
               result.test_is_true("OFB is supported", ctx->supports_algorithm("OFB"));
1✔
214
            }),
1✔
215

216
      CHECK("Unsupported algorithms aren't supported",
217
            [&](Test::Result& result) {
1✔
218
               result.test_is_true("Enigma is not supported", !ctx->supports_algorithm("Enigma"));
1✔
219
               result.test_is_true("MD5 is not supported", !ctx->supports_algorithm("MD5"));
1✔
220
               result.test_is_true("DES is not supported", !ctx->supports_algorithm("DES"));
1✔
221
               result.test_is_true("OAEP(Keccak) is not supported", !ctx->supports_algorithm("OAEP(Keccak)"));
1✔
222
            }),
1✔
223
   };
5✔
224
}
2✔
225

226
std::vector<Test::Result> test_tpm2_context() {
1✔
227
   auto ctx = get_tpm2_context(__func__);
1✔
228
   if(!ctx) {
1✔
229
      return {bail_out()};
×
230
   }
231

232
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
233

234
   return {
1✔
235
      CHECK("Persistent handles",
236
            [&](Test::Result& result) {
1✔
237
               const auto handles = ctx->persistent_handles();
1✔
238
               result.test_is_true("At least one persistent handle", !handles.empty());
1✔
239
               result.test_is_true("SRK is in the list", Botan::value_exists(handles, 0x81000001));
2✔
240
               result.test_is_true("Test private key is in the list", Botan::value_exists(handles, persistent_key_id));
2✔
241
               result.test_is_true("Test persistence location is not in the list",
×
242
                                   !Botan::value_exists(handles, persistent_key_id + 1));
2✔
243
            }),
1✔
244

245
         CHECK("Crypto backend",
246
               [&](Test::Result& result) {
1✔
247
                  const bool backend_supported = ctx->supports_botan_crypto_backend();
1✔
248
                  const bool backend_used = ctx->uses_botan_crypto_backend();
1✔
249
                  result.require("Crypto backend availability",
1✔
250
                                 backend_supported == crypto_backend_should_be_available);
251
                  result.require("Crypto backend is used in the tests, if it is available",
1✔
252
                                 backend_used == backend_supported);
253

254
                  if(backend_used) {
1✔
255
                     result.test_throws<Botan::Invalid_State>(
1✔
256
                        "If the backend is already in use, we cannot enable it once more",
257
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng("tpm2_backend_test")); });
4✔
258
                  }
259

260
                  if(!backend_supported) {
1✔
261
                     result.test_throws<Botan::Not_Implemented>(
×
262
                        "If the backend is not supported, we cannot enable it",
263
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng("tpm2_backend_test")); });
×
264
                  }
265
               }),
1✔
266

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

280
std::vector<Test::Result> test_external_tpm2_context() {
1✔
281
   auto raw_start_session = [](ESYS_CONTEXT* esys_ctx) -> std::pair<TR, TSS2_RC> {
5✔
282
      const TPMT_SYM_DEF sym_spec{
4✔
283
         .algorithm = TPM2_ALG_AES,
284
         .keyBits = {.sym = 256},
285
         .mode = {.sym = TPM2_ALG_CFB},
286
      };
287
      ESYS_TR session = 0;
4✔
288

289
      auto rc = Esys_StartAuthSession(esys_ctx,
4✔
290
                                      ESYS_TR_NONE,
291
                                      ESYS_TR_NONE,
292
                                      ESYS_TR_NONE,
293
                                      ESYS_TR_NONE,
294
                                      ESYS_TR_NONE,
295
                                      nullptr,
296
                                      TPM2_SE_HMAC,
297
                                      &sym_spec,
298
                                      TPM2_ALG_SHA256,
299
                                      &session);
4✔
300

301
      if(rc == TSS2_RC_SUCCESS) {
4✔
302
         const auto session_attributes = TPMA_SESSION_CONTINUESESSION | TPMA_SESSION_DECRYPT | TPMA_SESSION_ENCRYPT;
3✔
303
         rc = Esys_TRSess_SetAttributes(esys_ctx, session, session_attributes, 0xFF);
3✔
304
      }
305

306
      return {TR{esys_ctx, session}, rc};
4✔
307
   };
308

309
   auto raw_get_random_bytes = [](ESYS_CONTEXT* esys_ctx, uint16_t bytes, ESYS_TR session = ESYS_TR_NONE) {
4✔
310
      Botan::TPM2::unique_esys_ptr<TPM2B_DIGEST> random_bytes;
3✔
311
      const auto rc =
3✔
312
         Esys_GetRandom(esys_ctx, session, ESYS_TR_NONE, ESYS_TR_NONE, bytes, Botan::out_ptr(random_bytes));
3✔
313
      return std::make_pair(std::move(random_bytes), rc);
3✔
314
   };
3✔
315

316
   return {
1✔
317
      CHECK("ESYS context is still functional after TPM2::Context destruction",
318
            [&](Test::Result& result) {
1✔
319
               auto esys_ctx = get_external_tpm2_context();
1✔
320
               if(!esys_ctx) {
1✔
321
                  bail_out(result);
×
322
                  return;
×
323
               }
324

325
               {
1✔
326
                  // Do some TPM2-stuff via the Botan wrappers
327

328
                  auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
1✔
329
                  auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
330
                  auto rng = Botan::TPM2::RandomNumberGenerator(ctx, session);
3✔
331

332
                  auto bytes = rng.random_vec(16);
1✔
333
                  result.test_sz_eq("some random bytes generated", bytes.size(), 16);
1✔
334

335
                  // All Botan-wrapped things go out of scope...
336
               }
2✔
337

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

341
               auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
342
               Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
343
               result.test_sz_eq("some raw random bytes generated", bytes->size, 16);
1✔
344
            }),
2✔
345

346
         CHECK("TPM2::Context-managed crypto backend fails gracefully after TPM2::Context destruction",
347
               [&](Test::Result& result) {
1✔
348
                  auto esys_ctx = get_external_tpm2_context();
1✔
349
                  if(!esys_ctx) {
1✔
350
                     bail_out(result);
×
351
                     return;
×
352
                  }
353

354
                  {
1✔
355
                     auto ctx = Botan::TPM2::Context::create(esys_ctx.get());
1✔
356
                     if(!ctx->supports_botan_crypto_backend()) {
1✔
357
                        bail_out(result, "skipping, because botan-based crypto backend is not supported");
×
358
                        return;
×
359
                     }
360

361
                     ctx->use_botan_crypto_backend(Test::new_rng("tpm2_backend_context_test"));
3✔
362
                  }
×
363

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

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

371
   #if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
372
                  // Manually resetting the crypto callbacks (in retrospect) fixes this
373
                  const auto callbacks_rc = Esys_SetCryptoCallbacks(esys_ctx.get(), nullptr);
1✔
374
                  Botan::TPM2::check_rc("resetting crypto callbacks", callbacks_rc);
1✔
375

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

379
                  auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
380
                  Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
381
                  result.test_sz_eq("some raw random bytes generated", bytes->size, 16);
1✔
382
   #endif
383
               }),
3✔
384

385
   #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND)
386
         CHECK("free-standing crypto backend", [&](Test::Result& result) {
1✔
387
            if(!Botan::TPM2::supports_botan_crypto_backend()) {
1✔
388
               bail_out(result, "botan crypto backend is not supported");
×
389
               return;
×
390
            }
391

392
            auto esys_ctx = get_external_tpm2_context();
1✔
393
            if(!esys_ctx) {
1✔
394
               bail_out(result);
×
395
               return;
×
396
            }
397

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

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

403
            auto [bytes, rc_random] = raw_get_random_bytes(esys_ctx.get(), 16, raw_session);
1✔
404
            Botan::TPM2::check_rc("random byte generation successful", rc_random);
1✔
405
            result.test_sz_eq("some raw random bytes generated", bytes->size, 16);
1✔
406
         }),
2✔
407
   #endif
408
   };
4✔
409
}
1✔
410

411
std::vector<Test::Result> test_tpm2_sessions() {
1✔
412
   auto ctx = get_tpm2_context(__func__);
1✔
413
   if(!ctx) {
1✔
414
      return {bail_out()};
×
415
   }
416

417
   auto ok = [](Test::Result& result, std::string_view name, const std::shared_ptr<Botan::TPM2::Session>& session) {
13✔
418
      result.require(Botan::fmt("Session '{}' is non-null", name), session != nullptr);
12✔
419
      result.test_is_true(Botan::fmt("Session '{}' has a valid handle", name), session->handle() != ESYS_TR_NONE);
24✔
420
      result.test_is_true(Botan::fmt("Session '{}' has a non-empty nonce", name), !session->tpm_nonce().empty());
24✔
421
   };
12✔
422

423
   return {
1✔
424
      CHECK("Unauthenticated sessions",
425
            [&](Test::Result& result) {
1✔
426
               using Session = Botan::TPM2::Session;
1✔
427

428
               ok(result, "default", Session::unauthenticated_session(ctx));
1✔
429
               ok(result, "CFB(AES-128)", Session::unauthenticated_session(ctx, "CFB(AES-128)"));
1✔
430
               ok(result, "CFB(AES-128),SHA-384", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-384"));
1✔
431
               ok(result, "CFB(AES-128),SHA-1", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-1"));
1✔
432
            }),
1✔
433

434
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
435
         CHECK(
436
            "Authenticated sessions SRK",
437
            [&](Test::Result& result) {
1✔
438
               using Session = Botan::TPM2::Session;
1✔
439

440
               auto srk = ctx->storage_root_key({}, {});
2✔
441
               ok(result, "default", Session::authenticated_session(ctx, *srk));
1✔
442
               ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *srk, "CFB(AES-128)"));
1✔
443
               ok(result, "CFB(AES-128),SHA-384", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-384"));
1✔
444
               ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-1"));
2✔
445
            }),
1✔
446
   #endif
447

448
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
449
         CHECK("Authenticated sessions ECC", [&](Test::Result& result) {
1✔
450
            using Session = Botan::TPM2::Session;
1✔
451
            const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
452

453
            auto ecc_key = Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, {}, {});
2✔
454
            result.require("EK is not null", ecc_key != nullptr);
1✔
455
            result.test_str_eq("Algo", ecc_key->algo_name(), "ECDSA");
1✔
456
            result.test_is_true("Has persistent handle", ecc_key->handles().has_persistent_handle());
1✔
457

458
            ok(result, "default", Session::authenticated_session(ctx, *ecc_key));
1✔
459
            ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)"));
1✔
460
            ok(result,
1✔
461
               "CFB(AES-128),SHA-384",
462
               Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-384"));
1✔
463
            ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-1"));
2✔
464
         }),
1✔
465
   #endif
466
   };
4✔
467
}
2✔
468

469
std::vector<Test::Result> test_tpm2_rng() {
1✔
470
   auto ctx = get_tpm2_context(__func__);
1✔
471
   if(!ctx) {
1✔
472
      return {bail_out()};
×
473
   }
474

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

477
   return {
1✔
478
      CHECK("Basic functionalities",
479
            [&](Test::Result& result) {
1✔
480
               result.test_is_true("Accepts input", rng.accepts_input());
1✔
481
               result.test_is_true("Is seeded", rng.is_seeded());
1✔
482
               result.test_str_eq("Right name", rng.name(), "TPM2_RNG");
1✔
483

484
               result.test_no_throw("Clear", [&] { rng.clear(); });
1✔
485
            }),
1✔
486

487
      CHECK("Random number generation",
488
            [&](Test::Result& result) {
1✔
489
               std::array<uint8_t, 8> buf1 = {};
1✔
490
               rng.randomize(buf1);
1✔
491
               result.test_is_true("Is at least not 0 (8)", not_zero_64(buf1));
1✔
492

493
               std::array<uint8_t, 15> buf2 = {};
1✔
494
               rng.randomize(buf2);
1✔
495
               result.test_is_true("Is at least not 0 (15)", not_zero_64(buf2));
1✔
496

497
               std::array<uint8_t, 256> buf3 = {};
1✔
498
               rng.randomize(buf3);
1✔
499
               result.test_is_true("Is at least not 0 (256)", not_zero_64(buf3));
1✔
500
            }),
1✔
501

502
      CHECK("Randomize with inputs",
503
            [&](Test::Result& result) {
1✔
504
               std::array<uint8_t, 9> buf1 = {};
1✔
505
               rng.randomize_with_input(buf1, std::array<uint8_t, 30>{});
1✔
506
               result.test_is_true("Randomized with inputs is at least not 0 (9)", not_zero_64(buf1));
1✔
507

508
               std::array<uint8_t, 66> buf2 = {};
1✔
509
               rng.randomize_with_input(buf2, std::array<uint8_t, 64>{});
1✔
510
               result.test_is_true("Randomized with inputs is at least not 0 (66)", not_zero_64(buf2));
1✔
511

512
               std::array<uint8_t, 256> buf3 = {};
1✔
513
               rng.randomize_with_input(buf3, std::array<uint8_t, 196>{});
1✔
514
               result.test_is_true("Randomized with inputs is at least not 0 (256)", not_zero_64(buf3));
1✔
515
            }),
1✔
516
   };
4✔
517
}
3✔
518

519
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
520

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

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

540
   result.test_str_eq("Algo", key->algo_name(), "RSA" /* TODO ECC support*/);
32✔
541
   result.test_u64_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
32✔
542
   return key;
32✔
543
}
32✔
544

545
std::vector<Test::Result> test_tpm2_rsa() {
1✔
546
   auto ctx = get_tpm2_context(__func__);
1✔
547
   if(!ctx) {
1✔
548
      return {bail_out()};
×
549
   }
550

551
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
552

553
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
554
   const auto password = Botan::as_span_of_bytes(Test::options().tpm2_persistent_auth_value());
1✔
555

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

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

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

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

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

596
               auto public_key = key->public_key();
1✔
597
               Botan::PK_Verifier verifier(*public_key, "PSS(SHA-256)");
1✔
598
               result.test_is_true("Signature is valid", verifier.verify_message(message, signature));
1✔
599
            }),
5✔
600

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

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

618
               const auto message = Botan::hex_decode("baadcafe");
1✔
619
               const auto signature = sign(message);
1✔
620

621
               result.test_is_true("verification successful", verify(message, signature));
1✔
622

623
               // change the message
624
               auto rng = Test::new_rng("tpm2_verify_message");
1✔
625
               auto mutated_message = Test::mutate_vec(message, *rng);
1✔
626
               result.test_is_true("verification failed", !verify(mutated_message, signature));
1✔
627

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

636
               // original message again
637
               result.test_is_true("verification still successful", verify(message, signature));
1✔
638
            }),
4✔
639

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

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

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

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

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

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

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

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

698
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
699

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

705
               // decrypt the message using the TPM's private RSA key
706
               const Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
707
               const auto decrypted = dec.decrypt(ciphertext);
1✔
708
               result.test_bin_eq("decrypted message", decrypted, plaintext);
1✔
709
            }),
5✔
710

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

716
               const auto plaintext = Botan::hex_decode("feedface");
1✔
717

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

724
               // decrypt the message using the TPM's private key
725
               Botan::Null_RNG null_rng;
1✔
726
               Botan::PK_Decryptor_EME dec(*key, null_rng /* TPM takes care of this */, "OAEP(SHA-256)");
1✔
727
               const auto decrypted = dec.decrypt(ciphertext);
1✔
728
               result.test_bin_eq("decrypted message", decrypted, plaintext);
1✔
729

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

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

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

746
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
747

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

753
               // decrypt the message using the TPM's private RSA key
754
               Botan::Null_RNG null_rng;
1✔
755
               const Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
756
               const auto decrypted = dec.decrypt(ciphertext);
1✔
757
               result.test_bin_eq("decrypted message", decrypted, plaintext);
1✔
758

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

763
               // decrypt the message using the TPM's private RSA key (using PKCS#1)
764
               const Botan::PK_Decryptor_EME dec_pkcs(*sk, null_rng, "PKCS1v15");
1✔
765
               const auto decrypted_pkcs = dec_pkcs.decrypt(ciphertext_pkcs);
1✔
766
               result.test_bin_eq("decrypted message", decrypted_pkcs, plaintext);
1✔
767
            }),
10✔
768

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

779
      CHECK("Create a new transient key",
780
            [&](Test::Result& result) {
1✔
781
               auto srk = ctx->storage_root_key({}, {});
2✔
782

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

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

787
               auto sk =
1✔
788
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
789

790
               result.require("key was created", sk != nullptr);
1✔
791
               result.test_is_true("is transient", sk->handles().has_transient_handle());
1✔
792
               result.test_is_true("is not persistent", !sk->handles().has_persistent_handle());
1✔
793

794
               const auto sk_blob = sk->raw_private_key_bits();
1✔
795
               const auto pk_blob = sk->raw_public_key_bits();
1✔
796
               const auto pk = sk->public_key();
1✔
797

798
               result.test_is_true("secret blob is not empty", !sk_blob.empty());
1✔
799
               result.test_is_true("public blob is not empty", !pk_blob.empty());
1✔
800

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

808
               Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
1✔
809
               result.test_is_true("Signature is valid", verifier.verify_message(message, signature));
1✔
810

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

818
               const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
819
               const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
820

821
               result.test_bin_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
822
               result.test_bin_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
823

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

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

836
               Botan::PK_Verifier verifier_loaded(*pk_loaded, "PSS(SHA-256)");
1✔
837
               result.test_is_true("TPM-verified signature is valid",
1✔
838
                                   verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
839

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

848
               // Create a verifier for PKCS#1
849
               Botan::PK_Verifier verifier_pkcs(*pk_loaded, "PKCS1v15(SHA-256)");
1✔
850
               result.test_is_true("TPM-verified signature is valid",
1✔
851
                                   verifier_pkcs.verify_message(message_pkcs, signature_pkcs));
1✔
852
            }),
16✔
853

854
      CHECK(
855
         "Make a transient key persistent then remove it again",
856
         [&](Test::Result& result) {
1✔
857
            auto srk = ctx->storage_root_key({}, {});
2✔
858

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

866
               auto pk = key.public_key();
2✔
867
               Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
2✔
868
               result.test_is_true("Signature is valid", verifier.verify_message(message, signature));
2✔
869
            };
8✔
870

871
            // Create Key
872
            auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
873

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

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

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

904
   #endif
905

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

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

927
   result.test_str_eq("Algo", key->algo_name(), "ECDSA");
31✔
928
   result.test_u64_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
31✔
929
   return key;
31✔
930
}
31✔
931

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

939
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
940

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

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

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

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

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

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

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

1001
                  result.test_is_true("verification successful", verify(message, signature));
1✔
1002

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

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

1016
                  // original message again
1017
                  result.test_is_true("verification still successful", verify(message, signature));
1✔
1018
               }),
4✔
1019

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

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

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

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

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

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

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

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

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

1089
                  const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
1090

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

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

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

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

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

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

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

1123
                  result.require("key was created", sk != nullptr);
1✔
1124
                  result.test_is_true("is transient", sk->handles().has_transient_handle());
1✔
1125
                  result.test_is_true("is not persistent", !sk->handles().has_persistent_handle());
1✔
1126

1127
                  const auto sk_blob = sk->raw_private_key_bits();
1✔
1128
                  const auto pk_blob = sk->raw_public_key_bits();
1✔
1129
                  const auto pk = sk->public_key();
1✔
1130

1131
                  result.test_is_true("secret blob is not empty", !sk_blob.empty());
1✔
1132
                  result.test_is_true("public blob is not empty", !pk_blob.empty());
1✔
1133

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

1141
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
1✔
1142
                  result.test_is_true("Signature is valid", verifier.verify_message(message, signature));
1✔
1143

1144
                  // Destruct the key and load it again from the encrypted blob
1145
                  sk.reset();
1✔
1146
                  auto sk_loaded =
1✔
1147
                     Botan::TPM2::PrivateKey::load_transient(ctx, secret, *srk, pk_blob, sk_blob, authed_session);
2✔
1148
                  result.require("key was loaded", sk_loaded != nullptr);
1✔
1149
                  result.test_str_eq("loaded key is ECDSA", sk_loaded->algo_name(), "ECDSA");
1✔
1150

1151
                  const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
1152
                  const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
1153

1154
                  result.test_bin_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
1155
                  result.test_bin_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
1156

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

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

1169
                  Botan::PK_Verifier verifier_loaded(*pk_loaded, "SHA-256");
1✔
1170
                  result.test_is_true("TPM-verified signature is valid",
1✔
1171
                                      verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
1172
               }),
15✔
1173

1174
         CHECK(
1175
            "Make a transient ECDSA key persistent then remove it again",
1176
            [&](Test::Result& result) {
1✔
1177
               auto srk = ctx->storage_root_key({}, {});
2✔
1178
               auto ecc_session_key = Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
1179

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

1187
                  auto pk = key.public_key();
2✔
1188
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
2✔
1189
                  result.test_is_true("Signature is valid", verifier.verify_message(message, signature));
2✔
1190
               };
8✔
1191

1192
               // Create Key
1193
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
1194

1195
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
1196
               auto sk = Botan::TPM2::EC_PrivateKey::create_unrestricted_transient(
1✔
1197
                  ctx, authed_session, secret, *srk, Botan::EC_Group::from_name("secp192r1"));
2✔
1198
               result.require("key was created", sk != nullptr);
1✔
1199
               result.test_is_true("is transient", sk->handles().has_transient_handle());
1✔
1200
               result.test_is_true("is not persistent", !sk->handles().has_persistent_handle());
1✔
1201
               result.test_no_throw("use key after creation", [&] { sign_verify_roundtrip(*sk); });
2✔
1202

1203
               // Make it persistent
1204
               const auto handles = ctx->persistent_handles().size();
1✔
1205
               const auto new_location = ctx->persist(*sk, authed_session, secret);
2✔
1206
               result.test_sz_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
1✔
1207
               result.test_is_true("New location occupied",
1✔
1208
                                   Botan::value_exists(ctx->persistent_handles(), new_location));
2✔
1209
               result.test_is_true("is persistent", sk->handles().has_persistent_handle());
1✔
1210
               result.test_u64_eq(
1✔
1211
                  "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
1212
               result.test_throws<Botan::Invalid_Argument>(
1✔
1213
                  "Cannot persist to the same location", [&] { ctx->persist(*sk, authed_session, {}, new_location); });
2✔
1214
               result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key",
1✔
1215
                                                           [&] { ctx->persist(*sk, authed_session); });
2✔
1216
               result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
2✔
1217

1218
               // Evict it
1219
               ctx->evict(std::move(sk), authed_session);
3✔
1220
               result.test_sz_eq("One less handle", ctx->persistent_handles().size(), handles);
1✔
1221
               result.test_is_true("New location no longer occupied",
1✔
1222
                                   !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
1223
            }),
4✔
1224
      #endif
1225

1226
         CHECK("Read a software public key from a TPM serialization", [&](Test::Result& result) {
1✔
1227
            auto pk = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
1228
            result.test_no_throw("Botan can read serialized ECC public key", [&] {
1✔
1229
               std::ignore = Botan::ECDSA_PublicKey(pk->algorithm_identifier(), pk->public_key_bits());
1✔
1230
            });
1✔
1231

1232
            auto sk =
1✔
1233
               load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
1234
            result.test_no_throw("Botan can read serialized public key from ECC private key", [&] {
1✔
1235
               std::ignore = Botan::ECDSA_PublicKey(sk->algorithm_identifier(), sk->public_key_bits());
1✔
1236
            });
1✔
1237
         }),
2✔
1238
   };
11✔
1239
}
3✔
1240
   #endif
1241

1242
std::vector<Test::Result> test_tpm2_hash() {
1✔
1243
   auto ctx = get_tpm2_context(__func__);
1✔
1244
   if(!ctx) {
1✔
1245
      return {bail_out()};
×
1246
   }
1247

1248
   auto test = [&](Test::Result& result, std::string_view algo) {
8✔
1249
      auto tpm_hash = [&]() -> std::unique_ptr<Botan::TPM2::HashFunction> {
21✔
1250
         try {
7✔
1251
            return std::make_unique<Botan::TPM2::HashFunction>(
7✔
1252
               ctx, algo, ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
7✔
1253
         } catch(const Botan::Lookup_Error&) {
3✔
1254
            return {};
3✔
1255
         }
3✔
1256
      }();
7✔
1257
      auto soft_hash = Botan::HashFunction::create(algo);
7✔
1258

1259
      if(!tpm_hash) {
7✔
1260
         result.test_note(Botan::fmt("Skipping {}, TPM 2.0 does not support it", algo));
3✔
1261
         return;
3✔
1262
      }
1263

1264
      if(!soft_hash) {
4✔
1265
         result.test_note(Botan::fmt("Skipping {}, no software equivalent available", algo));
×
1266
         return;
×
1267
      }
1268

1269
      result.test_str_eq("Name", tpm_hash->name(), soft_hash->name());
4✔
1270
      result.test_sz_eq("Output length", tpm_hash->output_length(), soft_hash->output_length());
4✔
1271

1272
      // multiple update calls
1273
      tpm_hash->update("Hello, ");
4✔
1274
      tpm_hash->update("world!");
4✔
1275
      result.test_bin_eq("digest (multi-update)", tpm_hash->final(), soft_hash->process("Hello, world!"));
12✔
1276

1277
      // single process call
1278
      result.test_bin_eq(
12✔
1279
         "digest (single-process)", tpm_hash->process("Hallo, Welt."), soft_hash->process("Hallo, Welt."));
16✔
1280

1281
      // create a message that is larger than the TPM2 max buffer size
1282
      const auto long_message = [] {
×
1283
         std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
4✔
1284
         for(size_t i = 0; i < msg.size(); ++i) {
4,120✔
1285
            msg[i] = static_cast<uint8_t>(i);
4,116✔
1286
         }
1287
         return msg;
4✔
1288
      }();
4✔
1289

1290
      tpm_hash->update(long_message);
4✔
1291
      result.test_bin_eq("digest (long msg via update)", tpm_hash->final(), soft_hash->process(long_message));
8✔
1292
      result.test_bin_eq(
4✔
1293
         "digest (long msg via process)", tpm_hash->process(long_message), soft_hash->process(long_message));
8✔
1294

1295
      // test clear
1296
      tpm_hash->update("Hello");
4✔
1297
      tpm_hash->clear();
4✔
1298
      tpm_hash->update("Bonjour");
4✔
1299
      result.test_bin_eq("digest (clear)", tpm_hash->final(), soft_hash->process("Bonjour"));
12✔
1300

1301
      // new_object
1302
      auto new_tpm_hash = tpm_hash->new_object();
4✔
1303
      result.test_str_eq("Name (new_object)", new_tpm_hash->name(), tpm_hash->name());
4✔
1304
      result.test_sz_eq("Output length (new_object)", new_tpm_hash->output_length(), tpm_hash->output_length());
4✔
1305
      result.test_bin_eq("digest (new object)",
12✔
1306
                         new_tpm_hash->process("Salut tout le monde!"),
12✔
1307
                         soft_hash->process("Salut tout le monde!"));
12✔
1308
   };
15✔
1309

1310
   return {
1✔
1311
      CHECK("Hashes are supported",
1312
            [&](Test::Result& result) {
1✔
1313
               result.test_is_true("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
1✔
1314
               result.test_is_true("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
1✔
1315
               result.test_is_true("SHA-384 is supported", ctx->supports_algorithm("SHA-384"));
1✔
1316
               result.test_is_true("SHA-512 is supported", ctx->supports_algorithm("SHA-512"));
1✔
1317
            }),
1✔
1318

1319
      CHECK("SHA-1", [&](Test::Result& result) { test(result, "SHA-1"); }),
1✔
1320
      CHECK("SHA-256", [&](Test::Result& result) { test(result, "SHA-256"); }),
1✔
1321
      CHECK("SHA-384", [&](Test::Result& result) { test(result, "SHA-384"); }),
1✔
1322
      CHECK("SHA-512", [&](Test::Result& result) { test(result, "SHA-512"); }),
1✔
1323
      CHECK("SHA-3(256)", [&](Test::Result& result) { test(result, "SHA-3(256)"); }),
1✔
1324
      CHECK("SHA-3(384)", [&](Test::Result& result) { test(result, "SHA-3(384)"); }),
1✔
1325
      CHECK("SHA-3(512)", [&](Test::Result& result) { test(result, "SHA-3(512)"); }),
1✔
1326

1327
      CHECK("lookup error",
1328
            [&](Test::Result& result) {
1✔
1329
               result.test_throws<Botan::Lookup_Error>(
1✔
1330
                  "Lookup error", [&] { [[maybe_unused]] auto _ = Botan::TPM2::HashFunction(ctx, "MD-5"); });
7✔
1331
            }),
1✔
1332

1333
      CHECK("copy_state is not implemented",
1334
            [&](Test::Result& result) {
1✔
1335
               auto tpm_hash = Botan::TPM2::HashFunction(ctx, "SHA-256");
3✔
1336
               result.test_throws<Botan::Not_Implemented>("TPM2 hash does not support copy_state",
1✔
1337
                                                          [&] { [[maybe_unused]] auto _ = tpm_hash.copy_state(); });
2✔
1338
            }),
1✔
1339

1340
      CHECK("validation ticket",
1341
            [&](Test::Result& result) {
1✔
1342
               // using the NULL hierarchy essentially disables the validation ticket
1343
               auto tpm_hash_null = Botan::TPM2::HashFunction(
1✔
1344
                  ctx, "SHA-256", ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1345
               tpm_hash_null.update("Hola mundo!");
1✔
1346
               const auto [digest_null, ticket_null] = tpm_hash_null.final_with_ticket();
1✔
1347
               result.require("digest is set", digest_null != nullptr);
1✔
1348
               result.require("ticket is set", ticket_null != nullptr);
1✔
1349
               result.test_is_true("ticket is empty", ticket_null->digest.size == 0);
1✔
1350

1351
               // using the OWNER hierarchy (for instance) enables the validation ticket
1352
               auto tpm_hash_owner = Botan::TPM2::HashFunction(
1✔
1353
                  ctx, "SHA-256", ESYS_TR_RH_OWNER, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1354
               tpm_hash_owner.update("Hola mundo!");
1✔
1355
               const auto [digest_owner, ticket_owner] = tpm_hash_owner.final_with_ticket();
1✔
1356
               result.require("digest is set", digest_owner != nullptr);
1✔
1357
               result.require("ticket is set", ticket_owner != nullptr);
1✔
1358
               result.test_is_true("ticket is not empty", ticket_owner->digest.size > 0);
1✔
1359

1360
               const auto digest_vec = Botan::TPM2::copy_into<Botan::secure_vector<uint8_t>>(*digest_owner);
1✔
1361
               result.test_bin_eq(
1✔
1362
                  "digest",
1363
                  digest_vec,
1364
                  Botan::hex_decode("1e479f4d871e59e9054aad62105a259726801d5f494acbfcd40591c82f9b3136"));
1✔
1365

1366
               result.test_bin_eq("digests are the same, regardless of ticket",
1✔
1367
                                  Botan::TPM2::copy_into<std::vector<uint8_t>>(*digest_null),
2✔
1368
                                  digest_vec);
1369
            }),
1✔
1370
   };
12✔
1371
}
2✔
1372

1373
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_props", test_tpm2_properties);
1374
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ctx", test_tpm2_context);
1375
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_external_ctx", test_external_tpm2_context);
1376
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_sessions", test_tpm2_sessions);
1377
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rng", test_tpm2_rng);
1378
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
1379
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rsa", test_tpm2_rsa);
1380
   #endif
1381
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
1382
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ecc", test_tpm2_ecc);
1383
   #endif
1384
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_hash", test_tpm2_hash);
1385

1386
#endif
1387

1388
}  // namespace
1389

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