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