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