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

randombit / botan / 11269086321

10 Oct 2024 07:04AM UTC coverage: 90.996% (+0.01%) from 90.983%
11269086321

push

github

web-flow
Merge pull request #4357 from Rohde-Schwarz/feature/tpm2_ecc

Feature: ECC Support in TPM2

89905 of 98801 relevant lines covered (91.0%)

8974764.45 hits per line

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

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

8
#include "tests.h"
9

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

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

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

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

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

32
namespace Botan_Tests {
33

34
#if defined(BOTAN_HAS_TPM2)
35
namespace {
36

37
   #if defined(BOTAN_HAS_TPM2_CRYPTO_BACKEND) && defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
38
constexpr bool crypto_backend_should_be_available = true;
39
   #else
40
constexpr bool crypto_backend_should_be_available = false;
41
   #endif
42

43
std::shared_ptr<Botan::TPM2::Context> get_tpm2_context(std::string_view rng_tag) {
7✔
44
   const auto tcti_name = Test::options().tpm2_tcti_name();
7✔
45
   if(tcti_name.value() == "disabled") {
7✔
46
      // skip the test if the special 'disabled' TCTI is configured
47
      return {};
×
48
   }
49

50
   auto ctx = Botan::TPM2::Context::create(tcti_name, Test::options().tpm2_tcti_conf());
14✔
51
   if(ctx->vendor() != "SW   TPM" || ctx->manufacturer() != "IBM") {
14✔
52
      return {};
×
53
   }
54

55
   if(ctx->supports_botan_crypto_backend()) {
7✔
56
      ctx->use_botan_crypto_backend(Test::new_rng(rng_tag));
21✔
57
   }
58

59
   return ctx;
7✔
60
}
14✔
61

62
Test::Result bail_out() {
×
63
   Test::Result result("TPM2 test bail out");
×
64

65
   if(Test::options().tpm2_tcti_name() == "disabled") {
×
66
      result.test_note("TPM2 tests are disabled.");
×
67
      return result;
×
68
   } else {
69
      result.test_failure("Not sure we're on a simulated TPM2, cautiously refusing any action.");
×
70
      return result;
×
71
   }
72
}
×
73

74
bool not_zero_64(std::span<const uint8_t> in) {
6✔
75
   Botan::BufferSlicer bs(in);
6✔
76

77
   while(bs.remaining() > 8) {
78✔
78
      if(Botan::load_be(bs.take<8>()) == 0) {
144✔
79
         return false;
80
      }
81
   }
82
   // Ignore remaining bytes
83

84
   return true;
85
}
86

87
std::vector<Test::Result> test_tpm2_properties() {
1✔
88
   auto ctx = get_tpm2_context(__func__);
1✔
89
   if(!ctx) {
1✔
90
      return {bail_out()};
×
91
   }
92

93
   return {
1✔
94
      CHECK("Vendor and Manufacturer",
95
            [&](Test::Result& result) {
1✔
96
               result.test_eq("Vendor", ctx->vendor(), "SW   TPM");
2✔
97
               result.test_eq("Manufacturer", ctx->manufacturer(), "IBM");
2✔
98
            }),
1✔
99

100
      CHECK("Max random bytes per request",
101
            [&](Test::Result& result) {
1✔
102
               const auto prop = ctx->max_random_bytes_per_request();
1✔
103
               result.test_gte("at least as long as SHA-256", prop, 32);
1✔
104
               result.test_lte("at most as long as SHA-512", prop, 64);
1✔
105
            }),
1✔
106

107
      CHECK("Supports basic algorithms",
108
            [&](Test::Result& result) {
1✔
109
               result.confirm("RSA is supported", ctx->supports_algorithm("RSA"));
2✔
110
               result.confirm("AES-128 is supported", ctx->supports_algorithm("AES-128"));
2✔
111
               result.confirm("AES-256 is supported", ctx->supports_algorithm("AES-256"));
2✔
112
               result.confirm("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
2✔
113
               result.confirm("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
2✔
114
               result.confirm("OFB(AES-128) is supported", ctx->supports_algorithm("OFB(AES-128)"));
2✔
115
               result.confirm("OFB is supported", ctx->supports_algorithm("OFB"));
2✔
116
            }),
1✔
117

118
      CHECK("Unsupported algorithms aren't supported",
119
            [&](Test::Result& result) {
1✔
120
               result.confirm("Enigma is not supported", !ctx->supports_algorithm("Enigma"));
2✔
121
               result.confirm("MD5 is not supported", !ctx->supports_algorithm("MD5"));
2✔
122
               result.confirm("DES is not supported", !ctx->supports_algorithm("DES"));
2✔
123
               result.confirm("OAEP(Keccak) is not supported", !ctx->supports_algorithm("OAEP(Keccak)"));
2✔
124
            }),
1✔
125
   };
5✔
126
}
2✔
127

128
std::vector<Test::Result> test_tpm2_context() {
1✔
129
   auto ctx = get_tpm2_context(__func__);
1✔
130
   if(!ctx) {
1✔
131
      return {bail_out()};
×
132
   }
133

134
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
135

136
   return {
1✔
137
      CHECK("Persistent handles",
138
            [&](Test::Result& result) {
1✔
139
               const auto handles = ctx->persistent_handles();
1✔
140
               result.confirm("At least one persistent handle", !handles.empty());
2✔
141
               result.confirm("SRK is in the list", Botan::value_exists(handles, 0x81000001));
3✔
142
               result.confirm("Test private key is in the list", Botan::value_exists(handles, persistent_key_id));
3✔
143
               result.confirm("Test persistence location is not in the list",
1✔
144
                              !Botan::value_exists(handles, persistent_key_id + 1));
3✔
145
            }),
1✔
146

147
         CHECK("Crypto backend",
148
               [&](Test::Result& result) {
1✔
149
                  const bool backend_supported = ctx->supports_botan_crypto_backend();
1✔
150
                  const bool backend_used = ctx->uses_botan_crypto_backend();
1✔
151
                  result.require("Crypto backend availability",
1✔
152
                                 backend_supported == crypto_backend_should_be_available);
153
                  result.require("Crypto backend is used in the tests, if it is available",
1✔
154
                                 backend_used == backend_supported);
155

156
                  if(backend_used) {
1✔
157
                     result.test_throws<Botan::Invalid_State>(
3✔
158
                        "If the backend is already in use, we cannot enable it once more",
159
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng(__func__)); });
4✔
160
                  }
161

162
                  if(!backend_supported) {
1✔
163
                     result.test_throws<Botan::Not_Implemented>(
×
164
                        "If the backend is not supported, we cannot enable it",
165
                        [&] { ctx->use_botan_crypto_backend(Test::new_rng(__func__)); });
×
166
                  }
167
               }),
1✔
168

169
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
170
         // TODO: Since SRK is always RSA in start_tpm2_simulator.sh, the test always requires the RSA adapter?
171
         CHECK("Fetch Storage Root Key RSA", [&](Test::Result& result) {
1✔
172
            auto srk = ctx->storage_root_key({}, {});
2✔
173
            result.require("SRK is not null", srk != nullptr);
1✔
174
            result.test_eq("Algo", srk->algo_name(), "RSA");
2✔
175
            result.test_eq("Key size", srk->key_length(), 2048);
1✔
176
            result.confirm("Has persistent handle", srk->handles().has_persistent_handle());
2✔
177
         }),
1✔
178
   #endif
179
   };
4✔
180
}
2✔
181

182
std::vector<Test::Result> test_tpm2_sessions() {
1✔
183
   auto ctx = get_tpm2_context(__func__);
1✔
184
   if(!ctx) {
1✔
185
      return {bail_out()};
×
186
   }
187

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

194
   return {
1✔
195
      CHECK("Unauthenticated sessions",
196
            [&](Test::Result& result) {
1✔
197
               using Session = Botan::TPM2::Session;
1✔
198

199
               ok(result, "default", Session::unauthenticated_session(ctx));
1✔
200
               ok(result, "CFB(AES-128)", Session::unauthenticated_session(ctx, "CFB(AES-128)"));
1✔
201
               ok(result, "CFB(AES-128),SHA-384", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-384"));
1✔
202
               ok(result, "CFB(AES-128),SHA-1", Session::unauthenticated_session(ctx, "CFB(AES-128)", "SHA-1"));
1✔
203
            }),
1✔
204

205
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
206
         CHECK(
207
            "Authenticated sessions SRK",
208
            [&](Test::Result& result) {
1✔
209
               using Session = Botan::TPM2::Session;
1✔
210

211
               auto srk = ctx->storage_root_key({}, {});
2✔
212
               ok(result, "default", Session::authenticated_session(ctx, *srk));
1✔
213
               ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *srk, "CFB(AES-128)"));
1✔
214
               ok(result, "CFB(AES-128),SHA-384", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-384"));
1✔
215
               ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *srk, "CFB(AES-128)", "SHA-1"));
2✔
216
            }),
1✔
217
   #endif
218

219
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
220
         CHECK("Authenticated sessions ECC", [&](Test::Result& result) {
1✔
221
            using Session = Botan::TPM2::Session;
1✔
222
            const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
223

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

229
            ok(result, "default", Session::authenticated_session(ctx, *ecc_key));
1✔
230
            ok(result, "CFB(AES-128)", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)"));
1✔
231
            ok(result,
1✔
232
               "CFB(AES-128),SHA-384",
233
               Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-384"));
1✔
234
            ok(result, "CFB(AES-128),SHA-1", Session::authenticated_session(ctx, *ecc_key, "CFB(AES-128)", "SHA-1"));
2✔
235
         }),
1✔
236
   #endif
237
   };
4✔
238
}
2✔
239

240
std::vector<Test::Result> test_tpm2_rng() {
1✔
241
   auto ctx = get_tpm2_context(__func__);
1✔
242
   if(!ctx) {
1✔
243
      return {bail_out()};
×
244
   }
245

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

248
   return {
1✔
249
      CHECK("Basic functionalities",
250
            [&](Test::Result& result) {
1✔
251
               result.confirm("Accepts input", rng.accepts_input());
2✔
252
               result.confirm("Is seeded", rng.is_seeded());
2✔
253
               result.test_eq("Right name", rng.name(), "TPM2_RNG");
2✔
254

255
               result.test_no_throw("Clear", [&] { rng.clear(); });
2✔
256
            }),
1✔
257

258
      CHECK("Random number generation",
259
            [&](Test::Result& result) {
1✔
260
               std::array<uint8_t, 8> buf1 = {};
1✔
261
               rng.randomize(buf1);
1✔
262
               result.confirm("Is at least not 0 (8)", not_zero_64(buf1));
2✔
263

264
               std::array<uint8_t, 15> buf2 = {};
1✔
265
               rng.randomize(buf2);
1✔
266
               result.confirm("Is at least not 0 (15)", not_zero_64(buf2));
2✔
267

268
               std::array<uint8_t, 256> buf3 = {};
1✔
269
               rng.randomize(buf3);
1✔
270
               result.confirm("Is at least not 0 (256)", not_zero_64(buf3));
2✔
271
            }),
1✔
272

273
      CHECK("Randomize with inputs",
274
            [&](Test::Result& result) {
1✔
275
               std::array<uint8_t, 9> buf1 = {};
1✔
276
               rng.randomize_with_input(buf1, std::array<uint8_t, 30>{});
1✔
277
               result.confirm("Randomized with inputs is at least not 0 (9)", not_zero_64(buf1));
2✔
278

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

283
               std::array<uint8_t, 256> buf3 = {};
1✔
284
               rng.randomize_with_input(buf3, std::array<uint8_t, 196>{});
1✔
285
               result.confirm("Randomized with inputs is at least not 0 (256)", not_zero_64(buf3));
2✔
286
            }),
1✔
287
   };
4✔
288
}
3✔
289

290
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
291

292
template <typename KeyT>
293
auto load_persistent(Test::Result& result,
32✔
294
                     const std::shared_ptr<Botan::TPM2::Context>& ctx,
295
                     uint32_t persistent_key_id,
296
                     std::span<const uint8_t> auth_value,
297
                     const std::shared_ptr<Botan::TPM2::Session>& session) {
298
   const auto persistent_handles = ctx->persistent_handles();
32✔
299
   result.confirm(
64✔
300
      "Persistent key available",
301
      std::find(persistent_handles.begin(), persistent_handles.end(), persistent_key_id) != persistent_handles.end());
32✔
302

303
   auto key = [&] {
64✔
304
      if constexpr(std::same_as<Botan::TPM2::RSA_PublicKey, KeyT>) {
305
         return KeyT::load_persistent(ctx, persistent_key_id, session);
10✔
306
      } else {
307
         return KeyT::load_persistent(ctx, persistent_key_id, auth_value, session);
54✔
308
      }
309
   }();
310

311
   result.test_eq("Algo", key->algo_name(), "RSA" /* TODO ECC support*/);
64✔
312
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
64✔
313
   return key;
32✔
314
}
32✔
315

316
std::vector<Test::Result> test_tpm2_rsa() {
1✔
317
   auto ctx = get_tpm2_context(__func__);
1✔
318
   if(!ctx) {
1✔
319
      return {bail_out()};
×
320
   }
321

322
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
323

324
   const auto persistent_key_id = Test::options().tpm2_persistent_rsa_handle();
1✔
325
   const auto password = Test::options().tpm2_persistent_auth_value();
1✔
326

327
   return {
1✔
328
      CHECK("RSA and its helpers are supported",
329
            [&](Test::Result& result) {
1✔
330
               result.confirm("RSA is supported", ctx->supports_algorithm("RSA"));
2✔
331
               result.confirm("PKCS1 is supported", ctx->supports_algorithm("PKCS1v15"));
2✔
332
               result.confirm("PKCS1 with hash is supported", ctx->supports_algorithm("PKCS1v15(SHA-1)"));
2✔
333
               result.confirm("OAEP is supported", ctx->supports_algorithm("OAEP"));
2✔
334
               result.confirm("OAEP with hash is supported", ctx->supports_algorithm("OAEP(SHA-256)"));
2✔
335
               result.confirm("PSS is supported", ctx->supports_algorithm("PSS"));
2✔
336
               result.confirm("PSS with hash is supported", ctx->supports_algorithm("PSS(SHA-256)"));
2✔
337
            }),
1✔
338

339
      CHECK("Load the private key multiple times",
340
            [&](Test::Result& result) {
1✔
341
               for(size_t i = 0; i < 20; ++i) {
21✔
342
                  auto key =
20✔
343
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
20✔
344
                  result.test_eq(Botan::fmt("Key loaded successfully ({})", i), key->algo_name(), "RSA");
40✔
345
               }
20✔
346
            }),
1✔
347

348
      CHECK("Sign a message",
349
            [&](Test::Result& result) {
1✔
350
               auto key =
1✔
351
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
352

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

356
               // create a message that is larger than the TPM2 max buffer size
357
               const auto message = [] {
3✔
358
                  std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
359
                  for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
360
                     msg[i] = static_cast<uint8_t>(i);
1,029✔
361
                  }
362
                  return msg;
1✔
363
               }();
1✔
364
               const auto signature = signer.sign_message(message, null_rng);
1✔
365
               result.require("signature is not empty", !signature.empty());
1✔
366

367
               auto public_key = key->public_key();
1✔
368
               Botan::PK_Verifier verifier(*public_key, "PSS(SHA-256)");
1✔
369
               result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
370
            }),
5✔
371

372
      CHECK("verify signature",
373
            [&](Test::Result& result) {
1✔
374
               auto sign = [&](std::span<const uint8_t> message) {
2✔
375
                  auto key =
1✔
376
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
377
                  Botan::Null_RNG null_rng;
1✔
378
                  Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
379
                  return signer.sign_message(message, null_rng);
1✔
380
               };
2✔
381

382
               auto verify = [&](std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
4✔
383
                  auto key =
3✔
384
                     load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
3✔
385
                  Botan::PK_Verifier verifier(*key, "PSS(SHA-256)");
3✔
386
                  return verifier.verify_message(msg, sig);
3✔
387
               };
6✔
388

389
               const auto message = Botan::hex_decode("baadcafe");
1✔
390
               const auto signature = sign(message);
1✔
391

392
               result.confirm("verification successful", verify(message, signature));
2✔
393

394
               // change the message
395
               auto rng = Test::new_rng(__func__);
1✔
396
               auto mutated_message = Test::mutate_vec(message, *rng);
1✔
397
               result.confirm("verification failed", !verify(mutated_message, signature));
2✔
398

399
               // ESAPI manipulates the session attributes internally and does
400
               // not reset them when an error occurs. A failure to validate a
401
               // signature is an error, and hence behaves surprisingly by
402
               // leaving the session attributes in an unexpected state.
403
               // The Botan wrapper has a workaround for this...
404
               const auto attrs = session->attributes();
1✔
405
               result.confirm("encrypt flag was not cleared by ESAPI", attrs.encrypt);
2✔
406

407
               // orignal message again
408
               result.confirm("verification still successful", verify(message, signature));
2✔
409
            }),
4✔
410

411
      CHECK("sign and verify multiple messages with the same Signer/Verifier objects",
412
            [&](Test::Result& result) {
1✔
413
               const std::vector<std::vector<uint8_t>> messages = {
1✔
414
                  Botan::hex_decode("BAADF00D"),
415
                  Botan::hex_decode("DEADBEEF"),
416
                  Botan::hex_decode("CAFEBABE"),
417
               };
4✔
418

419
               // Generate a few signatures, then deallocate the private key.
420
               auto signatures = [&] {
3✔
421
                  auto sk =
1✔
422
                     load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
423
                  Botan::Null_RNG null_rng;
1✔
424
                  Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "PSS(SHA-256)");
1✔
425
                  std::vector<std::vector<uint8_t>> sigs;
1✔
426
                  sigs.reserve(messages.size());
1✔
427
                  for(const auto& message : messages) {
4✔
428
                     sigs.emplace_back(signer.sign_message(message, null_rng));
9✔
429
                  }
430
                  return sigs;
2✔
431
               }();
2✔
432

433
               // verify via TPM 2.0
434
               auto pk = load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
435
               Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
1✔
436
               for(size_t i = 0; i < messages.size(); ++i) {
4✔
437
                  result.confirm(Botan::fmt("verification successful ({})", i),
3✔
438
                                 verifier.verify_message(messages[i], signatures[i]));
3✔
439
               }
440

441
               // verify via software
442
               auto soft_pk = Botan::RSA_PublicKey(pk->algorithm_identifier(), pk->public_key_bits());
1✔
443
               Botan::PK_Verifier soft_verifier(soft_pk, "PSS(SHA-256)");
1✔
444
               for(size_t i = 0; i < messages.size(); ++i) {
4✔
445
                  result.confirm(Botan::fmt("software verification successful ({})", i),
3✔
446
                                 soft_verifier.verify_message(messages[i], signatures[i]));
3✔
447
               }
448
            }),
4✔
449

450
      CHECK("Wrong password is not accepted during signing",
451
            [&](Test::Result& result) {
1✔
452
               auto key = load_persistent<Botan::TPM2::RSA_PrivateKey>(
1✔
453
                  result, ctx, persistent_key_id, Botan::hex_decode("deadbeef"), session);
1✔
454

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

458
               const auto message = Botan::hex_decode("baadcafe");
1✔
459
               result.test_throws<Botan::TPM2::Error>("Fail with wrong password",
2✔
460
                                                      [&] { signer.sign_message(message, null_rng); });
3✔
461
            }),
2✔
462

463
      CHECK("Encrypt a message",
464
            [&](Test::Result& result) {
1✔
465
               auto pk = load_persistent<Botan::TPM2::RSA_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
466
               auto sk =
1✔
467
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
468

469
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
470

471
               // encrypt a message using the TPM's public key
472
               Botan::Null_RNG null_rng;
1✔
473
               Botan::PK_Encryptor_EME enc(*pk, null_rng, "OAEP(SHA-256)");
1✔
474
               const auto ciphertext = enc.encrypt(plaintext, null_rng);
1✔
475

476
               // decrypt the message using the TPM's private RSA key
477
               Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
478
               const auto decrypted = dec.decrypt(ciphertext);
1✔
479
               result.test_eq("decrypted message", decrypted, plaintext);
2✔
480
            }),
5✔
481

482
      CHECK("Decrypt a message",
483
            [&](Test::Result& result) {
1✔
484
               auto key =
1✔
485
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
486

487
               const auto plaintext = Botan::hex_decode("feedface");
1✔
488

489
               // encrypt a message using a software RSA key for the TPM's private key
490
               auto pk = key->public_key();
1✔
491
               auto rng = Test::new_rng("tpm2 rsa decrypt");
1✔
492
               Botan::PK_Encryptor_EME enc(*pk, *rng, "OAEP(SHA-256)");
1✔
493
               const auto ciphertext = enc.encrypt(plaintext, *rng);
1✔
494

495
               // decrypt the message using the TPM's private key
496
               Botan::Null_RNG null_rng;
1✔
497
               Botan::PK_Decryptor_EME dec(*key, null_rng /* TPM takes care of this */, "OAEP(SHA-256)");
1✔
498
               const auto decrypted = dec.decrypt(ciphertext);
1✔
499
               result.test_eq("decrypted message", decrypted, plaintext);
1✔
500

501
               // corrupt the ciphertext and try to decrypt it
502
               auto mutated_ciphertext = Test::mutate_vec(ciphertext, *rng);
1✔
503
               result.test_throws<Botan::Decoding_Error>("Fail with wrong ciphertext",
3✔
504
                                                         [&] { dec.decrypt(mutated_ciphertext); });
2✔
505
            }),
7✔
506

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

512
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
513
               auto sk =
1✔
514
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
515
               auto pk = sk->public_key();
1✔
516

517
               const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
518

519
               // encrypt a message using the TPM's public key
520
               auto rng = Test::new_rng(__func__);
1✔
521
               Botan::PK_Encryptor_EME enc(*pk, *rng, "OAEP(SHA-256)");
1✔
522
               const auto ciphertext = enc.encrypt(plaintext, *rng);
1✔
523

524
               // decrypt the message using the TPM's private RSA key
525
               Botan::Null_RNG null_rng;
1✔
526
               Botan::PK_Decryptor_EME dec(*sk, null_rng, "OAEP(SHA-256)");
1✔
527
               const auto decrypted = dec.decrypt(ciphertext);
1✔
528
               result.test_eq("decrypted message", decrypted, plaintext);
1✔
529

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

534
               // decrypt the message using the TPM's private RSA key (using PKCS#1)
535
               Botan::PK_Decryptor_EME dec_pkcs(*sk, null_rng, "PKCS1v15");
1✔
536
               const auto decrypted_pkcs = dec_pkcs.decrypt(ciphertext_pkcs);
1✔
537
               result.test_eq("decrypted message", decrypted_pkcs, plaintext);
2✔
538
            }),
10✔
539

540
      CHECK("Cannot export private key blob from persistent key",
541
            [&](Test::Result& result) {
1✔
542
               auto key =
1✔
543
                  load_persistent<Botan::TPM2::RSA_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
544
               result.test_throws<Botan::Not_Implemented>("Export private key blob not implemented",
2✔
545
                                                          [&] { key->private_key_bits(); });
2✔
546
               result.test_throws<Botan::Invalid_State>("Export raw private key blob not implemented",
3✔
547
                                                        [&] { key->raw_private_key_bits(); });
2✔
548
            }),
1✔
549

550
      CHECK("Create a new transient key",
551
            [&](Test::Result& result) {
1✔
552
               auto srk = ctx->storage_root_key({}, {});
2✔
553

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

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

558
               auto sk =
1✔
559
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
560

561
               result.require("key was created", sk != nullptr);
1✔
562
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
563
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
564

565
               const auto sk_blob = sk->raw_private_key_bits();
1✔
566
               const auto pk_blob = sk->raw_public_key_bits();
1✔
567
               const auto pk = sk->public_key();
1✔
568

569
               result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
570
               result.confirm("public blob is not empty", !pk_blob.empty());
2✔
571

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

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

582
               // Destruct the key and load it again from the encrypted blob
583
               sk.reset();
1✔
584
               auto sk_loaded =
1✔
585
                  Botan::TPM2::PrivateKey::load_transient(ctx, secret, *srk, pk_blob, sk_blob, authed_session);
2✔
586
               result.require("key was loaded", sk_loaded != nullptr);
1✔
587
               result.test_eq("loaded key is RSA", sk_loaded->algo_name(), "RSA");
2✔
588

589
               const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
590
               const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
591

592
               result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
593
               result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
594

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

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

607
               Botan::PK_Verifier verifier_loaded(*pk_loaded, "PSS(SHA-256)");
1✔
608
               result.confirm("TPM-verified signature is valid",
2✔
609
                              verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
610

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

619
               // Create a verifier for PKCS#1
620
               Botan::PK_Verifier verifier_pkcs(*pk_loaded, "PKCS1v15(SHA-256)");
1✔
621
               result.confirm("TPM-verified signature is valid",
2✔
622
                              verifier_pkcs.verify_message(message_pkcs, signature_pkcs));
1✔
623
            }),
16✔
624

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

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

636
                  auto pk = key.public_key();
2✔
637
                  Botan::PK_Verifier verifier(*pk, "PSS(SHA-256)");
2✔
638
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
4✔
639
               };
8✔
640

641
               // Create Key
642
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *srk);
1✔
643

644
               const std::array<uint8_t, 6> secret = {'s', 'e', 'c', 'r', 'e', 't'};
1✔
645
               auto sk =
1✔
646
                  Botan::TPM2::RSA_PrivateKey::create_unrestricted_transient(ctx, authed_session, secret, *srk, 2048);
2✔
647
               result.require("key was created", sk != nullptr);
1✔
648
               result.confirm("is transient", sk->handles().has_transient_handle());
2✔
649
               result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
650
               result.test_no_throw("use key after creation", [&] { sign_verify_roundtrip(*sk); });
3✔
651

652
               // Make it persistent
653
               const auto handles = ctx->persistent_handles().size();
1✔
654
               const auto new_location = ctx->persist(*sk, authed_session, secret);
2✔
655
               result.test_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
2✔
656
               result.confirm("New location occupied", Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
657
               result.confirm("is persistent", sk->handles().has_persistent_handle());
2✔
658
               result.test_is_eq(
1✔
659
                  "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
660
               result.test_throws<Botan::Invalid_Argument>(
2✔
661
                  "Cannot persist to the same location", [&] { ctx->persist(*sk, authed_session, {}, new_location); });
2✔
662
               result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key",
2✔
663
                                                           [&] { ctx->persist(*sk, authed_session); });
2✔
664
               result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
3✔
665

666
               // Evict it
667
               ctx->evict(std::move(sk), authed_session);
3✔
668
               result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
669
               result.confirm("New location no longer occupied",
1✔
670
                              !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
671
            }),
3✔
672
   };
13✔
673
}
4✔
674

675
   #endif
676

677
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
678
template <typename KeyT>
679
auto load_persistent_ecc(Test::Result& result,
31✔
680
                         const std::shared_ptr<Botan::TPM2::Context>& ctx,
681
                         uint32_t persistent_key_id,
682
                         std::span<const uint8_t> auth_value,
683
                         const std::shared_ptr<Botan::TPM2::Session>& session) {
684
   // TODO: Merge with RSA
685
   const auto persistent_handles = ctx->persistent_handles();
31✔
686
   result.confirm(
62✔
687
      "Persistent key available",
688
      std::find(persistent_handles.begin(), persistent_handles.end(), persistent_key_id) != persistent_handles.end());
31✔
689

690
   auto key = [&] {
62✔
691
      if constexpr(std::same_as<Botan::TPM2::EC_PublicKey, KeyT>) {
692
         return KeyT::load_persistent(ctx, persistent_key_id, session);
10✔
693
      } else {
694
         return KeyT::load_persistent(ctx, persistent_key_id, auth_value, session);
52✔
695
      }
696
   }();
697

698
   result.test_eq("Algo", key->algo_name(), "ECDSA");
62✔
699
   result.test_is_eq("Handle", key->handles().persistent_handle(), persistent_key_id);
62✔
700
   return key;
31✔
701
}
31✔
702

703
std::vector<Test::Result> test_tpm2_ecc() {
1✔
704
   //TODO: Merge with RSA?
705
   auto ctx = get_tpm2_context(__func__);
1✔
706
   if(!ctx) {
1✔
707
      return {bail_out()};
×
708
   }
709

710
   auto session = Botan::TPM2::Session::unauthenticated_session(ctx);
1✔
711

712
   const auto persistent_key_id = Test::options().tpm2_persistent_ecc_handle();
1✔
713
   const auto password = Test::options().tpm2_persistent_auth_value();
1✔
714

715
   return {
1✔
716
      CHECK("ECC and its helpers are supported",
717
            [&](Test::Result& result) {
1✔
718
               result.confirm("ECC is supported", ctx->supports_algorithm("ECC"));
2✔
719
               result.confirm("ECDSA is supported", ctx->supports_algorithm("ECDSA"));
2✔
720
            }),
1✔
721
         CHECK("Load the private key multiple times",
722
               [&](Test::Result& result) {
1✔
723
                  for(size_t i = 0; i < 20; ++i) {
21✔
724
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
20✔
725
                        result, ctx, persistent_key_id, password, session);
20✔
726
                     result.test_eq(Botan::fmt("Key loaded successfully ({})", i), key->algo_name(), "ECDSA");
40✔
727
                  }
20✔
728
               }),
1✔
729
         CHECK("Sign a message ECDSA",
730
               [&](Test::Result& result) {
1✔
731
                  auto key =
1✔
732
                     load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
733

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

737
                  // create a message that is larger than the TPM2 max buffer size
738
                  const auto message = [] {
3✔
739
                     std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
740
                     for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
741
                        msg[i] = static_cast<uint8_t>(i);
1,029✔
742
                     }
743
                     return msg;
1✔
744
                  }();
1✔
745
                  const auto signature = signer.sign_message(message, null_rng);
1✔
746
                  result.require("signature is not empty", !signature.empty());
1✔
747

748
                  auto public_key = key->public_key();
1✔
749
                  Botan::PK_Verifier verifier(*public_key, "SHA-256");
1✔
750
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
751
               }),
5✔
752
         CHECK("verify signature ECDSA",
753
               [&](Test::Result& result) {
1✔
754
                  auto sign = [&](std::span<const uint8_t> message) {
2✔
755
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
756
                        result, ctx, persistent_key_id, password, session);
1✔
757
                     Botan::Null_RNG null_rng;
1✔
758
                     Botan::PK_Signer signer(*key, null_rng /* TPM takes care of this */, "SHA-256");
1✔
759
                     return signer.sign_message(message, null_rng);
1✔
760
                  };
2✔
761

762
                  auto verify = [&](std::span<const uint8_t> msg, std::span<const uint8_t> sig) {
4✔
763
                     auto key = load_persistent_ecc<Botan::TPM2::EC_PublicKey>(
3✔
764
                        result, ctx, persistent_key_id, password, session);
3✔
765
                     Botan::PK_Verifier verifier(*key, "SHA-256");
3✔
766
                     return verifier.verify_message(msg, sig);
3✔
767
                  };
6✔
768

769
                  const auto message = Botan::hex_decode("baadcafe");
1✔
770
                  const auto signature = sign(message);
1✔
771

772
                  result.confirm("verification successful", verify(message, signature));
2✔
773

774
                  // change the message
775
                  auto rng = Test::new_rng(__func__);
1✔
776
                  auto mutated_message = Test::mutate_vec(message, *rng);
1✔
777
                  result.confirm("verification failed", !verify(mutated_message, signature));
2✔
778

779
                  // ESAPI manipulates the session attributes internally and does
780
                  // not reset them when an error occurs. A failure to validate a
781
                  // signature is an error, and hence behaves surprisingly by
782
                  // leaving the session attributes in an unexpected state.
783
                  // The Botan wrapper has a workaround for this...
784
                  const auto attrs = session->attributes();
1✔
785
                  result.confirm("encrypt flag was not cleared by ESAPI", attrs.encrypt);
2✔
786

787
                  // orignal message again
788
                  result.confirm("verification still successful", verify(message, signature));
2✔
789
               }),
4✔
790

791
         CHECK("sign and verify multiple messages with the same Signer/Verifier objects",
792
               [&](Test::Result& result) {
1✔
793
                  const std::vector<std::vector<uint8_t>> messages = {
1✔
794
                     Botan::hex_decode("BAADF00D"),
795
                     Botan::hex_decode("DEADBEEF"),
796
                     Botan::hex_decode("CAFEBABE"),
797
                  };
4✔
798

799
                  // Generate a few signatures, then deallocate the private key.
800
                  auto signatures = [&] {
3✔
801
                     auto sk = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
802
                        result, ctx, persistent_key_id, password, session);
1✔
803
                     Botan::Null_RNG null_rng;
1✔
804
                     Botan::PK_Signer signer(*sk, null_rng /* TPM takes care of this */, "SHA-256");
1✔
805
                     std::vector<std::vector<uint8_t>> sigs;
1✔
806
                     sigs.reserve(messages.size());
1✔
807
                     for(const auto& message : messages) {
4✔
808
                        sigs.emplace_back(signer.sign_message(message, null_rng));
9✔
809
                     }
810
                     return sigs;
2✔
811
                  }();
2✔
812

813
                  // verify via TPM 2.0
814
                  auto pk =
1✔
815
                     load_persistent_ecc<Botan::TPM2::EC_PublicKey>(result, ctx, persistent_key_id, password, session);
1✔
816
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
1✔
817
                  for(size_t i = 0; i < messages.size(); ++i) {
4✔
818
                     result.confirm(Botan::fmt("verification successful ({})", i),
3✔
819
                                    verifier.verify_message(messages[i], signatures[i]));
3✔
820
                  }
821

822
                  // verify via software
823
                  auto soft_pk =
1✔
824
                     load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session)
1✔
825
                        ->public_key();
1✔
826
                  Botan::PK_Verifier soft_verifier(*soft_pk, "SHA-256");
1✔
827
                  for(size_t i = 0; i < messages.size(); ++i) {
4✔
828
                     result.confirm(Botan::fmt("software verification successful ({})", i),
3✔
829
                                    soft_verifier.verify_message(messages[i], signatures[i]));
3✔
830
                  }
831
               }),
5✔
832

833
         CHECK("Wrong password is not accepted during ECDSA signing",
834
               [&](Test::Result& result) {
1✔
835
                  auto key = load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(
1✔
836
                     result, ctx, persistent_key_id, Botan::hex_decode("deadbeef"), session);
1✔
837

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

841
                  const auto message = Botan::hex_decode("baadcafe");
1✔
842
                  result.test_throws<Botan::TPM2::Error>("Fail with wrong password",
2✔
843
                                                         [&] { signer.sign_message(message, null_rng); });
3✔
844
               }),
2✔
845

846
      // SRK is an RSA key, so we can only test with the RSA adapter
847
      #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
848
         CHECK("Create a transient ECDSA key and sign/verify a message",
849
               [&](Test::Result& result) {
1✔
850
                  auto srk = ctx->storage_root_key({}, {});
2✔
851
                  auto ecc_session_key =
1✔
852
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
853
                  auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
854

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

860
                  const auto plaintext = Botan::hex_decode("feedc0debaadcafe");
1✔
861

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

865
                  // create a message that is larger than the TPM2 max buffer size
866
                  const auto message = [] {
3✔
867
                     std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
1✔
868
                     for(size_t i = 0; i < msg.size(); ++i) {
1,030✔
869
                        msg[i] = static_cast<uint8_t>(i);
1,029✔
870
                     }
871
                     return msg;
1✔
872
                  }();
1✔
873
                  const auto signature = signer.sign_message(message, null_rng);
1✔
874
                  result.require("signature is not empty", !signature.empty());
1✔
875

876
                  auto public_key = sk->public_key();
1✔
877
                  Botan::PK_Verifier verifier(*public_key, "SHA-256");
1✔
878
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
2✔
879
               }),
10✔
880

881
         CHECK("Create a new transient ECDSA key",
882
               [&](Test::Result& result) {
1✔
883
                  auto srk = ctx->storage_root_key({}, {});
2✔
884
                  auto ecc_session_key =
1✔
885
                     Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
886

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

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

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

894
                  result.require("key was created", sk != nullptr);
1✔
895
                  result.confirm("is transient", sk->handles().has_transient_handle());
2✔
896
                  result.confirm("is not persistent", !sk->handles().has_persistent_handle());
2✔
897

898
                  const auto sk_blob = sk->raw_private_key_bits();
1✔
899
                  const auto pk_blob = sk->raw_public_key_bits();
1✔
900
                  const auto pk = sk->public_key();
1✔
901

902
                  result.confirm("secret blob is not empty", !sk_blob.empty());
2✔
903
                  result.confirm("public blob is not empty", !pk_blob.empty());
2✔
904

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

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

915
                  // Destruct the key and load it again from the encrypted blob
916
                  sk.reset();
1✔
917
                  auto sk_loaded =
1✔
918
                     Botan::TPM2::PrivateKey::load_transient(ctx, secret, *srk, pk_blob, sk_blob, authed_session);
2✔
919
                  result.require("key was loaded", sk_loaded != nullptr);
1✔
920
                  result.test_eq("loaded key is ECDSA", sk_loaded->algo_name(), "ECDSA");
2✔
921

922
                  const auto sk_blob_loaded = sk_loaded->raw_private_key_bits();
1✔
923
                  const auto pk_blob_loaded = sk_loaded->raw_public_key_bits();
1✔
924

925
                  result.test_is_eq("secret blob did not change", sk_blob, sk_blob_loaded);
1✔
926
                  result.test_is_eq("public blob did not change", pk_blob, pk_blob_loaded);
1✔
927

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

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

940
                  Botan::PK_Verifier verifier_loaded(*pk_loaded, "SHA-256");
1✔
941
                  result.confirm("TPM-verified signature is valid",
2✔
942
                                 verifier_loaded.verify_message(message_loaded, signature_loaded));
1✔
943
               }),
15✔
944

945
         CHECK(
946
            "Make a transient ECDSA key persistent then remove it again",
947
            [&](Test::Result& result) {
1✔
948
               auto srk = ctx->storage_root_key({}, {});
2✔
949
               auto ecc_session_key = Botan::TPM2::EC_PrivateKey::load_persistent(ctx, persistent_key_id, password, {});
2✔
950

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

958
                  auto pk = key.public_key();
2✔
959
                  Botan::PK_Verifier verifier(*pk, "SHA-256");
2✔
960
                  result.confirm("Signature is valid", verifier.verify_message(message, signature));
4✔
961
               };
8✔
962

963
               // Create Key
964
               auto authed_session = Botan::TPM2::Session::authenticated_session(ctx, *ecc_session_key);
1✔
965

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

974
               // Make it persistent
975
               const auto handles = ctx->persistent_handles().size();
1✔
976
               const auto new_location = ctx->persist(*sk, authed_session, secret);
2✔
977
               result.test_eq("One more handle", ctx->persistent_handles().size(), handles + 1);
2✔
978
               result.confirm("New location occupied", Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
979
               result.confirm("is persistent", sk->handles().has_persistent_handle());
2✔
980
               result.test_is_eq(
1✔
981
                  "Persistent handle is the new handle", sk->handles().persistent_handle(), new_location);
1✔
982
               result.test_throws<Botan::Invalid_Argument>(
2✔
983
                  "Cannot persist to the same location", [&] { ctx->persist(*sk, authed_session, {}, new_location); });
2✔
984
               result.test_throws<Botan::Invalid_Argument>("Cannot persist and already persistent key",
2✔
985
                                                           [&] { ctx->persist(*sk, authed_session); });
2✔
986
               result.test_no_throw("use key after persisting", [&] { sign_verify_roundtrip(*sk); });
3✔
987

988
               // Evict it
989
               ctx->evict(std::move(sk), authed_session);
3✔
990
               result.test_eq("One less handle", ctx->persistent_handles().size(), handles);
2✔
991
               result.confirm("New location no longer occupied",
1✔
992
                              !Botan::value_exists(ctx->persistent_handles(), new_location));
3✔
993
            }),
4✔
994
      #endif
995

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

1002
            auto sk =
1✔
1003
               load_persistent_ecc<Botan::TPM2::EC_PrivateKey>(result, ctx, persistent_key_id, password, session);
1✔
1004
            result.test_no_throw("Botan can read serialized public key from ECC private key", [&] {
3✔
1005
               auto sk_sw = Botan::ECDSA_PublicKey(sk->algorithm_identifier(), sk->public_key_bits());
2✔
1006
            });
1✔
1007
         }),
2✔
1008
   };
11✔
1009
}
4✔
1010
   #endif
1011

1012
std::vector<Test::Result> test_tpm2_hash() {
1✔
1013
   auto ctx = get_tpm2_context(__func__);
1✔
1014
   if(!ctx) {
1✔
1015
      return {bail_out()};
×
1016
   }
1017

1018
   auto test = [&](Test::Result& result, std::string_view algo) {
8✔
1019
      auto tpm_hash = [&]() -> std::unique_ptr<Botan::TPM2::HashFunction> {
21✔
1020
         try {
7✔
1021
            return std::make_unique<Botan::TPM2::HashFunction>(
7✔
1022
               ctx, algo, ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
7✔
1023
         } catch(const Botan::Lookup_Error&) {
3✔
1024
            return {};
3✔
1025
         }
3✔
1026
      }();
7✔
1027
      auto soft_hash = Botan::HashFunction::create(algo);
7✔
1028

1029
      if(!tpm_hash) {
7✔
1030
         result.test_note(Botan::fmt("Skipping {}, TPM 2.0 does not support it", algo));
3✔
1031
         return;
3✔
1032
      }
1033

1034
      if(!soft_hash) {
4✔
1035
         result.test_note(Botan::fmt("Skipping {}, no software equivalent available", algo));
×
1036
         return;
×
1037
      }
1038

1039
      result.test_eq("Name", tpm_hash->name(), soft_hash->name());
8✔
1040
      result.test_eq("Output length", tpm_hash->output_length(), soft_hash->output_length());
4✔
1041

1042
      // multiple update calls
1043
      tpm_hash->update("Hello, ");
4✔
1044
      tpm_hash->update("world!");
4✔
1045
      result.test_eq("digest (multi-update)", tpm_hash->final(), soft_hash->process("Hello, world!"));
12✔
1046

1047
      // single process call
1048
      result.test_eq("digest (single-process)", tpm_hash->process("Hallo, Welt."), soft_hash->process("Hallo, Welt."));
12✔
1049

1050
      // create a message that is larger than the TPM2 max buffer size
1051
      const auto long_message = [] {
×
1052
         std::vector<uint8_t> msg(TPM2_MAX_DIGEST_BUFFER + 5);
4✔
1053
         for(size_t i = 0; i < msg.size(); ++i) {
4,120✔
1054
            msg[i] = static_cast<uint8_t>(i);
4,116✔
1055
         }
1056
         return msg;
4✔
1057
      }();
4✔
1058

1059
      tpm_hash->update(long_message);
4✔
1060
      result.test_eq("digest (long msg via update)", tpm_hash->final(), soft_hash->process(long_message));
12✔
1061
      result.test_eq(
12✔
1062
         "digest (long msg via process)", tpm_hash->process(long_message), soft_hash->process(long_message));
8✔
1063

1064
      // test clear
1065
      tpm_hash->update("Hello");
4✔
1066
      tpm_hash->clear();
4✔
1067
      tpm_hash->update("Bonjour");
4✔
1068
      result.test_eq("digest (clear)", tpm_hash->final(), soft_hash->process("Bonjour"));
12✔
1069

1070
      // new_object
1071
      auto new_tpm_hash = tpm_hash->new_object();
4✔
1072
      result.test_eq("Name (new_object)", new_tpm_hash->name(), tpm_hash->name());
8✔
1073
      result.test_eq("Output length (new_object)", new_tpm_hash->output_length(), tpm_hash->output_length());
4✔
1074
      result.test_eq("digest (new object)",
12✔
1075
                     new_tpm_hash->process("Salut tout le monde!"),
8✔
1076
                     soft_hash->process("Salut tout le monde!"));
8✔
1077
   };
15✔
1078

1079
   return {
1✔
1080
      CHECK("Hashes are supported",
1081
            [&](Test::Result& result) {
1✔
1082
               result.confirm("SHA-1 is supported", ctx->supports_algorithm("SHA-1"));
2✔
1083
               result.confirm("SHA-256 is supported", ctx->supports_algorithm("SHA-256"));
2✔
1084
               result.confirm("SHA-384 is supported", ctx->supports_algorithm("SHA-384"));
2✔
1085
               result.confirm("SHA-512 is supported", ctx->supports_algorithm("SHA-512"));
2✔
1086
            }),
1✔
1087

1088
      CHECK("SHA-1", [&](Test::Result& result) { test(result, "SHA-1"); }),
1✔
1089
      CHECK("SHA-256", [&](Test::Result& result) { test(result, "SHA-256"); }),
1✔
1090
      CHECK("SHA-384", [&](Test::Result& result) { test(result, "SHA-384"); }),
1✔
1091
      CHECK("SHA-512", [&](Test::Result& result) { test(result, "SHA-512"); }),
1✔
1092
      CHECK("SHA-3(256)", [&](Test::Result& result) { test(result, "SHA-3(256)"); }),
1✔
1093
      CHECK("SHA-3(384)", [&](Test::Result& result) { test(result, "SHA-3(384)"); }),
1✔
1094
      CHECK("SHA-3(512)", [&](Test::Result& result) { test(result, "SHA-3(512)"); }),
1✔
1095

1096
      CHECK("lookup error",
1097
            [&](Test::Result& result) {
1✔
1098
               result.test_throws<Botan::Lookup_Error>(
2✔
1099
                  "Lookup error", [&] { [[maybe_unused]] auto _ = Botan::TPM2::HashFunction(ctx, "MD-5"); });
7✔
1100
            }),
1✔
1101

1102
      CHECK("copy_state is not implemented",
1103
            [&](Test::Result& result) {
1✔
1104
               auto tpm_hash = Botan::TPM2::HashFunction(ctx, "SHA-256");
3✔
1105
               result.test_throws<Botan::Not_Implemented>("TPM2 hash does not support copy_state",
2✔
1106
                                                          [&] { [[maybe_unused]] auto _ = tpm_hash.copy_state(); });
2✔
1107
            }),
1✔
1108

1109
      CHECK("validation ticket",
1110
            [&](Test::Result& result) {
1✔
1111
               // using the NULL hierarchy essentially disables the validation ticket
1112
               auto tpm_hash_null = Botan::TPM2::HashFunction(
1✔
1113
                  ctx, "SHA-256", ESYS_TR_RH_NULL, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1114
               tpm_hash_null.update("Hola mundo!");
1✔
1115
               const auto [digest_null, ticket_null] = tpm_hash_null.final_with_ticket();
1✔
1116
               result.require("digest is set", digest_null != nullptr);
1✔
1117
               result.require("ticket is set", ticket_null != nullptr);
1✔
1118
               result.confirm("ticket is empty", ticket_null->digest.size == 0);
2✔
1119

1120
               // using the OWNER hierarchy (for instance) enables the validation ticket
1121
               auto tpm_hash_owner = Botan::TPM2::HashFunction(
1✔
1122
                  ctx, "SHA-256", ESYS_TR_RH_OWNER, Botan::TPM2::Session::unauthenticated_session(ctx));
3✔
1123
               tpm_hash_owner.update("Hola mundo!");
1✔
1124
               const auto [digest_owner, ticket_owner] = tpm_hash_owner.final_with_ticket();
1✔
1125
               result.require("digest is set", digest_owner != nullptr);
1✔
1126
               result.require("ticket is set", ticket_owner != nullptr);
1✔
1127
               result.confirm("ticket is not empty", ticket_owner->digest.size > 0);
2✔
1128

1129
               const auto digest_vec = Botan::TPM2::copy_into<Botan::secure_vector<uint8_t>>(*digest_owner);
1✔
1130
               result.test_eq("digest",
2✔
1131
                              digest_vec,
1132
                              Botan::hex_decode("1e479f4d871e59e9054aad62105a259726801d5f494acbfcd40591c82f9b3136"));
1✔
1133

1134
               result.test_eq("digests are the same, regardless of ticket",
2✔
1135
                              Botan::TPM2::copy_into<std::vector<uint8_t>>(*digest_null),
2✔
1136
                              digest_vec);
1137
            }),
1✔
1138
   };
12✔
1139
}
2✔
1140

1141
}  // namespace
1142

1143
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_props", test_tpm2_properties);
1144
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ctx", test_tpm2_context);
1145
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_sessions", test_tpm2_sessions);
1146
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rng", test_tpm2_rng);
1147
   #if defined(BOTAN_HAS_TPM2_RSA_ADAPTER)
1148
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_rsa", test_tpm2_rsa);
1149
   #endif
1150
   #if defined(BOTAN_HAS_TPM2_ECC_ADAPTER)
1151
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_ecc", test_tpm2_ecc);
1152
   #endif
1153
BOTAN_REGISTER_TEST_FN("tpm2", "tpm2_hash", test_tpm2_hash);
1154

1155
#endif
1156

1157
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc