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

randombit / botan / 6430235977

06 Oct 2023 10:03AM UTC coverage: 91.692% (+0.004%) from 91.688%
6430235977

Pull #3733

github

web-flow
Merge 1e15f8c79 into 30ecb8719
Pull Request #3733: [TLS 1.3] Clean up support Hybrid PQ/T algorithms

79958 of 87203 relevant lines covered (91.69%)

8673284.41 hits per line

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

74.3
/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) {
5✔
25
   BOTAN_ARG_CHECK(is_hybrid(group), "Group is not hybrid");
5✔
26

27
   switch(group) {
5✔
28
      case Group_Params::HYBRID_X25519_KYBER_512_R3_OQS:
2✔
29
      case Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE:
2✔
30
         return {{"Curve25519", "Curve25519"}, {"Kyber", "Kyber-512-r3"}};
6✔
31
      case Group_Params::HYBRID_X25519_KYBER_768_R3_OQS:
3✔
32
         return {{"Curve25519", "Curve25519"}, {"Kyber", "Kyber-768-r3"}};
9✔
33

34
      case Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS:
×
35
         return {{"ECDH", "secp256r1"}, {"Kyber", "Kyber-512-r3"}};
×
36
      case Group_Params::HYBRID_SECP256R1_KYBER_768_R3_OQS:
×
37
         return {{"ECDH", "secp256r1"}, {"Kyber", "Kyber-768-r3"}};
×
38
      case Group_Params::HYBRID_SECP384R1_KYBER_768_R3_OQS:
×
39
         return {{"ECDH", "secp384r1"}, {"Kyber", "Kyber-768-r3"}};
×
40
      case Group_Params::HYBRID_SECP521R1_KYBER_1024_R3_OQS:
×
41
         return {{"ECDH", "secp521r1"}, {"Kyber", "Kyber-1024-r3"}};
×
42

43
      default:
×
44
         return {};
5✔
45
   }
46
}
47

48
std::vector<AlgorithmIdentifier> algorithm_identifiers_for_group(Group_Params group) {
2✔
49
   BOTAN_ASSERT_NOMSG(is_hybrid(group));
2✔
50

51
   const auto specs = algorithm_specs_for_group(group);
2✔
52
   std::vector<AlgorithmIdentifier> result;
2✔
53
   result.reserve(specs.size());
2✔
54

55
   // This maps the string-based algorithm specs hard-coded above to OID-based
56
   // AlgorithmIdentifier objects. The mapping is needed because
57
   // load_public_key() depends on those while create_private_key() requires the
58
   // strong-based spec.
59
   //
60
   // TODO: This is inconvenient, confusing and error-prone. Find a better way
61
   //       to load arbitrary public keys.
62
   for(const auto& spec : specs) {
6✔
63
      result.push_back(AlgorithmIdentifier(spec.second, AlgorithmIdentifier::USE_EMPTY_PARAM));
8✔
64
   }
65

66
   return result;
2✔
67
}
2✔
68

69
std::vector<size_t> public_value_lengths_for_group(Group_Params group) {
2✔
70
   BOTAN_ASSERT_NOMSG(is_hybrid(group));
2✔
71

72
   // This duplicates information of the algorithm internals.
73
   //
74
   // TODO: Find a way to expose important algorithm constants globally
75
   //       in the library, to avoid violating the DRY principle.
76
   switch(group) {
2✔
77
      case Group_Params::HYBRID_X25519_KYBER_512_R3_CLOUDFLARE:
1✔
78
      case Group_Params::HYBRID_X25519_KYBER_512_R3_OQS:
1✔
79
         return {32, 800};
1✔
80
      case Group_Params::HYBRID_X25519_KYBER_768_R3_OQS:
1✔
81
         return {32, 1184};
1✔
82

83
      case Group_Params::HYBRID_SECP256R1_KYBER_512_R3_OQS:
×
84
         return {32, 800};
×
85
      case Group_Params::HYBRID_SECP256R1_KYBER_768_R3_OQS:
×
86
         return {32, 1184};
×
87
      case Group_Params::HYBRID_SECP384R1_KYBER_768_R3_OQS:
×
88
         return {48, 1184};
×
89
      case Group_Params::HYBRID_SECP521R1_KYBER_1024_R3_OQS:
×
90
         return {66, 1568};
×
91

92
      default:
×
93
         return {};
×
94
   }
95
}
96

97
}  // namespace
98

99
std::unique_ptr<Hybrid_KEM_PublicKey> Hybrid_KEM_PublicKey::load_for_group(
2✔
100
   Group_Params group, std::span<const uint8_t> concatenated_public_values) {
101
   const auto public_value_lengths = public_value_lengths_for_group(group);
2✔
102
   auto alg_ids = algorithm_identifiers_for_group(group);
2✔
103
   BOTAN_ASSERT_NOMSG(public_value_lengths.size() == alg_ids.size());
2✔
104

105
   const auto expected_public_values_length =
6✔
106
      reduce(public_value_lengths, size_t(0), [](size_t acc, size_t len) { return acc + len; });
4✔
107
   BOTAN_ARG_CHECK(expected_public_values_length == concatenated_public_values.size(),
2✔
108
                   "Concatenated public values have an unexpected length");
109

110
   BufferSlicer public_value_slicer(concatenated_public_values);
2✔
111
   std::vector<std::unique_ptr<Public_Key>> pks;
2✔
112
   for(size_t idx = 0; idx < alg_ids.size(); ++idx) {
6✔
113
      pks.emplace_back(load_public_key(alg_ids[idx], public_value_slicer.take(public_value_lengths[idx])));
8✔
114
   }
115
   BOTAN_ASSERT_NOMSG(public_value_slicer.empty());
2✔
116
   return std::make_unique<Hybrid_KEM_PublicKey>(std::move(pks));
2✔
117
}
4✔
118

119
Hybrid_KEM_PublicKey::Hybrid_KEM_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks) {
23✔
120
   BOTAN_ARG_CHECK(pks.size() >= 2, "List of public keys must include at least two keys");
23✔
121
   BOTAN_ARG_CHECK(std::all_of(pks.begin(), pks.end(), [](const auto& pk) { return pk != nullptr; }),
51✔
122
                   "List of public keys contains a nullptr");
123
   BOTAN_ARG_CHECK(std::all_of(pks.begin(),
45✔
124
                               pks.end(),
125
                               [](const auto& pk) {
126
                                  return pk->supports_operation(PublicKeyOperation::KeyEncapsulation) ||
127
                                         pk->supports_operation(PublicKeyOperation::KeyAgreement);
128
                               }),
129
                   "Some provided public key is not compatible with this hybrid wrapper");
130

131
   std::transform(
13✔
132
      pks.begin(), pks.end(), std::back_inserter(m_public_keys), [](auto& key) -> std::unique_ptr<Public_Key> {
41✔
133
         if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
43✔
134
            !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
15✔
135
            return std::make_unique<KEX_to_KEM_Adapter_PublicKey>(std::move(key));
30✔
136
         } else {
137
            return std::move(key);
13✔
138
         }
139
      });
140

141
   m_key_length =
26✔
142
      reduce(m_public_keys, size_t(0), [](size_t kl, const auto& key) { return std::max(kl, key->key_length()); });
59✔
143
   m_estimated_strength = reduce(
13✔
144
      m_public_keys, size_t(0), [](size_t es, const auto& key) { return std::max(es, key->estimated_strength()); });
50✔
145
}
23✔
146

147
std::string Hybrid_KEM_PublicKey::algo_name() const {
×
148
   std::ostringstream algo_name("Hybrid(");
×
149
   for(size_t i = 0; i < m_public_keys.size(); ++i) {
×
150
      if(i > 0) {
×
151
         algo_name << ",";
×
152
      }
153
      algo_name << m_public_keys[i]->algo_name();
×
154
   }
155
   algo_name << ")";
×
156
   return algo_name.str();
×
157
}
×
158

159
size_t Hybrid_KEM_PublicKey::estimated_strength() const {
×
160
   return m_estimated_strength;
×
161
}
162

163
size_t Hybrid_KEM_PublicKey::key_length() const {
×
164
   return m_key_length;
×
165
}
166

167
bool Hybrid_KEM_PublicKey::check_key(RandomNumberGenerator& rng, bool strong) const {
×
168
   return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
×
169
}
170

171
AlgorithmIdentifier Hybrid_KEM_PublicKey::algorithm_identifier() const {
×
172
   throw Botan::Not_Implemented("Hybrid keys don't have an algorithm identifier");
×
173
}
174

175
std::vector<uint8_t> Hybrid_KEM_PublicKey::public_key_bits() const {
3✔
176
   // Technically, that's not really correct. The docstring for public_key_bits()
177
   // states that it should return a BER-encoding of the public key.
178
   //
179
   // TODO: Perhaps add something like Public_Key::raw_public_key_bits() to
180
   //       better reflect what we need here.
181
   return public_value();
3✔
182
}
183

184
std::vector<uint8_t> Hybrid_KEM_PublicKey::public_value() const {
7✔
185
   // draft-ietf-tls-hybrid-design-06 3.2
186
   //   The values are directly concatenated, without any additional encoding
187
   //   or length fields; this assumes that the representation and length of
188
   //   elements is fixed once the algorithm is fixed.  If concatenation were
189
   //   to be used with values that are not fixed-length, a length prefix or
190
   //   other unambiguous encoding must be used to ensure that the composition
191
   //   of the two values is injective.
192
   return reduce(m_public_keys, std::vector<uint8_t>(), [](auto pkb, const auto& key) {
22✔
193
      // Technically, this is not correct! `public_key_bits()` is meant to
194
      // return a BER-encoded public key. For (e.g.) Kyber, that contract is
195
      // broken: It returns the raw encoding as used in the reference
196
      // implementation.
197
      //
198
      // TODO: Provide something like Public_Key::raw_public_key_bits() to
199
      //       reflect that difference. Also: Key agreement keys could return
200
      //       their raw public value there.
201
      return concat(pkb, key->public_key_bits());
30✔
202
   });
14✔
203
}
204

205
bool Hybrid_KEM_PublicKey::supports_operation(PublicKeyOperation op) const {
×
206
   return PublicKeyOperation::KeyEncapsulation == op;
×
207
}
208

209
namespace {
210

211
class Hybrid_KEM_Encryption_Operation final : public PK_Ops::KEM_Encryption_with_KDF {
×
212
   public:
213
      Hybrid_KEM_Encryption_Operation(const Hybrid_KEM_PublicKey& key,
6✔
214
                                      std::string_view kdf,
215
                                      std::string_view provider) :
6✔
216
            PK_Ops::KEM_Encryption_with_KDF(kdf), m_raw_kem_shared_key_length(0), m_encapsulated_key_length(0) {
6✔
217
         m_kem_encryptors.reserve(key.public_keys().size());
6✔
218
         for(const auto& k : key.public_keys()) {
19✔
219
            const auto& newenc = m_kem_encryptors.emplace_back(*k, "Raw", provider);
13✔
220
            m_raw_kem_shared_key_length += newenc.shared_key_length(0 /* no KDF */);
13✔
221
            m_encapsulated_key_length += newenc.encapsulated_key_length();
13✔
222
         }
223
      }
6✔
224

225
      size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; }
6✔
226

227
      size_t encapsulated_key_length() const override { return m_encapsulated_key_length; }
6✔
228

229
      void raw_kem_encrypt(std::span<uint8_t> out_encapsulated_key,
6✔
230
                           std::span<uint8_t> raw_shared_key,
231
                           Botan::RandomNumberGenerator& rng) override {
232
         BOTAN_ASSERT_NOMSG(out_encapsulated_key.size() == encapsulated_key_length());
6✔
233
         BOTAN_ASSERT_NOMSG(raw_shared_key.size() == raw_kem_shared_key_length());
6✔
234

235
         BufferStuffer encaps_key_stuffer(out_encapsulated_key);
6✔
236
         BufferStuffer shared_key_stuffer(raw_shared_key);
6✔
237

238
         for(auto& kem_enc : m_kem_encryptors) {
19✔
239
            kem_enc.encrypt(encaps_key_stuffer.next(kem_enc.encapsulated_key_length()),
13✔
240
                            shared_key_stuffer.next(kem_enc.shared_key_length(0 /* no KDF */)),
241
                            rng);
242
         }
243
      }
6✔
244

245
   private:
246
      std::vector<PK_KEM_Encryptor> m_kem_encryptors;
247
      size_t m_raw_kem_shared_key_length;
248
      size_t m_encapsulated_key_length;
249
};
250

251
}  // namespace
252

253
std::unique_ptr<Botan::PK_Ops::KEM_Encryption> Hybrid_KEM_PublicKey::create_kem_encryption_op(
6✔
254
   std::string_view kdf, std::string_view provider) const {
255
   return std::make_unique<Hybrid_KEM_Encryption_Operation>(*this, kdf, provider);
6✔
256
}
257

258
namespace {
259

260
auto extract_public_keys(const std::vector<std::unique_ptr<Private_Key>>& private_keys) {
16✔
261
   std::vector<std::unique_ptr<Public_Key>> public_keys;
16✔
262
   public_keys.reserve(private_keys.size());
16✔
263
   for(const auto& private_key : private_keys) {
38✔
264
      BOTAN_ARG_CHECK(private_key != nullptr, "List of private keys contains a nullptr");
25✔
265
      public_keys.push_back(private_key->public_key());
44✔
266
   }
267
   return public_keys;
13✔
268
}
3✔
269

270
}  // namespace
271

272
std::unique_ptr<Hybrid_KEM_PrivateKey> Hybrid_KEM_PrivateKey::generate_from_group(Group_Params group,
3✔
273
                                                                                  RandomNumberGenerator& rng) {
274
   const auto algo_spec = algorithm_specs_for_group(group);
3✔
275
   std::vector<std::unique_ptr<Private_Key>> private_keys;
3✔
276
   private_keys.reserve(algo_spec.size());
3✔
277
   for(const auto& spec : algo_spec) {
9✔
278
      private_keys.push_back(create_private_key(spec.first, rng, spec.second));
12✔
279
   }
280
   return std::make_unique<Hybrid_KEM_PrivateKey>(std::move(private_keys));
6✔
281
}
3✔
282

283
Hybrid_KEM_PrivateKey::Hybrid_KEM_PrivateKey(std::vector<std::unique_ptr<Private_Key>> sks) :
16✔
284
      Hybrid_KEM_PublicKey(extract_public_keys(sks)) {
16✔
285
   BOTAN_ARG_CHECK(sks.size() >= 2, "List of private keys must include at least two keys");
7✔
286
   BOTAN_ARG_CHECK(std::all_of(sks.begin(),
22✔
287
                               sks.end(),
288
                               [](const auto& sk) {
289
                                  return sk->supports_operation(PublicKeyOperation::KeyEncapsulation) ||
290
                                         sk->supports_operation(PublicKeyOperation::KeyAgreement);
291
                               }),
292
                   "Some provided private key is not compatible with this hybrid wrapper");
293

294
   std::transform(
7✔
295
      sks.begin(), sks.end(), std::back_inserter(m_private_keys), [](auto& key) -> std::unique_ptr<Private_Key> {
22✔
296
         if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
23✔
297
            !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
8✔
298
            auto ka_key = dynamic_cast<PK_Key_Agreement_Key*>(key.get());
8✔
299
            BOTAN_ASSERT_NONNULL(ka_key);
8✔
300
            (void)key.release();
8✔
301
            return std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(std::unique_ptr<PK_Key_Agreement_Key>(ka_key));
16✔
302
         } else {
303
            return std::move(key);
15✔
304
         }
305
      });
306
}
7✔
307

308
secure_vector<uint8_t> Hybrid_KEM_PrivateKey::private_key_bits() const {
×
309
   throw Not_Implemented("Hybrid private keys cannot be serialized");
×
310
}
311

312
std::unique_ptr<Public_Key> Hybrid_KEM_PrivateKey::public_key() const {
×
313
   return std::make_unique<Hybrid_KEM_PublicKey>(extract_public_keys(m_private_keys));
×
314
}
315

316
bool Hybrid_KEM_PrivateKey::check_key(RandomNumberGenerator& rng, bool strong) const {
×
317
   return reduce(m_public_keys, true, [&](bool ckr, const auto& key) { return ckr && key->check_key(rng, strong); });
×
318
}
319

320
namespace {
321

322
class Hybrid_KEM_Decryption final : public PK_Ops::KEM_Decryption_with_KDF {
×
323
   public:
324
      Hybrid_KEM_Decryption(const Hybrid_KEM_PrivateKey& key,
7✔
325
                            RandomNumberGenerator& rng,
326
                            const std::string_view kdf,
327
                            const std::string_view provider) :
7✔
328
            PK_Ops::KEM_Decryption_with_KDF(kdf), m_encapsulated_key_length(0), m_raw_kem_shared_key_length(0) {
7✔
329
         m_decryptors.reserve(key.private_keys().size());
7✔
330
         for(const auto& private_key : key.private_keys()) {
22✔
331
            const auto& newdec = m_decryptors.emplace_back(*private_key, rng, "Raw", provider);
15✔
332
            m_encapsulated_key_length += newdec.encapsulated_key_length();
15✔
333
            m_raw_kem_shared_key_length += newdec.shared_key_length(0 /* no KDF */);
15✔
334
         }
335
      }
7✔
336

337
      void raw_kem_decrypt(std::span<uint8_t> out_shared_key, std::span<const uint8_t> encap_key) override {
7✔
338
         BOTAN_ASSERT_NOMSG(out_shared_key.size() == raw_kem_shared_key_length());
7✔
339
         BOTAN_ASSERT_NOMSG(encap_key.size() == encapsulated_key_length());
7✔
340

341
         BufferSlicer encap_key_slicer(encap_key);
7✔
342
         BufferStuffer shared_secret_stuffer(out_shared_key);
7✔
343

344
         for(auto& decryptor : m_decryptors) {
22✔
345
            decryptor.decrypt(shared_secret_stuffer.next(decryptor.shared_key_length(0 /* no KDF */)),
15✔
346
                              encap_key_slicer.take(decryptor.encapsulated_key_length()));
347
         }
348
      }
7✔
349

350
      size_t encapsulated_key_length() const override { return m_encapsulated_key_length; }
7✔
351

352
      size_t raw_kem_shared_key_length() const override { return m_raw_kem_shared_key_length; }
7✔
353

354
   private:
355
      std::vector<PK_KEM_Decryptor> m_decryptors;
356
      size_t m_encapsulated_key_length;
357
      size_t m_raw_kem_shared_key_length;
358
};
359

360
}  // namespace
361

362
std::unique_ptr<Botan::PK_Ops::KEM_Decryption> Hybrid_KEM_PrivateKey::create_kem_decryption_op(
7✔
363
   RandomNumberGenerator& rng, std::string_view kdf, std::string_view provider) const {
364
   return std::make_unique<Hybrid_KEM_Decryption>(*this, rng, kdf, provider);
7✔
365
}
366

367
}  // 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