Botan 3.13.0
Crypto and TLS for C&
tpm2_crypto_backend_impl.cpp
Go to the documentation of this file.
1/*
2* TPM 2 TSS crypto callbacks backend
3* (C) 2024 Jack Lloyd
4* (C) 2024 René Meusel, Amos Treiber - Rohde & Schwarz Cybersecurity GmbH, financed by LANCOM Systems GmbH
5*
6* Botan is released under the Simplified BSD License (see license.txt)
7*/
8
9#include <botan/internal/tpm2_crypto_backend_impl.h>
10
11#include <botan/cipher_mode.h>
12#include <botan/hash.h>
13#include <botan/mac.h>
14#include <botan/mem_ops.h>
15#include <botan/pubkey.h>
16#include <botan/tpm2_context.h>
17#include <botan/tpm2_crypto_backend.h>
18#include <botan/tpm2_key.h>
19
20#if defined(BOTAN_HAS_RSA)
21 #include <botan/rsa.h>
22#endif
23
24#if defined(BOTAN_HAS_ECDH)
25 #include <botan/ecdh.h>
26#endif
27
28#include <botan/internal/enc_padding.h>
29#include <botan/internal/fmt.h>
30#include <botan/internal/mem_utils.h>
31#include <botan/internal/tpm2_algo_mappings.h>
32#include <botan/internal/tpm2_util.h>
33
34#if defined(BOTAN_HAS_EME_OAEP)
35 #include <botan/internal/oaep.h>
36#endif
37
38#include <tss2/tss2_esys.h>
39#include <tss2/tss2_mu.h>
40
41#include <variant>
42
43#if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
44
45namespace {
46
47/// Holds the hash state between update callback invocations
48using DigestObject =
49 std::variant<std::unique_ptr<Botan::HashFunction>, std::unique_ptr<Botan::MessageAuthenticationCode>>;
50
51} // namespace
52
53extern "C" {
54
55/**
56 * Some ESYS crypto callbacks require to hold state between calls.
57 * This struct is forward-declared in tss2_esys.h and we're implementing it here.
58 */
59typedef struct ESYS_CRYPTO_CONTEXT_BLOB {
60 DigestObject ctx;
61} DigestCallbackState;
62
63} // extern "C"
64
65namespace {
66
67/// Safely converts the @p blob to a Botan crypto object of type @p T.
68template <typename T>
69 requires std::constructible_from<DigestObject, std::unique_ptr<T>>
70[[nodiscard]] std::optional<std::reference_wrapper<T>> get(DigestCallbackState* blob) noexcept {
71 if(!blob) {
72 return std::nullopt;
73 }
74
75 auto* ctx = std::get_if<std::unique_ptr<T>>(&blob->ctx);
76 if(ctx == nullptr) {
77 return std::nullopt;
78 }
79
80 return {std::ref(**ctx)};
81}
82
83template <typename T>
84 requires std::constructible_from<DigestObject, std::unique_ptr<T>>
85[[nodiscard]] std::optional<std::reference_wrapper<T>> get(DigestCallbackState** blob) noexcept {
86 if(!blob) {
87 return std::nullopt;
88 }
89
90 return get<T>(*blob);
91}
92
93/// Safely converts the @p userdata to the Botan crypto context object.
94[[nodiscard]] std::optional<std::reference_wrapper<Botan::TPM2::CryptoCallbackState>> get(void* userdata) noexcept {
95 if(auto* ccs = reinterpret_cast<Botan::TPM2::CryptoCallbackState*>(userdata)) {
96 return *ccs;
97 } else {
98 return std::nullopt;
99 }
100}
101
102/**
103 * Wraps the Botan-specific implementations of the TSS crypto callbacks into a
104 * try-catch block and converts encountered exceptions to TSS2_RC error codes as
105 * needed.
106 */
107template <std::invocable<> F>
108 requires std::same_as<std::invoke_result_t<F>, TSS2_RC>
109[[nodiscard]] TSS2_RC thunk(F f) noexcept {
110 try {
111 return f();
112 } catch(const Botan::Invalid_Argument&) {
113 return TSS2_ESYS_RC_BAD_VALUE;
114 } catch(const Botan::Invalid_State&) {
115 return TSS2_ESYS_RC_BAD_SEQUENCE;
116 } catch(const Botan::Lookup_Error&) {
117 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
118 } catch(const Botan::Invalid_Authentication_Tag&) {
119 return TSS2_ESYS_RC_MALFORMED_RESPONSE;
120 } catch(const Botan::Exception&) {
121 return TSS2_ESYS_RC_GENERAL_FAILURE;
122 } catch(...) {
123 return TSS2_ESYS_RC_GENERAL_FAILURE;
124 }
125}
126
127/**
128 * Encrypts or decrypts @p data using the symmetric cipher specified.
129 * The bytes in @p data are encrypted/decrypted in-place.
130 */
131[[nodiscard]] TSS2_RC symmetric_algo(Botan::Cipher_Dir direction,
132 const uint8_t* key,
133 TPM2_ALG_ID tpm_sym_alg,
134 TPMI_AES_KEY_BITS key_bits,
135 TPM2_ALG_ID tpm_mode,
136 uint8_t* buffer,
137 size_t buffer_size,
138 const uint8_t* iv) noexcept {
139 return thunk([&] {
140 if(key == nullptr) {
141 return (direction == Botan::Cipher_Dir::Encryption) ? TSS2_ESYS_RC_NO_ENCRYPT_PARAM
142 : TSS2_ESYS_RC_NO_DECRYPT_PARAM;
143 }
144
145 // nullptr buffer with size 0 is alright
146 if(buffer == nullptr && buffer_size != 0) {
147 return TSS2_ESYS_RC_BAD_VALUE;
148 }
149
150 const auto cipher_name = Botan::TPM2::cipher_tss2_to_botan({
151 .algorithm = tpm_sym_alg,
152 .keyBits = {.sym = key_bits},
153 .mode = {.sym = tpm_mode},
154 });
155 if(!cipher_name) {
156 return TSS2_ESYS_RC_NOT_SUPPORTED;
157 }
158
159 auto cipher = Botan::Cipher_Mode::create(cipher_name.value(), direction);
160 if(!cipher) {
161 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
162 }
163
164 // AEADs aren't supported by the crypto callback API, as there's
165 // no way to append the authentication tag to the ciphertext.
166 if(cipher->authenticated()) {
167 return TSS2_ESYS_RC_INSUFFICIENT_BUFFER;
168 }
169
170 BOTAN_ASSERT_NOMSG(key_bits % 8 == 0);
171 const size_t keylength = static_cast<size_t>(key_bits) / 8;
172 if(!cipher->valid_keylength(keylength)) {
173 return TSS2_ESYS_RC_BAD_VALUE;
174 }
175
176 const auto s_data = std::span{buffer, buffer_size};
177 const auto s_key = std::span{key, keylength};
178 const auto s_iv = [&]() -> std::span<const uint8_t> {
179 if(iv != nullptr) {
180 return {iv, cipher->default_nonce_length()};
181 } else {
182 return {};
183 }
184 }();
185
186 cipher->set_key(s_key);
187 cipher->start(s_iv);
188 cipher->process(s_data);
189 return TSS2_RC_SUCCESS;
190 });
191}
192
193extern "C" {
194
195/** Provide the context for the computation of a hash digest.
196 *
197 * The context will be created and initialized according to the hash function.
198 * @param[out] context The created context (callee-allocated).
199 * @param[in] hash_alg The hash algorithm for the creation of the context.
200 * @param[in,out] userdata information.
201 * @retval TSS2_RC_SUCCESS on success.
202 * @retval USER_DEFINED user defined errors on failure.
203 */
204TSS2_RC hash_start(ESYS_CRYPTO_CONTEXT_BLOB** context, TPM2_ALG_ID hash_alg, void* userdata) {
205 BOTAN_UNUSED(userdata);
206 return thunk([&] {
207 if(context == nullptr) {
208 return TSS2_ESYS_RC_BAD_REFERENCE;
209 }
210
211 const auto hash_name = Botan::TPM2::hash_algo_tss2_to_botan(hash_alg);
212 if(!hash_name) {
213 return TSS2_ESYS_RC_NOT_SUPPORTED;
214 }
215
216 auto hash = Botan::HashFunction::create(hash_name.value());
217 if(hash == nullptr) {
218 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
219 }
220
221 // Will be deleted in hash_abort() or hash_finish()
222 *context = new DigestCallbackState{std::move(hash)}; // NOLINT(*-owning-memory)
223 return TSS2_RC_SUCCESS;
224 });
225}
226
227/** Update the digest value of a digest object from a byte buffer.
228 *
229 * The context of a digest object will be updated according to the hash
230 * algorithm of the context. <
231 * @param[in,out] context The context of the digest object which will be updated.
232 * @param[in] buffer The data for the update.
233 * @param[in] size The size of the data buffer.
234 * @param[in,out] userdata information.
235 * @retval TSS2_RC_SUCCESS on success.
236 * @retval USER_DEFINED user defined errors on failure.
237 */
238TSS2_RC hash_update(ESYS_CRYPTO_CONTEXT_BLOB* context, const uint8_t* buffer, size_t size, void* userdata) {
239 BOTAN_UNUSED(userdata);
240 return thunk([&] {
241 const auto hash = get<Botan::HashFunction>(context);
242 if(!hash) {
243 return TSS2_ESYS_RC_BAD_REFERENCE;
244 }
245
246 // nullptr buffer with size 0 is alright
247 if(buffer == nullptr && size != 0) {
248 return TSS2_ESYS_RC_BAD_VALUE;
249 }
250
251 hash->get().update(std::span{buffer, size});
252 return TSS2_RC_SUCCESS;
253 });
254}
255
256/** Get the digest value of a digest object and close the context.
257 *
258 * The digest value will written to a passed buffer and the resources of the
259 * digest object are released.
260 * @param[in,out] context The context of the digest object to be released
261 * @param[out] buffer The buffer for the digest value (caller-allocated).
262 * @param[out] size The size of the digest.
263 * @param[in,out] userdata information.
264 * @retval TSS2_RC_SUCCESS on success.
265 * @retval USER_DEFINED user defined errors on failure.
266 */
267TSS2_RC hash_finish(ESYS_CRYPTO_CONTEXT_BLOB** context, uint8_t* buffer, size_t* size, void* userdata) {
268 BOTAN_UNUSED(userdata);
269 if(size != nullptr) {
270 *size = 0;
271 }
272
273 return thunk([&] {
274 auto hash = get<Botan::HashFunction>(context);
275 if(!hash || buffer == nullptr) {
276 return TSS2_ESYS_RC_BAD_REFERENCE;
277 }
278
279 const auto digest_size = hash->get().output_length();
280 hash->get().final(std::span{buffer, digest_size});
281 if(size != nullptr) {
282 *size = digest_size;
283 }
284
285 // allocated in hash_start()
286 delete *context; // NOLINT(*-owning-memory)
287 *context = nullptr;
288 return TSS2_RC_SUCCESS;
289 });
290}
291
292/** Release the resources of a digest object.
293 *
294 * The assigned resources will be released and the context will be set to NULL.
295 * @param[in,out] context The context of the digest object.
296 * @param[in,out] userdata information.
297 */
298void hash_abort(ESYS_CRYPTO_CONTEXT_BLOB** context, void* userdata) {
299 BOTAN_UNUSED(userdata);
300 if(context != nullptr) {
301 // allocated in hash_start()
302 delete *context; // NOLINT(*-owning-memory)
303 *context = nullptr;
304 }
305}
306
307/** Provide the context an HMAC digest object from a byte buffer key.
308 *
309 * The context will be created and initialized according to the hash function
310 * and the used HMAC key.
311 * @param[out] context The created context (callee-allocated).
312 * @param[in] hash_alg The hash algorithm for the HMAC computation.
313 * @param[in] key The byte buffer of the HMAC key.
314 * @param[in] size The size of the HMAC key.
315 * @param[in,out] userdata information.
316 * @retval TSS2_RC_SUCCESS on success.
317 * @retval USER_DEFINED user defined errors on failure.
318 */
319TSS2_RC hmac_start(
320 ESYS_CRYPTO_CONTEXT_BLOB** context, TPM2_ALG_ID hash_alg, const uint8_t* key, size_t size, void* userdata) {
321 BOTAN_UNUSED(userdata);
322 return thunk([&] {
323 if(Botan::any_null_pointers(context, key)) {
324 return TSS2_ESYS_RC_BAD_REFERENCE;
325 }
326
327 const auto hash_name = Botan::TPM2::hash_algo_tss2_to_botan(hash_alg);
328 if(!hash_name) {
329 return TSS2_ESYS_RC_NOT_SUPPORTED;
330 }
331
332 auto hmac = Botan::MessageAuthenticationCode::create(Botan::fmt("HMAC({})", hash_name.value()));
333 if(hmac == nullptr) {
334 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
335 }
336
337 hmac->set_key(std::span{key, size});
338
339 // Will be deleted in hmac_abort() or hmac_finish()
340 *context = new DigestCallbackState{std::move(hmac)}; // NOLINT(*-owning-memory)
341 return TSS2_RC_SUCCESS;
342 });
343}
344
345/** Update and HMAC digest value from a byte buffer.
346 *
347 * The context of a digest object will be updated according to the hash
348 * algorithm and the key of the context.
349 * @param[in,out] context The context of the digest object which will be updated.
350 * @param[in] buffer The data for the update.
351 * @param[in] size The size of the data buffer.
352 * @param[in,out] userdata information.
353 * @retval TSS2_RC_SUCCESS on success.
354 * @retval USER_DEFINED user defined errors on failure.
355 */
356TSS2_RC hmac_update(ESYS_CRYPTO_CONTEXT_BLOB* context, const uint8_t* buffer, size_t size, void* userdata) {
357 BOTAN_UNUSED(userdata);
358 return thunk([&] {
359 auto hmac = get<Botan::MessageAuthenticationCode>(context);
360 if(!hmac) {
361 return TSS2_ESYS_RC_BAD_REFERENCE;
362 }
363
364 // nullptr buffer with size 0 is alright
365 if(buffer == nullptr && size != 0) {
366 return TSS2_ESYS_RC_BAD_VALUE;
367 }
368
369 hmac->get().update(std::span{buffer, size});
370 return TSS2_RC_SUCCESS;
371 });
372}
373
374/** Write the HMAC digest value to a byte buffer and close the context.
375 *
376 * The digest value will written to a passed buffer and the resources of the
377 * HMAC object are released.
378 * @param[in,out] context The context of the HMAC object.
379 * @param[out] buffer The buffer for the digest value (caller-allocated).
380 * @param[out] size The size of the digest.
381 * @param[in,out] userdata information.
382 * @retval TSS2_RC_SUCCESS on success.
383 * @retval USER_DEFINED user defined errors on failure.
384 */
385TSS2_RC hmac_finish(ESYS_CRYPTO_CONTEXT_BLOB** context, uint8_t* buffer, size_t* size, void* userdata) {
386 BOTAN_UNUSED(userdata);
387 if(size != nullptr) {
388 *size = 0;
389 }
390
391 return thunk([&] {
392 auto hmac = get<Botan::MessageAuthenticationCode>(context);
393 if(!hmac || buffer == nullptr) {
394 return TSS2_ESYS_RC_BAD_REFERENCE;
395 }
396
397 const auto digest_size = hmac->get().output_length();
398 hmac->get().final(std::span{buffer, digest_size});
399 if(size != nullptr) {
400 *size = digest_size;
401 }
402
403 // allocated in hmac_start()
404 delete *context; // NOLINT(*-owning-memory)
405 *context = nullptr;
406 return TSS2_RC_SUCCESS;
407 });
408}
409
410/** Release the resources of an HMAC object.
411 *
412 * The assigned resources will be released and the context will be set to NULL.
413 * @param[in,out] context The context of the HMAC object.
414 * @param[in,out] userdata information.
415 */
416void hmac_abort(ESYS_CRYPTO_CONTEXT_BLOB** context, void* userdata) {
417 BOTAN_UNUSED(userdata);
418 if(context != nullptr) {
419 // allocated in hmac_start()
420 delete *context; // NOLINT(*-owning-memory)
421 *context = nullptr;
422 }
423}
424
425/** Compute random TPM2B data.
426 *
427 * The random data will be generated and written to a passed TPM2B structure.
428 * @param[out] nonce The TPM2B structure for the random data (caller-allocated).
429 * @param[in] num_bytes The number of bytes to be generated.
430 * @param[in,out] userdata information.
431 * @retval TSS2_RC_SUCCESS on success.
432 * @retval USER_DEFINED user defined errors on failure.
433 * @note: the TPM should not be used to obtain the random data
434 */
435TSS2_RC get_random2b(TPM2B_NONCE* nonce, size_t num_bytes, void* userdata) {
436 return thunk([&] {
437 auto ccs = get(userdata);
438 if(!ccs || !ccs->get().rng || Botan::any_null_pointers(nonce)) {
439 return TSS2_ESYS_RC_BAD_REFERENCE;
440 }
441
442 ccs->get().rng->randomize(Botan::TPM2::as_span(*nonce, num_bytes));
443 return TSS2_RC_SUCCESS;
444 });
445}
446
447/** Encryption of a buffer using a public (RSA) key.
448 *
449 * Encrypting a buffer using a public key is used for example during
450 * Esys_StartAuthSession in order to encrypt the salt value.
451 * @param[in] pub_tpm_key The key to be used for encryption.
452 * @param[in] in_size The size of the buffer to be encrypted.
453 * @param[in] in_buffer The data buffer to be encrypted.
454 * @param[in] max_out_size The maximum size for the output encrypted buffer.
455 * @param[out] out_buffer The encrypted buffer.
456 * @param[out] out_size The size of the encrypted output.
457 * @param[in] label The label used in the encryption scheme.
458 * @param[in,out] userdata information.
459 * @retval TSS2_RC_SUCCESS on success
460 * @retval USER_DEFINED user defined errors on failure.
461 */
462TSS2_RC rsa_pk_encrypt(TPM2B_PUBLIC* pub_tpm_key,
463 size_t in_size,
464 BYTE* in_buffer,
465 size_t max_out_size,
466 BYTE* out_buffer,
467 size_t* out_size,
468 const char* label,
469 void* userdata) {
470 if(out_size != nullptr) {
471 *out_size = 0;
472 }
473
474 // TODO: This is currently a dumpster fire of code duplication and
475 // YOLO manual padding.
476 //
477 // I'm hoping that a follow-up of Jack's work will help clean
478 // this up. See the extensive discussions in:
479 //
480 // https://github.com/randombit/botan/pull/4318#issuecomment-2297682058
481 #if defined(BOTAN_HAS_RSA)
482 auto create_eme = [&](const TPMT_RSA_SCHEME& scheme,
483 [[maybe_unused]] TPM2_ALG_ID name_algo,
484 [[maybe_unused]] TPMU_ASYM_SCHEME scheme_detail)
485 -> std::optional<std::unique_ptr<Botan::EncryptionPaddingScheme>> {
486 // OAEP is more complex by requiring a hash function and an optional
487 // label. To avoid marshalling this into Botan's algorithm descriptor
488 // we create an OAEP instance manually.
489 auto create_oaep = [&]() -> std::optional<std::unique_ptr<Botan::EncryptionPaddingScheme>> {
490 #if defined(BOTAN_HAS_EME_OAEP)
491 // TPM Library, Part 1: Architecture, Annex B.4
492 // The RSA key's scheme hash algorithm (or, if it is TPM_ALG_NULL,
493 // the RSA key's Name algorithm) is used to compute H(label).
494 const auto label_hash = Botan::TPM2::hash_algo_tss2_to_botan(
495 (scheme_detail.oaep.hashAlg == TPM2_ALG_NULL || scheme_detail.oaep.hashAlg == TPM2_ALG_ERROR)
496 ? pub_tpm_key->publicArea.nameAlg
497 : scheme_detail.oaep.hashAlg);
498
499 // TPM Library, Part 1: Architecture, Annex B.4
500 // The mask-generation function uses the Name algorithm of the RSA
501 // key as the hash algorithm.
502 const auto mgf1_hash = Botan::TPM2::hash_algo_tss2_to_botan(name_algo);
503 if(!label_hash || !mgf1_hash) {
504 return std::nullopt; // -> not supported
505 }
506
507 auto H_label = Botan::HashFunction::create(label_hash.value());
508 auto H_mgf1 = Botan::HashFunction::create(mgf1_hash.value());
509 if(!H_label || !H_mgf1) {
510 return nullptr; // -> not implemented
511 }
512
513 // TPM Library, Part 1: Architecture, Annex B.4
514 // [...] is used to compute lhash := H(label), and the null
515 // termination octet is included in the digest.
516 const std::string_view label_with_zero_terminator{label, std::strlen(label) + 1};
517 return std::make_unique<Botan::OAEP>(std::move(H_label), std::move(H_mgf1), label_with_zero_terminator);
518 #else
519 BOTAN_UNUSED(label);
520 return nullptr; // -> not implemented
521 #endif
522 };
523
524 try { // EncryptionPaddingScheme::create throws if algorithm is not available
525 switch(scheme.scheme) {
526 case TPM2_ALG_OAEP:
527 return create_oaep();
528 case TPM2_ALG_NULL:
530 case TPM2_ALG_RSAES:
532 default:
533 return std::nullopt; // -> not supported
534 }
535 } catch(const Botan::Algorithm_Not_Found&) {
536 /* ignore */
537 }
538
539 return nullptr; // -> not implemented (EncryptionPaddingScheme::create() threw)
540 };
541
542 return thunk([&] {
543 auto ccs = get(userdata);
544 if(!ccs || !ccs->get().rng || Botan::any_null_pointers(pub_tpm_key, in_buffer, out_buffer)) {
545 return TSS2_ESYS_RC_BAD_REFERENCE;
546 }
547
548 Botan::RandomNumberGenerator& rng = *ccs->get().rng;
549
550 BOTAN_ASSERT_NOMSG(pub_tpm_key->publicArea.type == TPM2_ALG_RSA);
551
552 const auto maybe_eme = create_eme(pub_tpm_key->publicArea.parameters.rsaDetail.scheme,
553 pub_tpm_key->publicArea.nameAlg,
554 pub_tpm_key->publicArea.parameters.rsaDetail.scheme.details);
555 if(!maybe_eme.has_value()) {
556 return TSS2_ESYS_RC_NOT_SUPPORTED;
557 }
558
559 const auto& eme = maybe_eme.value();
560 if(!eme) {
561 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
562 }
563
564 // The code below is duplicated logic with Botan's PK_Encryptor_EME.
565 // Currently, there's no way to instantiate the encryptor without
566 // marshalling the optional `label` into Botan's algorithm name.
567 //
568 // The label contains characters that are not allowed in Botan's string-
569 // based algorithm names, namely the \0 terminator. We currently handle
570 // the padding manually and then encrypt the padded data with raw RSA.
571 //
572 // TODO: Provide a way to instantiate an PK_Encryptor_EME that accepts a
573 // pre-made EME object. See: https://github.com/randombit/botan/pull/4318
574
575 const auto pubkey = Botan::TPM2::rsa_pubkey_from_tss2_public(pub_tpm_key);
576 const auto keybits = pubkey.key_length();
577 const auto output_size = keybits / 8;
578 if(eme->maximum_input_size(keybits) < in_size) {
579 return TSS2_ESYS_RC_BAD_VALUE;
580 }
581
582 if(output_size > max_out_size) {
583 return TSS2_ESYS_RC_INSUFFICIENT_BUFFER;
584 }
585
586 const auto max_raw_bits = keybits - 1;
587 const auto max_raw_bytes = (max_raw_bits + 7) / 8;
588 const auto padded_bytes = eme->pad({out_buffer, max_raw_bytes}, {in_buffer, in_size}, max_raw_bits, rng);
589
590 // PK_Encryptor_EME does not provide a way to pass in an output buffer.
591 // TODO: provide an `.encrypt()` overload that accepts an output buffer.
592 const Botan::PK_Encryptor_EME encryptor(pubkey, rng, "Raw");
593 const auto encrypted = encryptor.encrypt({out_buffer, padded_bytes}, rng);
594 BOTAN_DEBUG_ASSERT(encrypted.size() == output_size);
595
596 // We abused the `out_buffer` to hold the result of the padding. Hence, we
597 // now have to copy the encrypted data over the padded plaintext data.
598 Botan::copy_mem(std::span{out_buffer, encrypted.size()}, encrypted);
599 if(out_size != nullptr) {
600 *out_size = encrypted.size();
601 }
602
603 return TSS2_RC_SUCCESS;
604 });
605 #else
606 BOTAN_UNUSED(pub_tpm_key, in_size, in_buffer, max_out_size, out_buffer, label, userdata);
607 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
608 #endif
609}
610
611/** Computation of an ephemeral ECC key and shared secret Z.
612 *
613 * According to the description in TPM spec part 1 C 6.1 a shared secret
614 * between application and TPM is computed (ECDH). An ephemeral ECC key and a
615 * TPM key are used for the ECDH key exchange.
616 * @param[in] key The key to be used for ECDH key exchange.
617 * @param[in] max_out_size the max size for the output of the public key of the
618 * computed ephemeral key.
619 * @param[out] Z The computed shared secret.
620 * @param[out] Q The public part of the ephemeral key in TPM format.
621 * @param[out] out_buffer The public part of the ephemeral key will be marshaled
622 * to this buffer.
623 * @param[out] out_size The size of the marshaled output.
624 * @param[in,out] userdata information.
625 * @retval TSS2_RC_SUCCESS on success
626 * @retval USER_DEFINED user defined errors on failure.
627 */
628TSS2_RC get_ecdh_point(TPM2B_PUBLIC* key,
629 size_t max_out_size,
630 TPM2B_ECC_PARAMETER* Z,
631 TPMS_ECC_POINT* Q,
632 BYTE* out_buffer,
633 size_t* out_size,
634 void* userdata) {
635 if(out_size != nullptr) {
636 *out_size = 0;
637 }
638
639 #if defined(BOTAN_HAS_ECDH)
640 return thunk([&] {
641 auto ccs = get(userdata);
642 if(!ccs || !ccs->get().rng || Botan::any_null_pointers(key, Z, Q, out_buffer)) {
643 return TSS2_ESYS_RC_BAD_REFERENCE;
644 }
645
646 Botan::RandomNumberGenerator& rng = *ccs->get().rng;
647
648 // 1: Get TPM public key
649 const auto [tpm_ec_group, tpm_ec_point] = Botan::TPM2::ecc_pubkey_from_tss2_public(key);
650 const auto tpm_sw_pubkey = Botan::ECDH_PublicKey(tpm_ec_group, tpm_ec_point);
651
652 const auto curve_order_byte_size = tpm_sw_pubkey.domain().get_p_bytes();
653
654 // 2: Generate ephemeral key
655 const auto eph_key = Botan::ECDH_PrivateKey(rng, tpm_sw_pubkey.domain());
656
657 // Serialize public key coordinates into TPM2B_ECC_PARAMETER with Big Endian encoding.
658 // This ensures bn_{x,y}.bytes() <= curve_order_byte_size.
659 const auto& eph_pub_point = eph_key._public_ec_point();
660 eph_pub_point.serialize_x_to(Botan::TPM2::as_span(Q->x, curve_order_byte_size));
661 eph_pub_point.serialize_y_to(Botan::TPM2::as_span(Q->y, curve_order_byte_size));
662
663 // 3: ECDH Key Agreement
664 const Botan::PK_Key_Agreement ecdh(eph_key, rng, "Raw" /*No KDF used here*/);
665 const auto shared_secret = ecdh.derive_key(0 /*Ignored for raw KDF*/, tpm_sw_pubkey.public_value()).bits_of();
666
667 Botan::TPM2::copy_into(*Z, shared_secret);
668
669 Botan::TPM2::check_rc("Tss2_MU_TPMS_ECC_POINT_Marshal",
670 Tss2_MU_TPMS_ECC_POINT_Marshal(Q, out_buffer, max_out_size, out_size));
671
672 return TSS2_RC_SUCCESS;
673 });
674 #else
675 BOTAN_UNUSED(key, max_out_size, Z, Q, out_buffer, userdata);
676 return TSS2_ESYS_RC_NOT_IMPLEMENTED;
677 #endif
678}
679
680/** Encrypt data with AES.
681 *
682 * @param[in] key key used for AES.
683 * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES).
684 * @param[in] key_bits Key size in bits.
685 * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB).
686 * For parameter encryption only CFB can be used.
687 * @param[in,out] buffer Data to be encrypted. The encrypted date will be stored
688 * in this buffer.
689 * @param[in] buffer_size size of data to be encrypted.
690 * @param[in] iv The initialization vector.
691 * @param[in,out] userdata information.
692 * @retval TSS2_RC_SUCCESS on success
693 * @retval USER_DEFINED user defined errors on failure.
694 */
695TSS2_RC aes_encrypt(uint8_t* key,
696 TPM2_ALG_ID tpm_sym_alg,
697 TPMI_AES_KEY_BITS key_bits,
698 TPM2_ALG_ID tpm_mode,
699 uint8_t* buffer,
700 size_t buffer_size,
701 uint8_t* iv,
702 void* userdata) {
703 BOTAN_UNUSED(userdata);
704 if(tpm_sym_alg != TPM2_ALG_AES) {
705 return TSS2_ESYS_RC_BAD_VALUE;
706 }
707
708 return symmetric_algo(Botan::Cipher_Dir::Encryption, key, tpm_sym_alg, key_bits, tpm_mode, buffer, buffer_size, iv);
709}
710
711/** Decrypt data with AES.
712 *
713 * @param[in] key key used for AES.
714 * @param[in] tpm_sym_alg AES type in TSS2 notation (must be TPM2_ALG_AES).
715 * @param[in] key_bits Key size in bits.
716 * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB).
717 * For parameter encryption only CFB can be used.
718 * @param[in,out] buffer Data to be decrypted. The decrypted date will be stored
719 * in this buffer.
720 * @param[in] buffer_size size of data to be encrypted.
721 * @param[in] iv The initialization vector.
722 * @param[in,out] userdata information.
723 * @retval TSS2_RC_SUCCESS on success
724 * @retval USER_DEFINED user defined errors on failure.
725 */
726TSS2_RC aes_decrypt(uint8_t* key,
727 TPM2_ALG_ID tpm_sym_alg,
728 TPMI_AES_KEY_BITS key_bits,
729 TPM2_ALG_ID tpm_mode,
730 uint8_t* buffer,
731 size_t buffer_size,
732 uint8_t* iv,
733 void* userdata) {
734 BOTAN_UNUSED(userdata);
735 if(tpm_sym_alg != TPM2_ALG_AES) {
736 return TSS2_ESYS_RC_BAD_VALUE;
737 }
738
739 return symmetric_algo(Botan::Cipher_Dir::Decryption, key, tpm_sym_alg, key_bits, tpm_mode, buffer, buffer_size, iv);
740}
741
742 #if defined(BOTAN_TSS2_SUPPORTS_SM4_IN_CRYPTO_CALLBACKS)
743
744/** Encrypt data with SM4.
745 *
746 * @param[in] key key used for SM4.
747 * @param[in] tpm_sym_alg SM4 type in TSS2 notation (must be TPM2_ALG_SM4).
748 * @param[in] key_bits Key size in bits.
749 * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB).
750 * For parameter encryption only CFB can be used.
751 * @param[in,out] buffer Data to be encrypted. The encrypted date will be stored
752 * in this buffer.
753 * @param[in] buffer_size size of data to be encrypted.
754 * @param[in] iv The initialization vector.
755 * @param[in,out] userdata information.
756 * @retval TSS2_RC_SUCCESS on success
757 * @retval USER_DEFINED user defined errors on failure.
758 */
759TSS2_RC sm4_encrypt(uint8_t* key,
760 TPM2_ALG_ID tpm_sym_alg,
761 TPMI_SM4_KEY_BITS key_bits,
762 TPM2_ALG_ID tpm_mode,
763 uint8_t* buffer,
764 size_t buffer_size,
765 uint8_t* iv,
766 void* userdata) {
767 BOTAN_UNUSED(userdata);
768 if(tpm_sym_alg != TPM2_ALG_SM4) {
769 return TSS2_ESYS_RC_BAD_VALUE;
770 }
771
772 return symmetric_algo(Botan::Cipher_Dir::Encryption, key, tpm_sym_alg, key_bits, tpm_mode, buffer, buffer_size, iv);
773}
774
775/** Decrypt data with SM4.
776 *
777 * @param[in] key key used for SM4.
778 * @param[in] tpm_sym_alg SM4 type in TSS2 notation (must be TPM2_ALG_SM4).
779 * @param[in] key_bits Key size in bits.
780 * @param[in] tpm_mode Block cipher mode of operation in TSS2 notation (CFB).
781 * For parameter encryption only CFB can be used.
782 * @param[in,out] buffer Data to be decrypted. The decrypted date will be stored
783 * in this buffer.
784 * @param[in] buffer_size size of data to be encrypted.
785 * @param[in] iv The initialization vector.
786 * @param[in,out] userdata information.
787 * @retval TSS2_RC_SUCCESS on success
788 * @retval USER_DEFINED user defined errors on failure.
789 */
790TSS2_RC sm4_decrypt(uint8_t* key,
791 TPM2_ALG_ID tpm_sym_alg,
792 TPMI_SM4_KEY_BITS key_bits,
793 TPM2_ALG_ID tpm_mode,
794 uint8_t* buffer,
795 size_t buffer_size,
796 uint8_t* iv,
797 void* userdata) {
798 BOTAN_UNUSED(userdata);
799 if(tpm_sym_alg != TPM2_ALG_SM4) {
800 return TSS2_ESYS_RC_BAD_VALUE;
801 }
802
803 return symmetric_algo(Botan::Cipher_Dir::Decryption, key, tpm_sym_alg, key_bits, tpm_mode, buffer, buffer_size, iv);
804}
805
806 #endif /* TPM2_ALG_SM4 */
807
808/** Initialize crypto backend.
809 *
810 * Initialize internal tables of crypto backend.
811 *
812 * @param[in,out] userdata Optional userdata pointer.
813 *
814 * @retval TSS2_RC_SUCCESS ong success.
815 * @retval USER_DEFINED user defined errors on failure.
816 */
817TSS2_RC init(void* userdata) {
818 BOTAN_UNUSED(userdata);
819 return TSS2_RC_SUCCESS;
820}
821
822} // extern "C"
823
824} // namespace
825
826#endif /* BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS */
827
828namespace Botan::TPM2 {
829
830/**
831 * Enable the Botan crypto callbacks for the given ESYS context.
832 *
833 * The callbacks may maintain two types of state:
834 *
835 * * 'userdata' is a pointer to a CryptoCallbackState object that is passed
836 * to all callback functions. This provides access to a random
837 * number generator specified by the user.
838 * The lifetime of this object is bound to the TPM2::Context.
839 *
840 * * 'context' is a pointer to a DigestCallbackState object that contains
841 * either a HashFunction or a MessageAuthenticationCode object.
842 * This holds the hash state between update callback invocations.
843 * The lifetime of this object is bound to the digest callbacks,
844 * hence *_finish() and *_abort() will delete the object.
845 *
846 * The runtime crypto backend is available since TSS2 4.0.0 and later. Explicit
847 * support for SM4 was added in TSS2 4.1.0.
848 *
849 * Note that the callback implementations should be defensive in regard to the
850 * input parameters. All pointers should be checked for nullptr before being
851 * dereferenced. Some output parameters (e.g. out-buffer lengths) may be
852 * regarded as optional, and should be checked for nullptr before being written
853 * to.
854 *
855 * Error code conventions:
856 *
857 * * TSS2_ESYS_RC_BAD_REFERENCE: reference (typically userdata) invalid
858 * * TSS2_ESYS_RC_BAD_VALUE: invalid input (e.g. size != 0 w/ nullptr buffer)
859 * * TSS2_ESYS_RC_NOT_SUPPORTED: algorithm identifier not mapped to Botan
860 * * TSS2_ESYS_RC_NOT_IMPLEMENTED: algorithm not available (e.g. disabled)
861 */
862void set_crypto_callbacks(ESYS_CONTEXT* ctx, void* callback_state) {
863#if defined(BOTAN_TSS2_SUPPORTS_CRYPTO_CALLBACKS)
865
866 // clang-format off
867 ESYS_CRYPTO_CALLBACKS callbacks{
868 .rsa_pk_encrypt = &rsa_pk_encrypt,
869 .hash_start = &hash_start,
870 .hash_update = &hash_update,
871 .hash_finish = &hash_finish,
872 .hash_abort = &hash_abort,
873 .hmac_start = &hmac_start,
874 .hmac_update = &hmac_update,
875 .hmac_finish = &hmac_finish,
876 .hmac_abort = &hmac_abort,
877 .get_random2b = &get_random2b,
878 .get_ecdh_point = &get_ecdh_point,
879 .aes_encrypt = &aes_encrypt,
880 .aes_decrypt = &aes_decrypt,
881#if defined(BOTAN_TSS2_SUPPORTS_SM4_IN_CRYPTO_CALLBACKS)
882 .sm4_encrypt = &sm4_encrypt,
883 .sm4_decrypt = &sm4_decrypt,
884#endif
885 .init = &init,
886 .userdata = callback_state,
887 };
888 // clang-format on
889
890 check_rc("Esys_SetCryptoCallbacks", Esys_SetCryptoCallbacks(ctx, &callbacks));
891#else
892 BOTAN_UNUSED(ctx, callback_state);
893 throw Not_Implemented(
894 "This build of botan was compiled with a TSS2 version lower than 4.0.0, "
895 "which does not support custom runtime crypto backends");
896#endif
897}
898
899} // namespace Botan::TPM2
#define BOTAN_UNUSED
Definition assert.h:144
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_DEBUG_ASSERT(expr)
Definition assert.h:129
#define BOTAN_ASSERT_NONNULL(ptr)
Definition assert.h:114
static std::unique_ptr< Cipher_Mode > create(std::string_view algo, Cipher_Dir direction, std::string_view provider="")
static std::unique_ptr< EncryptionPaddingScheme > create(std::string_view algo_spec)
static std::unique_ptr< HashFunction > create(std::string_view algo_spec, std::string_view provider="")
Definition hash.cpp:111
static std::unique_ptr< MessageAuthenticationCode > create(std::string_view algo_spec, std::string_view provider="")
Definition mac.cpp:50
constexpr void check_rc(std::string_view location, TSS2_RC rc)
Definition tpm2_util.h:55
std::optional< std::string > hash_algo_tss2_to_botan(TPMI_ALG_HASH hash_id)
std::optional< std::string > cipher_tss2_to_botan(TPMT_SYM_DEF cipher_def)
constexpr void copy_into(T &dest, std::span< const uint8_t > data)
Definition tpm2_util.h:118
constexpr auto as_span(tpm2_buffer auto &data)
Construct a std::span as a view into a TPM2 buffer.
Definition tpm2_util.h:103
void set_crypto_callbacks(ESYS_CONTEXT *ctx, void *callback_state)
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
constexpr void copy_mem(T *out, const T *in, size_t n)
Definition mem_ops.h:144
bool any_null_pointers(Ptrs... ptr)
Definition mem_utils.h:54
uint32_t TSS2_RC
Forward declaration of TSS2 type for convenience.
Definition tpm2_error.h:15