Botan 3.9.0
Crypto and TLS for C&
tpm2_rsa.cpp
Go to the documentation of this file.
1/*
2* TPM 2.0 RSA Key Wrappres
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/tpm2_rsa.h>
10
11#include <botan/hash.h>
12#include <botan/pss_params.h>
13#include <botan/rsa.h>
14
15#include <botan/internal/ct_utils.h>
16#include <botan/internal/fmt.h>
17#include <botan/internal/scan_name.h>
18#include <botan/internal/sig_padding.h>
19#include <botan/internal/stl_util.h>
20#include <botan/internal/tpm2_algo_mappings.h>
21#include <botan/internal/tpm2_hash.h>
22#include <botan/internal/tpm2_pkops.h>
23#include <botan/internal/tpm2_util.h>
24
25#include <tss2/tss2_esys.h>
26
27namespace Botan::TPM2 {
28
29RSA_PublicKey::RSA_PublicKey(Object handle, SessionBundle session_bundle, const TPM2B_PUBLIC* public_blob) :
31 std::move(handle), std::move(session_bundle), rsa_pubkey_components_from_tss2_public(public_blob)) {}
32
33RSA_PublicKey::RSA_PublicKey(Object handle, SessionBundle session_bundle, const std::pair<BigInt, BigInt>& pubkey) :
34 Botan::TPM2::PublicKey(std::move(handle), std::move(session_bundle)),
35
36 // TODO: move those BigInts as soon as the RSA c'tor allows it
37 Botan::RSA_PublicKey(pubkey.first, pubkey.second) {}
38
40 SessionBundle session_bundle,
41 const TPM2B_PUBLIC* public_blob,
42 std::span<const uint8_t> private_blob) :
43 Botan::TPM2::RSA_PrivateKey(std::move(handle),
44 std::move(session_bundle),
45 rsa_pubkey_components_from_tss2_public(public_blob),
46 private_blob) {}
47
49 SessionBundle session_bundle,
50 const std::pair<BigInt, BigInt>& pubkey,
51 std::span<const uint8_t> private_blob) :
52 Botan::TPM2::PrivateKey(std::move(handle), std::move(session_bundle), private_blob),
53
54 // TODO: move those BigInts as soon as the RSA c'tor allows it
55 Botan::RSA_PublicKey(pubkey.first, pubkey.second) {}
56
57std::unique_ptr<TPM2::PrivateKey> RSA_PrivateKey::create_unrestricted_transient(const std::shared_ptr<Context>& ctx,
59 std::span<const uint8_t> auth_value,
60 const TPM2::PrivateKey& parent,
61 uint16_t keylength,
62 std::optional<uint32_t> exponent) {
63 BOTAN_ARG_CHECK(parent.is_parent(), "The passed key cannot be used as a parent key");
64
65 TPM2B_SENSITIVE_CREATE sensitive_data = {
66 .size = 0, // ignored
67 .sensitive =
68 {
69 .userAuth = copy_into<TPM2B_AUTH>(auth_value),
70
71 // Architecture Document, Section 25.2.3
72 // When an asymmetric key is created, the caller is not allowed to
73 // provide the sensitive data of the key.
75 },
76 };
77
78 TPMT_PUBLIC key_template = {
79 .type = TPM2_ALG_RSA,
80
81 // This is the algorithm for fingerprinting the newly created public key.
82 // For best compatibility we always use SHA-256.
83 .nameAlg = TPM2_ALG_SHA256,
84
85 // This sets up the key to be both a decryption and a signing key, forbids
86 // its duplication (fixed_tpm, fixed_parent) and ensures that the key's
87 // private portion can be used only by a user with an HMAC or password
88 // session.
89 .objectAttributes = ObjectAttributes::render({
90 .fixed_tpm = true,
91 .fixed_parent = true,
92 .sensitive_data_origin = true,
93 .user_with_auth = true,
94 .decrypt = true,
95 .sign_encrypt = true,
96 }),
97
98 // We currently do not support policy-based authorization
99 .authPolicy = init_empty<TPM2B_DIGEST>(),
100 .parameters =
101 {
102 .rsaDetail =
103 {
104 // Structures Document (Part 2), Section 12.2.3.5
105 // If the key is not a restricted decryption key, this field
106 // shall be set to TPM_ALG_NULL.
107 //
108 // TODO: Once we stop supporting TSS < 4.0, we could use
109 // `.keyBits = {.null = {}}, .mode = {.null = {}}`
110 // which better reflects our intention here.
111 .symmetric =
112 {
113 .algorithm = TPM2_ALG_NULL,
114 .keyBits = {.sym = 0},
115 .mode = {.sym = TPM2_ALG_NULL},
116 },
117
118 // Structures Document (Part 2), Section 12.2.3.5
119 // When both sign and decrypt are SET, restricted shall be
120 // CLEAR and scheme shall be TPM_ALG_NULL
121 //
122 // TODO: Once we stop supporting TSS < 4.0, we could use
123 // `.details = {.null = {}}`
124 // which better reflects our intention here.
125 .scheme =
126 {
127 .scheme = TPM2_ALG_NULL,
128 .details = {.anySig = {.hashAlg = TPM2_ALG_NULL}},
129 },
130 .keyBits = keylength,
131 .exponent = exponent.value_or(0 /* default value - 2^16 + 1*/),
132 },
133 },
134
135 // For creating an asymmetric key this value is not used.
136 .unique = {.rsa = init_empty<TPM2B_PUBLIC_KEY_RSA>()},
137 };
138
140 ctx, sessions, parent.handles().transient_handle(), key_template, sensitive_data);
141}
142
143namespace {
144
145SignatureAlgorithmSelection select_signature_algorithms(std::string_view padding) {
146 const SCAN_Name req(padding);
147 if(req.arg_count() == 0) {
148 throw Invalid_Argument("RSA signing padding scheme must at least specify a hash function");
149 }
150
151 auto sig_scheme = rsa_signature_scheme_botan_to_tss2(padding);
152 if(!sig_scheme) {
153 throw Not_Implemented(Botan::fmt("RSA signing with padding scheme {}", padding));
154 }
155
156 return {
157 .signature_scheme = sig_scheme.value(),
158 .hash_name = req.arg(0),
159 .padding = std::string(padding),
160 };
161}
162
163size_t signature_length_for_rsa_key_handle(const SessionBundle& sessions, const Object& key_handle) {
164 return key_handle._public_info(sessions, TPM2_ALG_RSA).pub->publicArea.parameters.rsaDetail.keyBits / 8;
165}
166
167class RSA_Signature_Operation final : public Signature_Operation {
168 public:
169 RSA_Signature_Operation(const Object& object, const SessionBundle& sessions, std::string_view padding) :
170 Signature_Operation(object, sessions, select_signature_algorithms(padding)) {}
171
172 size_t signature_length() const override { return signature_length_for_rsa_key_handle(sessions(), key_handle()); }
173
174 AlgorithmIdentifier algorithm_identifier() const override {
175 // TODO: This is essentially a copy of the ::algorithm_identifier()
176 // in `rsa.h`. We should probably refactor this into a common
177 // function.
178
179 // This EMSA object actually isn't required, we just need it to
180 // conveniently figure out the algorithm identifier.
181 //
182 // TODO: This is a hack, and we should clean this up.
183 BOTAN_STATE_CHECK(padding().has_value());
184 const std::string padding_name = SignaturePaddingScheme::create_or_throw(padding().value())->name();
185
186 try {
187 const std::string full_name = "RSA/" + padding_name;
188 const OID oid = OID::from_string(full_name);
189 return AlgorithmIdentifier(oid, AlgorithmIdentifier::USE_EMPTY_PARAM);
190 } catch(Lookup_Error&) {}
191
192 if(padding_name.starts_with("PSS(")) {
193 auto parameters = PSS_Params::from_padding_name(padding_name).serialize();
194 return AlgorithmIdentifier("RSA/PSS", parameters);
195 }
196
197 throw Invalid_Argument(fmt("Signatures using RSA/{} are not supported", padding_name));
198 }
199
200 private:
201 std::vector<uint8_t> marshal_signature(const TPMT_SIGNATURE& signature) const override {
202 const auto& sig = [&] {
203 if(signature.sigAlg == TPM2_ALG_RSASSA) {
204 return signature.signature.rsassa;
205 } else if(signature.sigAlg == TPM2_ALG_RSAPSS) {
206 return signature.signature.rsapss;
207 }
208 throw Invalid_State(fmt("TPM2 returned an unexpected signature scheme {}", signature.sigAlg));
209 }();
210
211 BOTAN_ASSERT_NOMSG(sig.sig.size == signature_length());
212 return copy_into<std::vector<uint8_t>>(sig.sig);
213 }
214};
215
216class RSA_Verification_Operation final : public Verification_Operation {
217 public:
218 RSA_Verification_Operation(const Object& object, const SessionBundle& sessions, std::string_view padding) :
219 Verification_Operation(object, sessions, select_signature_algorithms(padding)) {}
220
221 private:
222 TPMT_SIGNATURE unmarshal_signature(std::span<const uint8_t> signature) const override {
223 BOTAN_ARG_CHECK(signature.size() == signature_length_for_rsa_key_handle(sessions(), key_handle()),
224 "Unexpected signature byte length");
225
226 TPMT_SIGNATURE sig;
227 sig.sigAlg = scheme().scheme;
228
229 auto& sig_data = [&]() -> TPMS_SIGNATURE_RSA& {
230 if(sig.sigAlg == TPM2_ALG_RSASSA) {
231 return sig.signature.rsassa;
232 } else if(sig.sigAlg == TPM2_ALG_RSAPSS) {
233 return sig.signature.rsapss;
234 }
235 throw Invalid_State(fmt("Requested an unexpected signature scheme {}", sig.sigAlg));
236 }();
237
238 sig_data.hash = scheme().details.any.hashAlg;
239 copy_into(sig_data.sig, signature);
240 return sig;
241 }
242};
243
244TPMT_RSA_DECRYPT select_encryption_algorithms(std::string_view padding) {
245 auto scheme = rsa_encryption_scheme_botan_to_tss2(padding);
246 if(!scheme) {
247 throw Not_Implemented(Botan::fmt("RSA encryption with padding scheme {}", padding));
248 }
249 return scheme.value();
250}
251
252class RSA_Encryption_Operation final : public PK_Ops::Encryption {
253 public:
254 RSA_Encryption_Operation(const Object& object, const SessionBundle& sessions, std::string_view padding) :
255 m_key_handle(object), m_sessions(sessions), m_scheme(select_encryption_algorithms(padding)) {}
256
257 std::vector<uint8_t> encrypt(std::span<const uint8_t> msg, Botan::RandomNumberGenerator& /* rng */) override {
258 const auto plaintext = copy_into<TPM2B_PUBLIC_KEY_RSA>(msg);
259
260 // TODO: Figure out what this is for. Given that I didn't see any other
261 // way to pass an EME-OAEP label, I'm guessing that this is what
262 // it is for. But I'm not sure.
263 //
264 // Again, a follow-up of https://github.com/randombit/botan/pull/4318
265 // that targets async encryption will probably be quite helpful here.
266 const auto label = init_empty<TPM2B_DATA>();
267
269 check_rc("Esys_RSA_Encrypt",
270 Esys_RSA_Encrypt(*m_key_handle.context(),
271 m_key_handle.transient_handle(),
272 m_sessions[0],
273 m_sessions[1],
274 m_sessions[2],
275 &plaintext,
276 &m_scheme,
277 &label,
278 out_ptr(ciphertext)));
279 BOTAN_ASSERT_NONNULL(ciphertext);
280 return copy_into<std::vector<uint8_t>>(*ciphertext);
281 }
282
283 // This duplicates quite a bit of domain knowledge about those RSA
284 // EMEs. And I'm quite certain that I screwed up somewhere.
285 //
286 // TODO: See if we can somehow share the logic with the software
287 // RSA implementation and also PKCS#11 (which I believe is plain wrong).
288 size_t max_input_bits() const override {
289 const auto max_ptext_bytes =
290 (m_key_handle._public_info(m_sessions, TPM2_ALG_RSA).pub->publicArea.parameters.rsaDetail.keyBits - 1) / 8;
291 auto hash_output_bytes = [](TPM2_ALG_ID hash) -> size_t {
292 switch(hash) {
293 case TPM2_ALG_SHA1:
294 return 160 / 8;
295 case TPM2_ALG_SHA256:
296 case TPM2_ALG_SHA3_256:
297 return 256 / 8;
298 case TPM2_ALG_SHA384:
299 case TPM2_ALG_SHA3_384:
300 return 384 / 8;
301 case TPM2_ALG_SHA512:
302 case TPM2_ALG_SHA3_512:
303 return 512 / 8;
304 default:
305 throw Invalid_State("Unexpected hash algorithm");
306 }
307 };
308
309 const auto max_input_bytes = [&]() -> size_t {
310 switch(m_scheme.scheme) {
311 case TPM2_ALG_RSAES:
312 return max_ptext_bytes - 10;
313 case TPM2_ALG_OAEP:
314 return max_ptext_bytes - 2 * hash_output_bytes(m_scheme.details.oaep.hashAlg) - 1;
315 case TPM2_ALG_NULL:
316 return max_ptext_bytes;
317 default:
318 throw Invalid_State("Unexpected RSA encryption scheme");
319 }
320 }();
321
322 return max_input_bytes * 8;
323 }
324
325 size_t ciphertext_length(size_t /* ptext_len */) const override {
326 return m_key_handle._public_info(m_sessions, TPM2_ALG_RSA).pub->publicArea.parameters.rsaDetail.keyBits - 1;
327 }
328
329 private:
330 const Object& m_key_handle;
331 const SessionBundle& m_sessions;
332 TPMT_RSA_DECRYPT m_scheme;
333};
334
335class RSA_Decryption_Operation final : public PK_Ops::Decryption {
336 public:
337 RSA_Decryption_Operation(const Object& object, const SessionBundle& sessions, std::string_view padding) :
338 m_key_handle(object), m_sessions(sessions), m_scheme(select_encryption_algorithms(padding)) {}
339
340 secure_vector<uint8_t> decrypt(uint8_t& valid_mask, std::span<const uint8_t> input) override {
341 const auto ciphertext = copy_into<TPM2B_PUBLIC_KEY_RSA>(input);
342 const auto label = init_empty<TPM2B_DATA>(); // TODO: implement? see encrypt operation
344
345 // TODO: I'm not sure that TPM2_RC_FAILURE is the right error code for
346 // all cases here. It passed the test (with a faulty ciphertext),
347 // but I didn't find this to be clearly documented. :-(
348 auto rc = check_rc_expecting<TPM2_RC_FAILURE>("Esys_RSA_Decrypt",
349 Esys_RSA_Decrypt(*m_key_handle.context(),
350 m_key_handle.transient_handle(),
351 m_sessions[0],
352 m_sessions[1],
353 m_sessions[2],
354 &ciphertext,
355 &m_scheme,
356 &label,
357 out_ptr(plaintext)));
358
359 const auto success = CT::Mask<decltype(rc)>::is_equal(rc, TPM2_RC_SUCCESS).as_choice();
360 valid_mask = CT::Mask<uint8_t>::from_choice(success).value();
361
362 // A "typical" payload size for RSA encryption, assuming that we usually
363 // encrypt some symmetric key of a hybrid encryption scheme.
364 constexpr size_t default_plaintext_length = 32;
365
366 // When Esys_RSA_Decrypt fails to decrypt the ciphertext (e.g. because
367 // of a PKCS#1.5 padding failure), the `plaintext` pointer will be nullptr.
368 // This behaviour in itself is likely exposing a timing side channel already.
369 // Nevertheless, we do our best to mitigate any oracles by always copying a
370 // dummy plaintext value in this case.
371 auto dummy_plaintext = init_with_size<TPM2B_PUBLIC_KEY_RSA>(default_plaintext_length);
372 auto* out = &dummy_plaintext;
373 auto* maybe_plaintext = plaintext.get();
374 CT::conditional_swap_ptr(success.as_bool(), out, maybe_plaintext);
375
378 }
379
380 size_t plaintext_length(size_t /* ciphertext_length */) const override {
381 return m_key_handle._public_info(m_sessions, TPM2_ALG_RSA).pub->publicArea.parameters.rsaDetail.keyBits / 8;
382 }
383
384 private:
385 const Object& m_key_handle;
386 const SessionBundle& m_sessions;
387 TPMT_RSA_DECRYPT m_scheme;
388};
389
390} // namespace
391
392std::unique_ptr<PK_Ops::Verification> RSA_PublicKey::create_verification_op(std::string_view params,
393 std::string_view provider) const {
394 BOTAN_UNUSED(provider);
395 return std::make_unique<RSA_Verification_Operation>(handles(), sessions(), params);
396}
397
399 std::string_view params,
400 std::string_view provider) const {
401 BOTAN_UNUSED(rng, provider);
402 return std::make_unique<RSA_Signature_Operation>(handles(), sessions(), params);
403}
404
406 std::string_view params,
407 std::string_view provider) const {
408 BOTAN_UNUSED(rng, provider);
409 return std::make_unique<RSA_Encryption_Operation>(handles(), sessions(), params);
410}
411
413 std::string_view params,
414 std::string_view provider) const {
415 BOTAN_UNUSED(rng, provider);
416 return std::make_unique<RSA_Decryption_Operation>(handles(), sessions(), params);
417}
418
419} // namespace Botan::TPM2
#define BOTAN_UNUSED
Definition assert.h:144
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_STATE_CHECK(expr)
Definition assert.h:49
#define BOTAN_ASSERT_NONNULL(ptr)
Definition assert.h:114
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
static constexpr Mask< T > from_choice(Choice c)
Definition ct_utils.h:430
static OID from_string(std::string_view str)
Definition asn1_oid.cpp:86
static PSS_Params from_padding_name(std::string_view padding_name)
std::vector< uint8_t > serialize() const
static std::unique_ptr< SignaturePaddingScheme > create_or_throw(std::string_view algo_spec)
ESYS_TR transient_handle() const noexcept
static std::unique_ptr< PrivateKey > create_transient_from_template(const std::shared_ptr< Context > &ctx, const SessionBundle &sessions, ESYS_TR parent, const TPMT_PUBLIC &key_template, const TPM2B_SENSITIVE_CREATE &sensitive_data)
Definition tpm2_key.cpp:217
const SessionBundle & sessions() const
Definition tpm2_key.h:224
const Object & handles() const
Definition tpm2_key.h:99
const SessionBundle & sessions() const
Definition tpm2_key.h:101
RSA_PrivateKey(Object handle, SessionBundle sessions, const TPM2B_PUBLIC *public_blob, std::span< const uint8_t > private_blob={})
Definition tpm2_rsa.cpp:39
static std::unique_ptr< TPM2::PrivateKey > create_unrestricted_transient(const std::shared_ptr< Context > &ctx, const SessionBundle &sessions, std::span< const uint8_t > auth_value, const TPM2::PrivateKey &parent, uint16_t keylength, std::optional< uint32_t > exponent={})
Definition tpm2_rsa.cpp:57
std::unique_ptr< PK_Ops::Decryption > create_decryption_op(Botan::RandomNumberGenerator &rng, std::string_view params, std::string_view provider) const override
Definition tpm2_rsa.cpp:412
std::unique_ptr< PK_Ops::Signature > create_signature_op(Botan::RandomNumberGenerator &rng, std::string_view params, std::string_view provider) const override
Definition tpm2_rsa.cpp:398
std::unique_ptr< PK_Ops::Encryption > create_encryption_op(Botan::RandomNumberGenerator &rng, std::string_view params, std::string_view provider) const override
Definition tpm2_rsa.cpp:405
std::unique_ptr< PK_Ops::Verification > create_verification_op(std::string_view params, std::string_view provider) const override
Definition tpm2_rsa.cpp:392
RSA_PublicKey(Object handle, SessionBundle sessions, const TPM2B_PUBLIC *public_blob)
Definition tpm2_rsa.cpp:29
constexpr void conditional_swap_ptr(bool cnd, T &x, T &y)
Definition ct_utils.h:802
std::string decrypt(std::span< const uint8_t > input, std::string_view passphrase)
std::string encrypt(const uint8_t input[], size_t input_len, std::string_view passphrase, RandomNumberGenerator &rng)
Definition cryptobox.cpp:42
std::optional< TPMT_SIG_SCHEME > rsa_signature_scheme_botan_to_tss2(std::string_view name)
constexpr T init_empty()
Create an empty TPM2 buffer of the given type.
Definition tpm2_util.h:153
constexpr void check_rc(std::string_view location, TSS2_RC rc)
Definition tpm2_util.h:55
std::optional< TPMT_RSA_DECRYPT > rsa_encryption_scheme_botan_to_tss2(std::string_view padding)
std::unique_ptr< T, esys_liberator > unique_esys_ptr
A unique pointer type for ESYS handles that automatically frees the handle.
Definition tpm2_util.h:163
constexpr T init_with_size(size_t length)
Create a TPM2 buffer of a given type and length.
Definition tpm2_util.h:143
constexpr void copy_into(T &dest, std::span< const uint8_t > data)
Definition tpm2_util.h:118
constexpr TSS2_RC check_rc_expecting(std::string_view location, TSS2_RC rc)
Definition tpm2_util.h:73
constexpr auto out_ptr(T &outptr) noexcept
Definition stl_util.h:415
std::string fmt(std::string_view format, const T &... args)
Definition fmt.h:53
std::vector< T, secure_allocator< T > > secure_vector
Definition secmem.h:69
static TPMA_OBJECT render(ObjectAttributes attributes)