Botan 3.11.0
Crypto and TLS for C&
hybrid_public_key.cpp
Go to the documentation of this file.
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/ec_group.h>
14#include <botan/pk_algs.h>
15#include <botan/internal/buffer_slicer.h>
16#include <botan/internal/buffer_stuffer.h>
17#include <botan/internal/concat_util.h>
18#include <botan/internal/hybrid_kem_ops.h>
19#include <botan/internal/kex_to_kem_adapter.h>
20#include <botan/internal/stl_util.h>
21#include <algorithm>
22#include <sstream>
23
24namespace Botan::TLS {
25
26namespace {
27
28std::vector<std::pair<std::string, std::string>> algorithm_specs_for_group(Group_Params group) {
29 BOTAN_ARG_CHECK(group.is_pqc_hybrid(), "Group is not hybrid");
30
31 switch(group.code()) {
32 // draft-kwiatkowski-tls-ecdhe-mlkem-02 Section 3
33 //
34 // NIST's special publication 800-56Cr2 approves the usage of HKDF with
35 // two distinct shared secrets, with the condition that the first one
36 // is computed by a FIPS-approved key-establishment scheme. FIPS also
37 // requires a certified implementation of the scheme, which will remain
38 // more ubiqutous for secp256r1 in the coming years.
39 //
40 // For this reason we put the ML-KEM-768 shared secret first in
41 // X25519MLKEM768, and the secp256r1 shared secret first in
42 // SecP256r1MLKEM768.
43 case Group_Params::HYBRID_X25519_ML_KEM_768:
44 return {{"ML-KEM", "ML-KEM-768"}, {"X25519", "X25519"}};
45 case Group_Params::HYBRID_SECP256R1_ML_KEM_768:
46 return {{"ECDH", "secp256r1"}, {"ML-KEM", "ML-KEM-768"}};
47 case Group_Params::HYBRID_SECP384R1_ML_KEM_1024:
48 return {{"ECDH", "secp384r1"}, {"ML-KEM", "ML-KEM-1024"}};
49
50 case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
51 return {{"X25519", "X25519"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}};
52 case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
53 return {{"X25519", "X25519"}, {"FrodoKEM", "eFrodoKEM-640-AES"}};
54 case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
55 return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-SHAKE"}};
56 case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS:
57 return {{"X448", "X448"}, {"FrodoKEM", "eFrodoKEM-976-AES"}};
58
59 case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
60 return {{"ECDH", "secp256r1"}, {"FrodoKEM", "eFrodoKEM-640-SHAKE"}};
61 case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
62 return {{"ECDH", "secp256r1"}, {"FrodoKEM", "eFrodoKEM-640-AES"}};
63
64 case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
65 return {{"ECDH", "secp384r1"}, {"FrodoKEM", "eFrodoKEM-976-SHAKE"}};
66 case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
67 return {{"ECDH", "secp384r1"}, {"FrodoKEM", "eFrodoKEM-976-AES"}};
68
69 case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
70 return {{"ECDH", "secp521r1"}, {"FrodoKEM", "eFrodoKEM-1344-SHAKE"}};
71 case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
72 return {{"ECDH", "secp521r1"}, {"FrodoKEM", "eFrodoKEM-1344-AES"}};
73
74 default:
75 return {};
76 }
77}
78
79std::vector<AlgorithmIdentifier> algorithm_identifiers_for_group(Group_Params group) {
80 BOTAN_ASSERT_NOMSG(group.is_pqc_hybrid());
81
82 const auto specs = algorithm_specs_for_group(group);
83 std::vector<AlgorithmIdentifier> result;
84 result.reserve(specs.size());
85
86 // This maps the string-based algorithm specs hard-coded above to OID-based
87 // AlgorithmIdentifier objects. The mapping is needed because
88 // load_public_key() depends on those while create_private_key() requires the
89 // strong-based spec.
90 //
91 // TODO: This is inconvenient, confusing and error-prone. Find a better way
92 // to load arbitrary public keys.
93 for(const auto& spec : specs) {
94 if(spec.first == "ECDH") {
95 result.push_back(AlgorithmIdentifier("ECDH", EC_Group::from_name(spec.second).DER_encode()));
96 } else {
97 result.push_back(AlgorithmIdentifier(spec.second, AlgorithmIdentifier::USE_EMPTY_PARAM));
98 }
99 }
100
101 return result;
102}
103
104std::vector<size_t> public_key_lengths_for_group(Group_Params group) {
105 BOTAN_ASSERT_NOMSG(group.is_pqc_hybrid());
106
107 // This duplicates information of the algorithm internals.
108 //
109 // TODO: Find a way to expose important algorithm constants globally
110 // in the library, to avoid violating the DRY principle.
111 switch(group.code()) {
112 case Group_Params::HYBRID_X25519_ML_KEM_768:
113 return {1184, 32};
114 case Group_Params::HYBRID_SECP256R1_ML_KEM_768:
115 return {65, 1184};
116 case Group_Params::HYBRID_SECP384R1_ML_KEM_1024:
117 return {97, 1568};
118
119 case Group_Params::HYBRID_X25519_eFRODOKEM_640_SHAKE_OQS:
120 case Group_Params::HYBRID_X25519_eFRODOKEM_640_AES_OQS:
121 return {32, 9616};
122
123 case Group_Params::HYBRID_X448_eFRODOKEM_976_SHAKE_OQS:
124 case Group_Params::HYBRID_X448_eFRODOKEM_976_AES_OQS:
125 return {56, 15632};
126
127 case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_SHAKE_OQS:
128 case Group_Params::HYBRID_SECP256R1_eFRODOKEM_640_AES_OQS:
129 return {65, 9616};
130
131 case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_SHAKE_OQS:
132 case Group_Params::HYBRID_SECP384R1_eFRODOKEM_976_AES_OQS:
133 return {97, 15632};
134
135 case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_SHAKE_OQS:
136 case Group_Params::HYBRID_SECP521R1_eFRODOKEM_1344_AES_OQS:
137 return {133, 21520};
138
139 default:
140 return {};
141 }
142}
143
144std::vector<std::unique_ptr<Public_Key>> convert_kex_to_kem_pks(std::vector<std::unique_ptr<Public_Key>> pks) {
145 std::vector<std::unique_ptr<Public_Key>> result;
146 std::transform(pks.begin(), pks.end(), std::back_inserter(result), [](auto& key) -> std::unique_ptr<Public_Key> {
147 BOTAN_ARG_CHECK(key != nullptr, "Public key list contains a nullptr");
148 if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
149 !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
150 return std::make_unique<KEX_to_KEM_Adapter_PublicKey>(std::move(key));
151 } else {
152 return std::move(key);
153 }
154 });
155 return result;
156}
157
158std::vector<std::unique_ptr<Private_Key>> convert_kex_to_kem_sks(std::vector<std::unique_ptr<Private_Key>> sks) {
159 std::vector<std::unique_ptr<Private_Key>> result;
160 std::transform(sks.begin(), sks.end(), std::back_inserter(result), [](auto& key) -> std::unique_ptr<Private_Key> {
161 BOTAN_ARG_CHECK(key != nullptr, "Private key list contains a nullptr");
162 if(key->supports_operation(PublicKeyOperation::KeyAgreement) &&
163 !key->supports_operation(PublicKeyOperation::KeyEncapsulation)) {
164 auto* ka_key = dynamic_cast<PK_Key_Agreement_Key*>(key.get());
165 BOTAN_ASSERT_NONNULL(ka_key);
166 (void)key.release();
167 return std::make_unique<KEX_to_KEM_Adapter_PrivateKey>(std::unique_ptr<PK_Key_Agreement_Key>(ka_key));
168 } else {
169 return std::move(key);
170 }
171 });
172 return result;
173}
174
175template <typename KEM_Operation>
176void concat_secret_combiner(KEM_Operation& op,
177 std::span<uint8_t> out_shared_secret,
178 const std::vector<secure_vector<uint8_t>>& shared_secrets,
179 size_t desired_shared_key_len) {
180 BOTAN_ARG_CHECK(out_shared_secret.size() == op.shared_key_length(desired_shared_key_len),
181 "Invalid output buffer size");
182
183 BufferStuffer shared_secret_stuffer(out_shared_secret);
184 for(const auto& ss : shared_secrets) {
185 shared_secret_stuffer.append(ss);
186 }
187 BOTAN_ASSERT_NOMSG(shared_secret_stuffer.full());
188}
189
190template <typename KEM_Operation>
191size_t concat_shared_key_length(const std::vector<KEM_Operation>& operation) {
192 return reduce(
193 operation, size_t(0), [](size_t acc, const auto& op) { return acc + op.shared_key_length(0 /*no KDF*/); });
194}
195
196/// Encryptor that simply concatenates the multiple shared secrets
197class Hybrid_TLS_KEM_Encryptor final : public KEM_Encryption_with_Combiner {
198 public:
199 Hybrid_TLS_KEM_Encryptor(const std::vector<std::unique_ptr<Public_Key>>& public_keys, std::string_view provider) :
200 KEM_Encryption_with_Combiner(public_keys, provider) {}
201
202 void combine_shared_secrets(std::span<uint8_t> out_shared_secret,
203 const std::vector<secure_vector<uint8_t>>& shared_secrets,
204 const std::vector<std::vector<uint8_t>>& /*ciphertexts*/,
205 size_t desired_shared_key_len,
206 std::span<const uint8_t> /*salt*/) override {
207 concat_secret_combiner(*this, out_shared_secret, shared_secrets, desired_shared_key_len);
208 }
209
210 size_t shared_key_length(size_t /*desired_shared_key_len*/) const override {
211 return concat_shared_key_length(encryptors());
212 }
213};
214
215/// Decryptor that simply concatenates the multiple shared secrets
216class Hybrid_TLS_KEM_Decryptor final : public KEM_Decryption_with_Combiner {
217 public:
218 Hybrid_TLS_KEM_Decryptor(const std::vector<std::unique_ptr<Private_Key>>& private_keys,
219 RandomNumberGenerator& rng,
220 const std::string_view provider) :
221 KEM_Decryption_with_Combiner(private_keys, rng, provider) {}
222
223 void combine_shared_secrets(std::span<uint8_t> out_shared_secret,
224 const std::vector<secure_vector<uint8_t>>& shared_secrets,
225 const std::vector<std::vector<uint8_t>>& /*ciphertexts*/,
226 size_t desired_shared_key_len,
227 std::span<const uint8_t> /*salt*/) override {
228 concat_secret_combiner(*this, out_shared_secret, shared_secrets, desired_shared_key_len);
229 }
230
231 size_t shared_key_length(size_t /*desired_shared_key_len*/) const override {
232 return concat_shared_key_length(decryptors());
233 }
234};
235
236} // namespace
237
238std::unique_ptr<Hybrid_KEM_PublicKey> Hybrid_KEM_PublicKey::load_for_group(
239 Group_Params group, std::span<const uint8_t> concatenated_public_keys) {
240 const auto public_key_lengths = public_key_lengths_for_group(group);
241 auto alg_ids = algorithm_identifiers_for_group(group);
242 BOTAN_ASSERT_NOMSG(public_key_lengths.size() == alg_ids.size());
243
244 const auto expected_public_keys_length =
245 reduce(public_key_lengths, size_t(0), [](size_t acc, size_t len) { return acc + len; });
246 if(expected_public_keys_length != concatenated_public_keys.size()) {
247 throw Decoding_Error("Concatenated public values have an unexpected length");
248 }
249
250 BufferSlicer public_key_slicer(concatenated_public_keys);
251 std::vector<std::unique_ptr<Public_Key>> pks;
252 pks.reserve(alg_ids.size());
253 for(size_t idx = 0; idx < alg_ids.size(); ++idx) {
254 pks.emplace_back(load_public_key(alg_ids[idx], public_key_slicer.take(public_key_lengths[idx])));
255 }
256 BOTAN_ASSERT_NOMSG(public_key_slicer.empty());
257 return std::make_unique<Hybrid_KEM_PublicKey>(std::move(pks));
258}
259
260Hybrid_KEM_PublicKey::Hybrid_KEM_PublicKey(std::vector<std::unique_ptr<Public_Key>> pks) :
261 Hybrid_PublicKey(convert_kex_to_kem_pks(std::move(pks))) {}
262
263Hybrid_KEM_PrivateKey::Hybrid_KEM_PrivateKey(std::vector<std::unique_ptr<Private_Key>> sks) :
264 Hybrid_PublicKey(convert_kex_to_kem_pks(extract_public_keys(sks))),
265 Hybrid_PrivateKey(convert_kex_to_kem_sks(std::move(sks))) {}
266
268 std::ostringstream algo_name("Hybrid(");
269 for(size_t i = 0; i < public_keys().size(); ++i) {
270 if(i > 0) {
271 algo_name << ",";
272 }
273 algo_name << public_keys().at(i)->algo_name();
274 }
275 algo_name << ")";
276 return algo_name.str();
277}
278
280 throw Botan::Not_Implemented("Hybrid keys don't have an algorithm identifier");
281}
282
283std::vector<uint8_t> Hybrid_KEM_PublicKey::public_key_bits() const {
284 return raw_public_key_bits();
285}
286
287std::vector<uint8_t> Hybrid_KEM_PublicKey::raw_public_key_bits() const {
288 // draft-ietf-tls-hybrid-design-06 3.2
289 // The values are directly concatenated, without any additional encoding
290 // or length fields; this assumes that the representation and length of
291 // elements is fixed once the algorithm is fixed. If concatenation were
292 // to be used with values that are not fixed-length, a length prefix or
293 // other unambiguous encoding must be used to ensure that the composition
294 // of the two values is injective.
295 return reduce(public_keys(), std::vector<uint8_t>(), [](auto pkb, const auto& key) {
296 return concat(pkb, key->raw_public_key_bits());
297 });
298}
299
300std::unique_ptr<Private_Key> Hybrid_KEM_PublicKey::generate_another(RandomNumberGenerator& rng) const {
301 return std::make_unique<Hybrid_KEM_PrivateKey>(generate_other_sks_from_pks(rng));
302}
303
304std::unique_ptr<Botan::PK_Ops::KEM_Encryption> Hybrid_KEM_PublicKey::create_kem_encryption_op(
305 std::string_view params, std::string_view provider) const {
306 if(params != "Raw" && !params.empty()) {
307 throw Botan::Invalid_Argument("Hybrid KEM encryption does not support KDFs");
308 }
309 return std::make_unique<Hybrid_TLS_KEM_Encryptor>(public_keys(), provider);
310}
311
312std::unique_ptr<Hybrid_KEM_PrivateKey> Hybrid_KEM_PrivateKey::generate_from_group(Group_Params group,
314 const auto algo_spec = algorithm_specs_for_group(group);
315 std::vector<std::unique_ptr<Private_Key>> private_keys;
316 private_keys.reserve(algo_spec.size());
317 for(const auto& spec : algo_spec) {
318 private_keys.push_back(create_private_key(spec.first, rng, spec.second));
319 }
320 return std::make_unique<Hybrid_KEM_PrivateKey>(std::move(private_keys));
321}
322
323std::unique_ptr<Botan::PK_Ops::KEM_Decryption> Hybrid_KEM_PrivateKey::create_kem_decryption_op(
324 RandomNumberGenerator& rng, std::string_view params, std::string_view provider) const {
325 if(params != "Raw" && !params.empty()) {
326 throw Botan::Invalid_Argument("Hybrid KEM decryption does not support KDFs");
327 }
328 return std::make_unique<Hybrid_TLS_KEM_Decryptor>(private_keys(), rng, provider);
329}
330
331} // namespace Botan::TLS
#define BOTAN_ASSERT_NOMSG(expr)
Definition assert.h:75
#define BOTAN_ARG_CHECK(expr, msg)
Definition assert.h:33
std::span< const uint8_t > take(const size_t count)
static EC_Group from_name(std::string_view name)
Definition ec_group.cpp:438
std::vector< uint8_t > DER_encode(EC_Group_Encoding form) const
Definition ec_group.cpp:699
const std::vector< std::unique_ptr< Private_Key > > & private_keys() const
Definition hybrid_kem.h:119
Hybrid_PrivateKey(const Hybrid_PrivateKey &)=delete
static std::vector< std::unique_ptr< Public_Key > > extract_public_keys(const std::vector< std::unique_ptr< Private_Key > > &private_keys)
Hybrid_PublicKey(std::vector< std::unique_ptr< Public_Key > > public_keys)
Constructor for a list of multiple KEM public keys.
std::vector< std::unique_ptr< Private_Key > > generate_other_sks_from_pks(RandomNumberGenerator &rng) const
Helper function for generate_another. Generate a new private key for each public key in this hybrid k...
const std::vector< std::unique_ptr< Public_Key > > & public_keys() const
Definition hybrid_kem.h:66
static std::unique_ptr< Hybrid_KEM_PrivateKey > generate_from_group(Group_Params group, RandomNumberGenerator &rng)
std::unique_ptr< PK_Ops::KEM_Decryption > create_kem_decryption_op(RandomNumberGenerator &rng, std::string_view params, std::string_view provider="base") const override
Hybrid_KEM_PrivateKey(std::vector< std::unique_ptr< Private_Key > > private_keys)
AlgorithmIdentifier algorithm_identifier() const override
std::vector< uint8_t > raw_public_key_bits() const override
Hybrid_KEM_PublicKey(std::vector< std::unique_ptr< Public_Key > > pks)
static std::unique_ptr< Hybrid_KEM_PublicKey > load_for_group(Group_Params group, std::span< const uint8_t > concatenated_public_values)
std::unique_ptr< Private_Key > generate_another(RandomNumberGenerator &rng) const final
std::string algo_name() const override
std::unique_ptr< PK_Ops::KEM_Encryption > create_kem_encryption_op(std::string_view params, std::string_view provider="base") const override
std::vector< uint8_t > public_key_bits() const override
std::unique_ptr< Private_Key > create_private_key(std::string_view alg_name, RandomNumberGenerator &rng, std::string_view params, std::string_view provider)
Definition pk_algs.cpp:493
constexpr auto concat(Rs &&... ranges)
Definition concat_util.h:90
RetT reduce(const std::vector< KeyT > &keys, RetT acc, ReducerT reducer)
Definition stl_util.h:29
std::unique_ptr< Public_Key > load_public_key(const AlgorithmIdentifier &alg_id, std::span< const uint8_t > key_bits)
Definition pk_algs.cpp:130