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

randombit / botan / 12548935066

30 Dec 2024 04:04PM UTC coverage: 91.262% (+0.05%) from 91.208%
12548935066

Pull #4507

github

web-flow
Merge 3aca433ed into 9b8f3cc80
Pull Request #4507: Remove support for Kyber r3 key exchange in TLS

93394 of 102336 relevant lines covered (91.26%)

11408572.15 hits per line

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

66.03
/src/lib/tls/tls13_pqc/hybrid_public_key.cpp
1
/**
2
* Composite key pair that exposes the Public/Private key API but combines
3
* multiple key agreement schemes into a hybrid algorithm.
4
*
5
* (C) 2023 Jack Lloyd
6
*     2023 Fabian Albert, René Meusel - Rohde & Schwarz Cybersecurity
7
*
8
* Botan is released under the Simplified BSD License (see license.txt)
9
*/
10

11
#include <botan/internal/hybrid_public_key.h>
12

13
#include <botan/pk_algs.h>
14

15
#include <botan/internal/fmt.h>
16
#include <botan/internal/kex_to_kem_adapter.h>
17
#include <botan/internal/pk_ops_impl.h>
18
#include <botan/internal/stl_util.h>
19

20
namespace Botan::TLS {
21

22
namespace {
23

24
std::vector<std::pair<std::string, std::string>> algorithm_specs_for_group(Group_Params group) {
41✔
25
   BOTAN_ARG_CHECK(group.is_pqc_hybrid(), "Group is not hybrid");
41✔
26

27
   switch(group.code()) {
41✔
28
      // draft-kwiatkowski-tls-ecdhe-mlkem-02 Section 3
29
      //
30
      //    NIST's special publication 800-56Cr2 approves the usage of HKDF with
31
      //    two distinct shared secrets, with the condition that the first one
32
      //    is computed by a FIPS-approved key-establishment scheme.  FIPS also
33
      //    requires a certified implementation of the scheme, which will remain
34
      //    more ubiqutous for secp256r1 in the coming years.
35
      //
36
      //    For this reason we put the ML-KEM-768 shared secret first in
37
      //    X25519MLKEM768, and the secp256r1 shared secret first in
38
      //    SecP256r1MLKEM768.
39
      case Group_Params::HYBRID_X25519_ML_KEM_768:
41✔
40
         return {{"ML-KEM", "ML-KEM-768"}, {"X25519", "X25519"}};
123✔
41
      case Group_Params::HYBRID_SECP256R1_ML_KEM_768:
×
42
         return {{"ECDH", "secp256r1"}, {"ML-KEM", "ML-KEM-768"}};
×
43

44
      case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
×
45
         return {{"X25519", "X25519"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}};
×
46
      case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
×
47
         return {{"X25519", "X25519"}, {"FrodoKEM", "eFrodoKEM-640-AES"}};
×
48
      case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
×
49
         return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-SHAKE"}};
×
50
      case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS:
×
51
         return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-AES"}};
×
52

53
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
×
54
         return {{"ECDH", "secp256r1"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}};
×
55
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
×
56
         return {{"ECDH", "secp256r1"}, {"FrodoKEM", "eFrodoKEM-640-AES"}};
×
57

58
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
×
59
         return {{"ECDH", "secp384r1"}, {"FrodoKEM", "eFrodoKEM-976-SHAKE"}};
×
60
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
×
61
         return {{"ECDH", "secp384r1"}, {"FrodoKEM", "eFrodoKEM-976-AES"}};
×
62

63
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
×
64
         return {{"ECDH", "secp521r1"}, {"FrodoKEM", "eFrodoKEM-1344-SHAKE"}};
×
65
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
×
66
         return {{"ECDH", "secp521r1"}, {"FrodoKEM", "eFrodoKEM-1344-AES"}};
×
67

68
      default:
×
69
         return {};
×
70
   }
71
}
41✔
72

73
std::vector<AlgorithmIdentifier> algorithm_identifiers_for_group(Group_Params group) {
20✔
74
   BOTAN_ASSERT_NOMSG(group.is_pqc_hybrid());
20✔
75

76
   const auto specs = algorithm_specs_for_group(group);
20✔
77
   std::vector<AlgorithmIdentifier> result;
20✔
78
   result.reserve(specs.size());
20✔
79

80
   // This maps the string-based algorithm specs hard-coded above to OID-based
81
   // AlgorithmIdentifier objects. The mapping is needed because
82
   // load_public_key() depends on those while create_private_key() requires the
83
   // strong-based spec.
84
   //
85
   // TODO: This is inconvenient, confusing and error-prone. Find a better way
86
   //       to load arbitrary public keys.
87
   for(const auto& spec : specs) {
60✔
88
      result.push_back(AlgorithmIdentifier(spec.second, AlgorithmIdentifier::USE_EMPTY_PARAM));
80✔
89
   }
90

91
   return result;
20✔
92
}
20✔
93

94
std::vector<size_t> public_value_lengths_for_group(Group_Params group) {
20✔
95
   BOTAN_ASSERT_NOMSG(group.is_pqc_hybrid());
20✔
96

97
   // This duplicates information of the algorithm internals.
98
   //
99
   // TODO: Find a way to expose important algorithm constants globally
100
   //       in the library, to avoid violating the DRY principle.
101
   switch(group.code()) {
20✔
102
      case Group_Params::HYBRID_X25519_ML_KEM_768:
20✔
103
         return {1184, 32};
20✔
104
      case Group_Params::HYBRID_SECP256R1_ML_KEM_768:
×
105
         return {32, 1184};
×
106

107
      case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
×
108
         return {32, 9616};
×
109
      case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
×
110
         return {32, 9616};
×
111
      case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
×
112
         return {56, 15632};
×
113
      case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS:
×
114
         return {56, 15632};
×
115

116
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
×
117
         return {32, 9616};
×
118
      case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
×
119
         return {32, 9616};
×
120

121
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
×
122
         return {48, 15632};
×
123
      case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
×
124
         return {48, 15632};
×
125

126
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
×
127
         return {66, 21520};
×
128
      case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
×
129
         return {66, 21520};
×
130

131
      default:
×
132
         return {};
×
133
   }
134
}
135

136
}  // namespace
137

138
std::unique_ptr<Hybrid_KEM_PublicKey> Hybrid_KEM_PublicKey::load_for_group(
20✔
139
   Group_Params group, std::span<const uint8_t> concatenated_public_values) {
140
   const auto public_value_lengths = public_value_lengths_for_group(group);
20✔
141
   auto alg_ids = algorithm_identifiers_for_group(group);
20✔
142
   BOTAN_ASSERT_NOMSG(public_value_lengths.size() == alg_ids.size());
20✔
143

144
   const auto expected_public_values_length =
145
      reduce(public_value_lengths, size_t(0), [](size_t acc, size_t len) { return acc + len; });
40✔
146
   if(expected_public_values_length != concatenated_public_values.size()) {
20✔
147
      throw Decoding_Error("Concatenated public values have an unexpected length");
2✔
148
   }
149

150
   BufferSlicer public_value_slicer(concatenated_public_values);
18✔
151
   std::vector<std::unique_ptr<Public_Key>> pks;
18✔
152
   for(size_t idx = 0; idx < alg_ids.size(); ++idx) {
52✔
153
      pks.emplace_back(load_public_key(alg_ids[idx], public_value_slicer.take(public_value_lengths[idx])));
69✔
154
   }
155
   BOTAN_ASSERT_NOMSG(public_value_slicer.empty());
17✔
156
   return std::make_unique<Hybrid_KEM_PublicKey>(std::move(pks));
17✔
157
}
38✔
158

159
Hybrid_KEM_PublicKey::Hybrid_KEM_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks) {
56✔
160
   BOTAN_ARG_CHECK(pks.size() >= 2, "List of public keys must include at least two keys");
56✔
161
   BOTAN_ARG_CHECK(std::all_of(pks.begin(), pks.end(), [](const auto& pk) { return pk != nullptr; }),
150✔
162
                   "List of public keys contains a nullptr");
163
   BOTAN_ARG_CHECK(std::all_of(pks.begin(),
144✔
164
                               pks.end(),
165
                               [](const auto& pk) {
166
                                  return pk->supports_operation(PublicKeyOperation::KeyEncapsulation) ||
167
                                         pk->supports_operation(PublicKeyOperation::KeyAgreement);
168
                               }),
169
                   "Some provided public key is not compatible with this hybrid wrapper");
170

171
   std::transform(
46✔
172
      pks.begin(), pks.end(), std::back_inserter(m_public_keys), [](auto& key) -> std::unique_ptr<Public_Key> {
140✔
173
         if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
142✔
174
            !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
48✔
175
            return std::make_unique<KEX_to_KEM_Adapter_PublicKey>(std::move(key));
96✔
176
         } else {
177
            return std::move(key);
46✔
178
         }
179
      });
180

181
   m_key_length =
92✔
182
      reduce(m_public_keys, size_t(0), [](size_t kl, const auto& key) { return std::max(kl, key->key_length()); });
186✔
183
   m_estimated_strength = reduce(
46✔
184
      m_public_keys, size_t(0), [](size_t es, const auto& key) { return std::max(es, key->estimated_strength()); });
146✔
185
}
56✔
186

187
std::string Hybrid_KEM_PublicKey::algo_name() const {
17✔
188
   std::ostringstream algo_name("Hybrid(");
17✔
189
   for(size_t i = 0; i < m_public_keys.size(); ++i) {
51✔
190
      if(i > 0) {
34✔
191
         algo_name << ",";
17✔
192
      }
193
      algo_name << m_public_keys[i]->algo_name();
68✔
194
   }
195
   algo_name << ")";
17✔
196
   return algo_name.str();
34✔
197
}
17✔
198

199
size_t Hybrid_KEM_PublicKey::estimated_strength() const {
×
200
   return m_estimated_strength;
×
201
}
202

203
size_t Hybrid_KEM_PublicKey::key_length() const {
17✔
204
   return m_key_length;
17✔
205
}
206

207
bool Hybrid_KEM_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
×
208
   return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
×
209
}
210

211
AlgorithmIdentifier Hybrid_KEM_PublicKey::algorithm_identifier() const {
×
212
   throw Botan::Not_Implemented("Hybrid keys don't have an algorithm identifier");
×
213
}
214

215
std::vector<uint8_t> Hybrid_KEM_PublicKey::public_key_bits() const {
21✔
216
   return raw_public_key_bits();
21✔
217
}
218

219
std::vector<uint8_t> Hybrid_KEM_PublicKey::raw_public_key_bits() const {
25✔
220
   // draft-ietf-tls-hybrid-design-06 3.2
221
   //   The values are directly concatenated, without any additional encoding
222
   //   or length fields; this assumes that the representation and length of
223
   //   elements is fixed once the algorithm is fixed.  If concatenation were
224
   //   to be used with values that are not fixed-length, a length prefix or
225
   //   other unambiguous encoding must be used to ensure that the composition
226
   //   of the two values is injective.
227
   return reduce(m_public_keys, std::vector<uint8_t>(), [](auto pkb, const auto& key) {
76✔
228
      return concat(pkb, key->raw_public_key_bits());
153✔
229
   });
50✔
230
}
231

232
std::unique_ptr<Private_Key> Hybrid_KEM_PublicKey::generate_another(RandomNumberGenerator& rng) const {
×
233
   std::vector<std::unique_ptr<Private_Key>> new_private_keys;
×
234
   std::transform(
×
235
      m_public_keys.begin(), m_public_keys.end(), std::back_inserter(new_private_keys), [&](const auto& public_key) {
×
236
         return public_key->generate_another(rng);
×
237
      });
238
   return std::make_unique<Hybrid_KEM_PrivateKey>(std::move(new_private_keys));
×
239
}
×
240

241
bool Hybrid_KEM_PublicKey::supports_operation(PublicKeyOperation op) const {
×
242
   return PublicKeyOperation::KeyEncapsulation == op;
×
243
}
244

245
namespace {
246

247
class Hybrid_KEM_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF {
×
248
   public:
249
      Hybrid_KEM_Encryption_Operation(const Hybrid_KEM_PublicKey& key,
21✔
250
                                      std::string_view kdf,
251
                                      std::string_view provider) :
21✔
252
            PK_Ops::KEM_Encryption_with_KDF(kdf), m_raw_kem_shared_key_length(0), m_encapsulated_key_length(0) {
21✔
253
         m_kem_encryptors.reserve(key.public_keys().size());
21✔
254
         for(const auto& k : key.public_keys()) {
64✔
255
            const auto& newenc = m_kem_encryptors.emplace_back(*k, "Raw", provider);
43✔
256
            m_raw_kem_shared_key_length += newenc.shared_key_length(0 /* no KDF */);
43✔
257
            m_encapsulated_key_length += newenc.encapsulated_key_length();
43✔
258
         }
259
      }
21✔
260

261
      size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; }
21✔
262

263
      size_t encapsulated_key_length() const override { return m_encapsulated_key_length; }
21✔
264

265
      void raw_kem_encrypt(std::span<uint8_t> out_encapsulated_key,
21✔
266
                           std::span<uint8_t> raw_shared_key,
267
                           Botan::RandomNumberGenerator& rng) override {
268
         BOTAN_ASSERT_NOMSG(out_encapsulated_key.size() == encapsulated_key_length());
21✔
269
         BOTAN_ASSERT_NOMSG(raw_shared_key.size() == raw_kem_shared_key_length());
21✔
270

271
         BufferStuffer encaps_key_stuffer(out_encapsulated_key);
21✔
272
         BufferStuffer shared_key_stuffer(raw_shared_key);
21✔
273

274
         for(auto& kem_enc : m_kem_encryptors) {
63✔
275
            kem_enc.encrypt(encaps_key_stuffer.next(kem_enc.encapsulated_key_length()),
43✔
276
                            shared_key_stuffer.next(kem_enc.shared_key_length(0 /* no KDF */)),
277
                            rng);
278
         }
279
      }
20✔
280

281
   private:
282
      std::vector<PK_KEM_Encryptor> m_kem_encryptors;
283
      size_t m_raw_kem_shared_key_length;
284
      size_t m_encapsulated_key_length;
285
};
286

287
}  // namespace
288

289
std::unique_ptr<Botan::PK_Ops::KEM_Encryption> Hybrid_KEM_PublicKey::create_kem_encryption_op(
21✔
290
   std::string_view kdf, std::string_view provider) const {
291
   return std::make_unique<Hybrid_KEM_Encryption_Operation>(*this, kdf, provider);
21✔
292
}
293

294
namespace {
295

296
auto extract_public_keys(const std::vector<std::unique_ptr<Private_Key>>& private_keys) {
34✔
297
   std::vector<std::unique_ptr<Public_Key>> public_keys;
34✔
298
   public_keys.reserve(private_keys.size());
34✔
299
   for(const auto& private_key : private_keys) {
92✔
300
      BOTAN_ARG_CHECK(private_key != nullptr, "List of private keys contains a nullptr");
61✔
301
      public_keys.push_back(private_key->public_key());
116✔
302
   }
303
   return public_keys;
31✔
304
}
3✔
305

306
}  // namespace
307

308
std::unique_ptr<Hybrid_KEM_PrivateKey> Hybrid_KEM_PrivateKey::generate_from_group(Group_Params group,
21✔
309
                                                                                  RandomNumberGenerator& rng) {
310
   const auto algo_spec = algorithm_specs_for_group(group);
21✔
311
   std::vector<std::unique_ptr<Private_Key>> private_keys;
21✔
312
   private_keys.reserve(algo_spec.size());
21✔
313
   for(const auto& spec : algo_spec) {
63✔
314
      private_keys.push_back(create_private_key(spec.first, rng, spec.second));
84✔
315
   }
316
   return std::make_unique<Hybrid_KEM_PrivateKey>(std::move(private_keys));
42✔
317
}
21✔
318

319
Hybrid_KEM_PrivateKey::Hybrid_KEM_PrivateKey(std::vector<std::unique_ptr<Private_Key>> sks) :
34✔
320
      Hybrid_KEM_PublicKey(extract_public_keys(sks)) {
34✔
321
   BOTAN_ARG_CHECK(sks.size() >= 2, "List of private keys must include at least two keys");
25✔
322
   BOTAN_ARG_CHECK(std::all_of(sks.begin(),
76✔
323
                               sks.end(),
324
                               [](const auto& sk) {
325
                                  return sk->supports_operation(PublicKeyOperation::KeyEncapsulation) ||
326
                                         sk->supports_operation(PublicKeyOperation::KeyAgreement);
327
                               }),
328
                   "Some provided private key is not compatible with this hybrid wrapper");
329

330
   std::transform(
25✔
331
      sks.begin(), sks.end(), std::back_inserter(m_private_keys), [](auto& key) -> std::unique_ptr<Private_Key> {
76✔
332
         if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
77✔
333
            !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
26✔
334
            auto ka_key = dynamic_cast<PK_Key_Agreement_Key*>(key.get());
26✔
335
            BOTAN_ASSERT_NONNULL(ka_key);
26✔
336
            (void)key.release();
26✔
337
            return std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(std::unique_ptr<PK_Key_Agreement_Key>(ka_key));
52✔
338
         } else {
339
            return std::move(key);
25✔
340
         }
341
      });
342
}
25✔
343

344
secure_vector<uint8_t> Hybrid_KEM_PrivateKey::private_key_bits() const {
×
345
   throw Not_Implemented("Hybrid private keys cannot be serialized");
×
346
}
347

348
std::unique_ptr<Public_Key> Hybrid_KEM_PrivateKey::public_key() const {
×
349
   return std::make_unique<Hybrid_KEM_PublicKey>(extract_public_keys(m_private_keys));
×
350
}
351

352
bool Hybrid_KEM_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const {
×
353
   return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
×
354
}
355

356
namespace {
357

358
class Hybrid_KEM_Decryption final : public PK_Ops::KEM_Decryption_with_KDF {
×
359
   public:
360
      Hybrid_KEM_Decryption(const Hybrid_KEM_PrivateKey& key,
25✔
361
                            RandomNumberGenerator& rng,
362
                            const std::string_view kdf,
363
                            const std::string_view provider) :
25✔
364
            PK_Ops::KEM_Decryption_with_KDF(kdf), m_encapsulated_key_length(0), m_raw_kem_shared_key_length(0) {
25✔
365
         m_decryptors.reserve(key.private_keys().size());
25✔
366
         for(const auto& private_key : key.private_keys()) {
76✔
367
            const auto& newdec = m_decryptors.emplace_back(*private_key, rng, "Raw", provider);
51✔
368
            m_encapsulated_key_length += newdec.encapsulated_key_length();
51✔
369
            m_raw_kem_shared_key_length += newdec.shared_key_length(0 /* no KDF */);
51✔
370
         }
371
      }
25✔
372

373
      void raw_kem_decrypt(std::span<uint8_t> out_shared_key, std::span<const uint8_t> encap_key) override {
23✔
374
         BOTAN_ASSERT_NOMSG(out_shared_key.size() == raw_kem_shared_key_length());
23✔
375
         BOTAN_ASSERT_NOMSG(encap_key.size() == encapsulated_key_length());
23✔
376

377
         BufferSlicer encap_key_slicer(encap_key);
23✔
378
         BufferStuffer shared_secret_stuffer(out_shared_key);
23✔
379

380
         for(auto& decryptor : m_decryptors) {
69✔
381
            decryptor.decrypt(shared_secret_stuffer.next(decryptor.shared_key_length(0 /* no KDF */)),
47✔
382
                              encap_key_slicer.take(decryptor.encapsulated_key_length()));
383
         }
384
      }
22✔
385

386
      size_t encapsulated_key_length() const override { return m_encapsulated_key_length; }
23✔
387

388
      size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; }
23✔
389

390
   private:
391
      std::vector<PK_KEM_Decryptor> m_decryptors;
392
      size_t m_encapsulated_key_length;
393
      size_t m_raw_kem_shared_key_length;
394
};
395

396
}  // namespace
397

398
std::unique_ptr<Botan::PK_Ops::KEM_Decryption> Hybrid_KEM_PrivateKey::create_kem_decryption_op(
25✔
399
   RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider) const {
400
   return std::make_unique<Hybrid_KEM_Decryption>(*this, rng, kdf, provider);
25✔
401
}
402

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

© 2025 Coveralls, Inc