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

randombit / botan / 18741093953

23 Oct 2025 07:36AM UTC coverage: 90.666% (-0.003%) from 90.669%
18741093953

Pull #5107

github

web-flow
Merge 19c70c57e into 46f6fc3b2
Pull Request #5107: Add optional callback for user-defined TLS 1.2 key derivation function

100422 of 110760 relevant lines covered (90.67%)

12182432.3 hits per line

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

81.38
/src/lib/tls/tls_callbacks.cpp
1
/*
2
* TLS Callbacks
3
* (C) 2016 Jack Lloyd
4
*     2017 Harry Reimann, Rohde & Schwarz Cybersecurity
5
*     2022 René Meusel, Hannes Rantzsch - neXenio GmbH
6
*     2023 René Meusel - Rohde & Schwarz Cybersecurity
7
*
8
* Botan is released under the Simplified BSD License (see license.txt)
9
*/
10

11
#include <botan/tls_callbacks.h>
12

13
#include <botan/dh.h>
14
#include <botan/dl_group.h>
15
#include <botan/ecdh.h>
16
#include <botan/ocsp.h>
17
#include <botan/pk_algs.h>
18
#include <botan/tls_algos.h>
19
#include <botan/tls_exceptn.h>
20
#include <botan/tls_policy.h>
21
#include <botan/x509path.h>
22
#include <botan/internal/ct_utils.h>
23
#include <botan/internal/fmt.h>
24
#include <botan/internal/stl_util.h>
25

26
#if defined(BOTAN_HAS_X25519)
27
   #include <botan/x25519.h>
28
#endif
29

30
#if defined(BOTAN_HAS_X448)
31
   #include <botan/x448.h>
32
#endif
33

34
#if defined(BOTAN_HAS_ML_KEM)
35
   #include <botan/ml_kem.h>
36
#endif
37

38
#if defined(BOTAN_HAS_FRODOKEM)
39
   #include <botan/frodokem.h>
40
#endif
41

42
#if defined(BOTAN_HAS_TLS_13_PQC)
43
   #include <botan/internal/hybrid_public_key.h>
44
#endif
45

46
namespace Botan {
47

48
void TLS::Callbacks::tls_inspect_handshake_msg(const Handshake_Message& /*unused*/) {
6,536✔
49
   // default is no op
50
}
6,536✔
51

52
std::string TLS::Callbacks::tls_server_choose_app_protocol(const std::vector<std::string>& /*unused*/) {
×
53
   return "";
×
54
}
55

56
std::string TLS::Callbacks::tls_peer_network_identity() {
790✔
57
   return "";
790✔
58
}
59

60
std::chrono::system_clock::time_point TLS::Callbacks::tls_current_timestamp() {
5,431✔
61
   return std::chrono::system_clock::now();
5,431✔
62
}
63

64
void TLS::Callbacks::tls_modify_extensions(Extensions& /*unused*/,
2,184✔
65
                                           Connection_Side /*unused*/,
66
                                           Handshake_Type /*unused*/) {}
2,184✔
67

68
void TLS::Callbacks::tls_examine_extensions(const Extensions& /*unused*/,
5,288✔
69
                                            Connection_Side /*unused*/,
70
                                            Handshake_Type /*unused*/) {}
5,288✔
71

72
bool TLS::Callbacks::tls_should_persist_resumption_information(const Session& session) {
2,616✔
73
   // RFC 5077 3.3
74
   //    The ticket_lifetime_hint field contains a hint from the server about
75
   //    how long the ticket should be stored. A value of zero is reserved to
76
   //    indicate that the lifetime of the ticket is unspecified.
77
   //
78
   // RFC 8446 4.6.1
79
   //    [A ticket_lifetime] of zero indicates that the ticket should be discarded
80
   //    immediately.
81
   //
82
   // By default we opt to keep all sessions, except for TLS 1.3 with a lifetime
83
   // hint of zero.
84
   return session.lifetime_hint().count() > 0 || session.version().is_pre_tls_13();
2,616✔
85
}
86

87
void TLS::Callbacks::tls_verify_cert_chain(const std::vector<X509_Certificate>& cert_chain,
369✔
88
                                           const std::vector<std::optional<OCSP::Response>>& ocsp_responses,
89
                                           const std::vector<Certificate_Store*>& trusted_roots,
90
                                           Usage_Type usage,
91
                                           std::string_view hostname,
92
                                           const TLS::Policy& policy) {
93
   if(cert_chain.empty()) {
369✔
94
      throw Invalid_Argument("Certificate chain was empty");
×
95
   }
96

97
   Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(),
369✔
98
                                             policy.minimum_signature_strength());
1,107✔
99

100
   Path_Validation_Result result = x509_path_validate(cert_chain,
369✔
101
                                                      restrictions,
102
                                                      trusted_roots,
103
                                                      hostname,
104
                                                      usage,
105
                                                      tls_current_timestamp(),
369✔
106
                                                      tls_verify_cert_chain_ocsp_timeout(),
369✔
107
                                                      ocsp_responses);
369✔
108

109
   if(!result.successful_validation()) {
369✔
110
      throw TLS_Exception(Alert::BadCertificate, "Certificate validation failure: " + result.result_string());
516✔
111
   }
112
}
627✔
113

114
void TLS::Callbacks::tls_verify_raw_public_key(const Public_Key& raw_public_key,
×
115
                                               Usage_Type usage,
116
                                               std::string_view hostname,
117
                                               const TLS::Policy& policy) {
118
   BOTAN_UNUSED(raw_public_key, usage, hostname, policy);
×
119
   // There is no good default implementation for authenticating raw public key.
120
   // Applications that wish to use them for authentication, must override this.
121
   throw TLS_Exception(Alert::CertificateUnknown, "Application did not provide a means to validate the raw public key");
×
122
}
123

124
std::optional<OCSP::Response> TLS::Callbacks::tls_parse_ocsp_response(const std::vector<uint8_t>& raw_response) {
×
125
   try {
×
126
      return OCSP::Response(raw_response);
×
127
   } catch(const Decoding_Error&) {
×
128
      // ignore parsing errors and just ignore the broken OCSP response
129
      return std::nullopt;
×
130
   }
×
131
}
132

133
std::vector<std::vector<uint8_t>> TLS::Callbacks::tls_provide_cert_chain_status(
254✔
134
   const std::vector<X509_Certificate>& chain, const Certificate_Status_Request& csr) {
135
   std::vector<std::vector<uint8_t>> result(chain.size());
254✔
136
   if(!chain.empty()) {
254✔
137
      result[0] = tls_provide_cert_status(chain, csr);
254✔
138
   }
139
   return result;
246✔
140
}
8✔
141

142
std::vector<uint8_t> TLS::Callbacks::tls_sign_message(const Private_Key& key,
987✔
143
                                                      RandomNumberGenerator& rng,
144
                                                      std::string_view padding,
145
                                                      Signature_Format format,
146
                                                      const std::vector<uint8_t>& msg) {
147
   PK_Signer signer(key, rng, padding, format);
987✔
148

149
   return signer.sign_message(msg, rng);
1,973✔
150
}
987✔
151

152
bool TLS::Callbacks::tls_verify_message(const Public_Key& key,
1,355✔
153
                                        std::string_view padding,
154
                                        Signature_Format format,
155
                                        const std::vector<uint8_t>& msg,
156
                                        const std::vector<uint8_t>& sig) {
157
   PK_Verifier verifier(key, padding, format);
1,355✔
158

159
   return verifier.verify_message(msg, sig);
2,710✔
160
}
1,355✔
161

162
namespace {
163

164
bool is_dh_group(const std::variant<TLS::Group_Params, DL_Group>& group) {
4,846✔
165
   return std::holds_alternative<DL_Group>(group) || std::get<TLS::Group_Params>(group).is_dh_named_group();
9,672✔
166
}
167

168
DL_Group get_dl_group(const std::variant<TLS::Group_Params, DL_Group>& group) {
24✔
169
   BOTAN_ASSERT_NOMSG(is_dh_group(group));
24✔
170

171
   // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of
172
   // a standardized DH group identifier. TLS 1.3 just offers pre-defined
173
   // groups.
174
   return std::visit(
24✔
175
      overloaded{[](const DL_Group& dl_group) { return dl_group; },
10✔
176
                 [&](TLS::Group_Params group_param) { return DL_Group::from_name(group_param.to_string().value()); }},
42✔
177
      group);
24✔
178
}
179

180
}  // namespace
181

182
std::unique_ptr<Public_Key> TLS::Callbacks::tls_deserialize_peer_public_key(
2,105✔
183
   const std::variant<TLS::Group_Params, DL_Group>& group, std::span<const uint8_t> key_bits) {
184
   if(is_dh_group(group)) {
2,105✔
185
      // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of
186
      // a standardized DH group identifier.
187
      const auto dl_group = get_dl_group(group);
10✔
188

189
      auto Y = BigInt::from_bytes(key_bits);
10✔
190

191
      /*
192
       * A basic check for key validity. As we do not know q here we
193
       * cannot check that Y is in the right subgroup. However since
194
       * our key is ephemeral there does not seem to be any
195
       * advantage to bogus keys anyway.
196
       */
197
      if(Y <= 1 || Y >= dl_group.get_p() - 1) {
20✔
198
         throw Decoding_Error("Server sent bad DH key for DHE exchange");
×
199
      }
200

201
      return std::make_unique<DH_PublicKey>(dl_group, Y);
20✔
202
   }
20✔
203

204
   // The special case for TLS 1.2 with an explicit DH group definition is
205
   // handled above. All other cases are based on the opaque group definition.
206
   BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group));
2,095✔
207
   const auto group_params = std::get<TLS::Group_Params>(group);
2,095✔
208

209
   if(group_params.is_ecdh_named_curve()) {
2,095✔
210
      const auto ec_group = EC_Group::from_name(group_params.to_string().value());
198✔
211
      return std::make_unique<ECDH_PublicKey>(ec_group, EC_AffinePoint(ec_group, key_bits));
162✔
212
   }
99✔
213

214
#if defined(BOTAN_HAS_X25519)
215
   if(group_params.is_x25519()) {
1,996✔
216
      return std::make_unique<X25519_PublicKey>(key_bits);
3,934✔
217
   }
218
#endif
219

220
#if defined(BOTAN_HAS_X448)
221
   if(group_params.is_x448()) {
25✔
222
      return std::make_unique<X448_PublicKey>(key_bits);
8✔
223
   }
224
#endif
225

226
#if defined(BOTAN_HAS_TLS_13_PQC)
227
   if(group_params.is_pqc_hybrid()) {
21✔
228
      return Hybrid_KEM_PublicKey::load_for_group(group_params, key_bits);
37✔
229
   }
230
#endif
231

232
#if defined(BOTAN_HAS_ML_KEM)
233
   if(group_params.is_pure_ml_kem()) {
1✔
234
      return std::make_unique<ML_KEM_PublicKey>(key_bits, ML_KEM_Mode(group_params.to_string().value()));
4✔
235
   }
236
#endif
237

238
#if defined(BOTAN_HAS_FRODOKEM)
239
   if(group_params.is_pure_frodokem()) {
×
240
      return std::make_unique<FrodoKEM_PublicKey>(key_bits, FrodoKEMMode(group_params.to_string().value()));
×
241
   }
242
#endif
243

244
   throw Decoding_Error("cannot create a key offering without a group definition");
×
245
}
246

247
std::unique_ptr<Private_Key> TLS::Callbacks::tls_kem_generate_key(TLS::Group_Params group, RandomNumberGenerator& rng) {
1,060✔
248
#if defined(BOTAN_HAS_ML_KEM)
249
   if(group.is_pure_ml_kem()) {
1,060✔
250
      return std::make_unique<ML_KEM_PrivateKey>(rng, ML_KEM_Mode(group.to_string().value()));
3✔
251
   }
252
#endif
253

254
#if defined(BOTAN_HAS_FRODOKEM)
255
   if(group.is_pure_frodokem()) {
1,059✔
256
      return std::make_unique<FrodoKEM_PrivateKey>(rng, FrodoKEMMode(group.to_string().value()));
×
257
   }
258
#endif
259

260
#if defined(BOTAN_HAS_TLS_13_PQC)
261
   if(group.is_pqc_hybrid()) {
1,059✔
262
      return Hybrid_KEM_PrivateKey::generate_from_group(group, rng);
42✔
263
   }
264
#endif
265

266
   return tls_generate_ephemeral_key(group, rng);
3,114✔
267
}
268

269
KEM_Encapsulation TLS::Callbacks::tls_kem_encapsulate(TLS::Group_Params group,
383✔
270
                                                      const std::vector<uint8_t>& encoded_public_key,
271
                                                      RandomNumberGenerator& rng,
272
                                                      const Policy& policy) {
273
   if(group.is_kem()) {
383✔
274
      auto kem_pub_key = [&] {
60✔
275
         try {
21✔
276
            return tls_deserialize_peer_public_key(group, encoded_public_key);
42✔
277
         } catch(const Decoding_Error& ex) {
3✔
278
            // This exception means that the public key was invalid. However,
279
            // TLS' DecodeError would imply that a protocol message was invalid.
280
            throw TLS_Exception(Alert::IllegalParameter, ex.what());
3✔
281
         }
3✔
282
      }();
21✔
283

284
      BOTAN_ASSERT_NONNULL(kem_pub_key);
18✔
285
      policy.check_peer_key_acceptable(*kem_pub_key);
18✔
286

287
      try {
18✔
288
         return PK_KEM_Encryptor(*kem_pub_key, "Raw").encrypt(rng);
18✔
289
      } catch(const Invalid_Argument& ex) {
1✔
290
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
1✔
291
      }
1✔
292
   } else {
18✔
293
      // TODO: We could use the KEX_to_KEM_Adapter to remove the case distinction
294
      //       of KEM and KEX. However, the workarounds in this adapter class
295
      //       should first be addressed.
296
      auto ephemeral_keypair = tls_generate_ephemeral_key(group, rng);
362✔
297
      BOTAN_ASSERT_NONNULL(ephemeral_keypair);
362✔
298
      return {ephemeral_keypair->public_value(),
724✔
299
              tls_ephemeral_key_agreement(group, *ephemeral_keypair, encoded_public_key, rng, policy)};
724✔
300
   }
350✔
301
}
302

303
secure_vector<uint8_t> TLS::Callbacks::tls_kem_decapsulate(TLS::Group_Params group,
442✔
304
                                                           const Private_Key& private_key,
305
                                                           const std::vector<uint8_t>& encapsulated_bytes,
306
                                                           RandomNumberGenerator& rng,
307
                                                           const Policy& policy) {
308
   if(group.is_kem()) {
442✔
309
      PK_KEM_Decryptor kemdec(private_key, rng, "Raw");
22✔
310
      if(encapsulated_bytes.size() != kemdec.encapsulated_key_length()) {
22✔
311
         throw TLS_Exception(Alert::IllegalParameter, "Invalid encapsulated key length");
2✔
312
      }
313
      return kemdec.decrypt(encapsulated_bytes, 0, {});
20✔
314
   }
22✔
315

316
   try {
420✔
317
      const auto& key_agreement_key = dynamic_cast<const PK_Key_Agreement_Key&>(private_key);
420✔
318
      return tls_ephemeral_key_agreement(group, key_agreement_key, encapsulated_bytes, rng, policy);
840✔
319
   } catch(const std::bad_cast&) {
12✔
320
      throw Invalid_Argument("provided ephemeral key is not a PK_Key_Agreement_Key");
×
321
   }
×
322
}
323

324
std::unique_ptr<PK_Key_Agreement_Key> TLS::Callbacks::tls_generate_ephemeral_key(
2,717✔
325
   const std::variant<TLS::Group_Params, DL_Group>& group, RandomNumberGenerator& rng) {
326
   if(is_dh_group(group)) {
2,717✔
327
      const DL_Group dl_group = get_dl_group(group);
14✔
328
      return std::make_unique<DH_PrivateKey>(rng, dl_group);
28✔
329
   }
14✔
330

331
   BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group));
2,703✔
332
   const auto group_params = std::get<TLS::Group_Params>(group);
2,703✔
333

334
   if(group_params.is_ecdh_named_curve()) {
2,703✔
335
      const auto ec_group = EC_Group::from_name(group_params.to_string().value());
296✔
336
      return std::make_unique<ECDH_PrivateKey>(rng, ec_group);
296✔
337
   }
148✔
338

339
#if defined(BOTAN_HAS_X25519)
340
   if(group_params.is_x25519()) {
2,555✔
341
      return std::make_unique<X25519_PrivateKey>(rng);
5,102✔
342
   }
343
#endif
344

345
#if defined(BOTAN_HAS_X448)
346
   if(group_params.is_x448()) {
4✔
347
      return std::make_unique<X448_PrivateKey>(rng);
8✔
348
   }
349
#endif
350

351
   if(group_params.is_kem()) {
×
352
      throw TLS_Exception(Alert::IllegalParameter, "cannot generate an ephemeral KEX key for a KEM");
×
353
   }
354

355
   throw TLS_Exception(Alert::DecodeError, "cannot create a key offering without a group definition");
×
356
}
357

358
std::unique_ptr<PK_Key_Agreement_Key> TLS::Callbacks::tls12_generate_ephemeral_ecdh_key(
50✔
359
   TLS::Group_Params group, RandomNumberGenerator& rng, EC_Point_Format tls12_ecc_pubkey_encoding_format) {
360
   // Delegating to the "universal" callback to obtain an ECDH key pair
361
   auto key = tls_generate_ephemeral_key(group, rng);
50✔
362

363
   // For ordinary ECDH key pairs (that are derived from `ECDH_PublicKey`), we
364
   // set the internal point encoding flag for the key before passing it on into
365
   // the TLS 1.2 implementation. For user-defined keypair types (e.g. to
366
   // offload to some crypto hardware) inheriting from Botan's `ECDH_PublicKey`
367
   // might not be feasible. Such users should consider overriding this
368
   // ECDH-specific callback and ensure that their custom class handles the
369
   // public point encoding as requested by `tls12_ecc_pubkey_encoding_format`.
370
   if(auto* ecc_key = dynamic_cast<ECDH_PublicKey*>(key.get())) {
50✔
371
      ecc_key->set_point_encoding(tls12_ecc_pubkey_encoding_format);
50✔
372
   }
373

374
   return key;
50✔
375
}
×
376

377
secure_vector<uint8_t> TLS::Callbacks::tls_ephemeral_key_agreement(
2,084✔
378
   const std::variant<TLS::Group_Params, DL_Group>& group,
379
   const PK_Key_Agreement_Key& private_key,
380
   const std::vector<uint8_t>& public_value,
381
   RandomNumberGenerator& rng,
382
   const Policy& policy) {
383
   const auto kex_pub_key = [&]() {
6,208✔
384
      try {
2,084✔
385
         return tls_deserialize_peer_public_key(group, public_value);
2,084✔
386
      } catch(const Decoding_Error& ex) {
44✔
387
         // This exception means that the public key was invalid. However,
388
         // TLS' DecodeError would imply that a protocol message was invalid.
389
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
44✔
390
      }
44✔
391
   }();
2,084✔
392

393
   BOTAN_ASSERT_NONNULL(kex_pub_key);
2,040✔
394
   policy.check_peer_key_acceptable(*kex_pub_key);
2,040✔
395

396
   // RFC 8422 - 5.11.
397
   //   With X25519 and X448, a receiving party MUST check whether the
398
   //   computed premaster secret is the all-zero value and abort the
399
   //   handshake if so, as described in Section 6 of [RFC7748].
400
   //
401
   // This is done within the key agreement operation and throws
402
   // an Invalid_Argument exception if the shared secret is all-zero.
403
   try {
2,040✔
404
      PK_Key_Agreement ka(private_key, rng, "Raw");
2,040✔
405
      return ka.derive_key(0, kex_pub_key->raw_public_key_bits()).bits_of();
6,116✔
406
   } catch(const Invalid_Argument& ex) {
2,044✔
407
      throw TLS_Exception(Alert::IllegalParameter, ex.what());
4✔
408
   }
4✔
409
}
2,036✔
410

411
void TLS::Callbacks::tls_session_established(const Session_Summary& session) {
72✔
412
   BOTAN_UNUSED(session);
72✔
413
}
72✔
414

415
std::vector<uint8_t> TLS::Callbacks::tls_provide_cert_status(const std::vector<X509_Certificate>& chain,
141✔
416
                                                             const Certificate_Status_Request& csr) {
417
   BOTAN_UNUSED(chain, csr);
141✔
418
   return std::vector<uint8_t>();
141✔
419
}
420

421
void TLS::Callbacks::tls_log_error(const char* err) {
×
422
   BOTAN_UNUSED(err);
×
423
}
×
424

425
void TLS::Callbacks::tls_log_debug(const char* what) {
×
426
   BOTAN_UNUSED(what);
×
427
}
×
428

429
void TLS::Callbacks::tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) {
×
430
   BOTAN_UNUSED(descr, val, val_len);
×
431
}
×
432

433
void TLS::Callbacks::tls_ssl_key_log_data(std::string_view label,
×
434
                                          std::span<const uint8_t> client_random,
435
                                          std::span<const uint8_t> secret) const {
436
   BOTAN_UNUSED(label, client_random, secret);
×
437
}
×
438

439
std::unique_ptr<KDF> TLS::Callbacks::tls12_protocol_specific_kdf(std::string_view prf_algo) const {
5,968✔
440
   if(prf_algo == "MD5" || prf_algo == "SHA-1") {
6,730✔
441
      return KDF::create_or_throw("TLS-12-PRF(SHA-256)");
762✔
442
   }
443

444
   return KDF::create_or_throw(Botan::fmt("TLS-12-PRF({})", prf_algo));
10,412✔
445
}
446

447
}  // namespace Botan
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

© 2026 Coveralls, Inc