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

randombit / botan / 25139258422

29 Apr 2026 08:02PM UTC coverage: 89.37% (-0.02%) from 89.385%
25139258422

push

github

web-flow
Merge pull request #5550 from randombit/jack/tls-misc

TLS conformance, hardening, and performance fixes

107055 of 119789 relevant lines covered (89.37%)

11415549.66 hits per line

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

80.6
/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/ec_group.h>
16
#include <botan/ecdh.h>
17
#include <botan/ocsp.h>
18
#include <botan/pk_algs.h>
19
#include <botan/tls_algos.h>
20
#include <botan/tls_exceptn.h>
21
#include <botan/tls_policy.h>
22
#include <botan/tls_session.h>
23
#include <botan/x509path.h>
24
#include <botan/internal/fmt.h>
25
#include <botan/internal/stl_util.h>
26

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

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

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

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

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

47
namespace Botan {
48

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

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

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

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

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

69
void TLS::Callbacks::tls_examine_extensions(const Extensions& /*unused*/,
6,196✔
70
                                            Connection_Side /*unused*/,
71
                                            Handshake_Type /*unused*/) {}
6,196✔
72

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

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

98
   const Path_Validation_Restrictions restrictions(policy.require_cert_revocation_info(),
1,497✔
99
                                                   policy.minimum_signature_strength());
4,491✔
100

101
   /*
102
   Hostname is always provided in order to allow host-specific logic if required,
103
   but it should not be passed to x509_path_validate unless we are verifying
104
   the server.
105
   */
106
   const std::string_view name_to_match = (usage == Usage_Type::TLS_CLIENT_AUTH) ? std::string_view{} : hostname;
1,497✔
107

108
   const Path_Validation_Result result = x509_path_validate(cert_chain,
1,497✔
109
                                                            restrictions,
110
                                                            trusted_roots,
111
                                                            name_to_match,
112
                                                            usage,
113
                                                            tls_current_timestamp(),
1,497✔
114
                                                            tls_verify_cert_chain_ocsp_timeout(),
1,497✔
115
                                                            ocsp_responses);
1,497✔
116

117
   if(!result.successful_validation()) {
1,497✔
118
      throw TLS_Exception(Alert::BadCertificate, "Certificate validation failure: " + result.result_string());
472✔
119
   }
120
}
1,733✔
121

122
void TLS::Callbacks::tls_verify_raw_public_key(const Public_Key& raw_public_key,
×
123
                                               Usage_Type usage,
124
                                               std::string_view hostname,
125
                                               const TLS::Policy& policy) {
126
   BOTAN_UNUSED(raw_public_key, usage, hostname, policy);
×
127
   // There is no good default implementation for authenticating raw public key.
128
   // Applications that wish to use them for authentication, must override this.
129
   throw TLS_Exception(Alert::CertificateUnknown, "Application did not provide a means to validate the raw public key");
×
130
}
131

132
std::optional<OCSP::Response> TLS::Callbacks::tls_parse_ocsp_response(const std::vector<uint8_t>& raw_response) {
×
133
   try {
×
134
      return OCSP::Response(raw_response);
×
135
   } catch(const Decoding_Error&) {
×
136
      // ignore parsing errors and just ignore the broken OCSP response
137
      return std::nullopt;
×
138
   }
×
139
}
140

141
std::vector<std::vector<uint8_t>> TLS::Callbacks::tls_provide_cert_chain_status(
261✔
142
   const std::vector<X509_Certificate>& chain, const Certificate_Status_Request& csr) {
143
   std::vector<std::vector<uint8_t>> result(chain.size());
261✔
144
   if(!chain.empty()) {
261✔
145
      result[0] = tls_provide_cert_status(chain, csr);
261✔
146
   }
147
   return result;
253✔
148
}
8✔
149

150
std::vector<uint8_t> TLS::Callbacks::tls_sign_message(const Private_Key& key,
1,032✔
151
                                                      RandomNumberGenerator& rng,
152
                                                      std::string_view padding,
153
                                                      Signature_Format format,
154
                                                      const std::vector<uint8_t>& msg) {
155
   PK_Signer signer(key, rng, padding, format);
1,032✔
156

157
   return signer.sign_message(msg, rng);
2,063✔
158
}
1,032✔
159

160
bool TLS::Callbacks::tls_verify_message(const Public_Key& key,
1,429✔
161
                                        std::string_view padding,
162
                                        Signature_Format format,
163
                                        const std::vector<uint8_t>& msg,
164
                                        const std::vector<uint8_t>& sig) {
165
   PK_Verifier verifier(key, padding, format);
1,429✔
166

167
   return verifier.verify_message(msg, sig);
2,858✔
168
}
1,429✔
169

170
namespace {
171

172
bool is_dh_group(const std::variant<TLS::Group_Params, DL_Group>& group) {
5,145✔
173
   return std::holds_alternative<DL_Group>(group) || std::get<TLS::Group_Params>(group).is_dh_named_group();
10,262✔
174
}
175

176
DL_Group get_dl_group(const std::variant<TLS::Group_Params, DL_Group>& group) {
32✔
177
   BOTAN_ASSERT_NOMSG(is_dh_group(group));
32✔
178

179
   // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of
180
   // a standardized DH group identifier. TLS 1.3 just offers pre-defined
181
   // groups.
182
   return std::visit(
32✔
183
      overloaded{[](const DL_Group& dl_group) { return dl_group; },
14✔
184
                 [&](TLS::Group_Params group_param) { return DL_Group::from_name(group_param.to_string().value()); }},
54✔
185
      group);
32✔
186
}
187

188
}  // namespace
189

190
std::unique_ptr<Public_Key> TLS::Callbacks::tls_deserialize_peer_public_key(
2,258✔
191
   const std::variant<TLS::Group_Params, DL_Group>& group, std::span<const uint8_t> key_bits) {
192
   if(is_dh_group(group)) {
2,258✔
193
      // TLS 1.2 allows specifying arbitrary DL_Group parameters in-lieu of
194
      // a standardized DH group identifier.
195
      const auto dl_group = get_dl_group(group);
14✔
196

197
      auto Y = BigInt::from_bytes(key_bits);
14✔
198

199
      /*
200
       * A basic check for key validity. As we do not know q here we
201
       * cannot check that Y is in the right subgroup. However since
202
       * our key is ephemeral there does not seem to be any
203
       * advantage to bogus keys anyway.
204
       */
205
      if(Y <= 1 || Y >= dl_group.get_p() - 1) {
28✔
206
         throw Decoding_Error("Server sent bad DH key for DHE exchange");
×
207
      }
208

209
      return std::make_unique<DH_PublicKey>(dl_group, Y);
28✔
210
   }
28✔
211

212
   // The special case for TLS 1.2 with an explicit DH group definition is
213
   // handled above. All other cases are based on the opaque group definition.
214
   BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group));
2,244✔
215
   const auto group_params = std::get<TLS::Group_Params>(group);
2,244✔
216

217
   if(group_params.is_ecdh_named_curve()) {
2,244✔
218
      const auto ec_group = EC_Group::from_name(group_params.to_string().value());
198✔
219
      return std::make_unique<ECDH_PublicKey>(ec_group, EC_AffinePoint(ec_group, key_bits));
162✔
220
   }
99✔
221

222
#if defined(BOTAN_HAS_X25519)
223
   if(group_params.is_x25519()) {
2,145✔
224
      return std::make_unique<X25519_PublicKey>(key_bits);
4,224✔
225
   }
226
#endif
227

228
#if defined(BOTAN_HAS_X448)
229
   if(group_params.is_x448()) {
29✔
230
      return std::make_unique<X448_PublicKey>(key_bits);
8✔
231
   }
232
#endif
233

234
#if defined(BOTAN_HAS_TLS_13_PQC)
235
   if(group_params.is_pqc_hybrid()) {
25✔
236
      return Hybrid_KEM_PublicKey::load_for_group(group_params, key_bits);
37✔
237
   }
238
#endif
239

240
#if defined(BOTAN_HAS_ML_KEM)
241
   if(group_params.is_pure_ml_kem()) {
5✔
242
      return std::make_unique<ML_KEM_PublicKey>(key_bits, ML_KEM_Mode(group_params.to_string().value()));
17✔
243
   }
244
#endif
245

246
#if defined(BOTAN_HAS_FRODOKEM)
247
   if(group_params.is_pure_frodokem()) {
×
248
      return std::make_unique<FrodoKEM_PublicKey>(key_bits, FrodoKEMMode(group_params.to_string().value()));
×
249
   }
250
#endif
251

252
   throw Decoding_Error("cannot create a key offering without a group definition");
×
253
}
254

255
std::unique_ptr<Private_Key> TLS::Callbacks::tls_kem_generate_key(TLS::Group_Params group, RandomNumberGenerator& rng) {
2,047✔
256
#if defined(BOTAN_HAS_ML_KEM)
257
   if(group.is_pure_ml_kem()) {
2,047✔
258
      return std::make_unique<ML_KEM_PrivateKey>(rng, ML_KEM_Mode(group.to_string().value()));
33✔
259
   }
260
#endif
261

262
#if defined(BOTAN_HAS_FRODOKEM)
263
   if(group.is_pure_frodokem()) {
2,036✔
264
      return std::make_unique<FrodoKEM_PrivateKey>(rng, FrodoKEMMode(group.to_string().value()));
×
265
   }
266
#endif
267

268
#if defined(BOTAN_HAS_TLS_13_PQC)
269
   if(group.is_pqc_hybrid()) {
2,036✔
270
      return Hybrid_KEM_PrivateKey::generate_from_group(group, rng);
1,914✔
271
   }
272
#endif
273

274
   return tls_generate_ephemeral_key(group, rng);
3,237✔
275
}
276

277
KEM_Encapsulation TLS::Callbacks::tls_kem_encapsulate(TLS::Group_Params group,
417✔
278
                                                      const std::vector<uint8_t>& encoded_public_key,
279
                                                      RandomNumberGenerator& rng,
280
                                                      const Policy& policy) {
281
   if(group.is_kem()) {
417✔
282
      auto kem_pub_key = [&] {
69✔
283
         try {
25✔
284
            return tls_deserialize_peer_public_key(group, encoded_public_key);
50✔
285
         } catch(const Decoding_Error& ex) {
6✔
286
            // This exception means that the public key was invalid. However,
287
            // TLS' DecodeError would imply that a protocol message was invalid.
288
            throw TLS_Exception(Alert::IllegalParameter, ex.what());
4✔
289
         } catch(const Invalid_Argument& ex) {
6✔
290
            throw TLS_Exception(Alert::IllegalParameter, ex.what());
2✔
291
         }
2✔
292
      }();
25✔
293

294
      BOTAN_ASSERT_NONNULL(kem_pub_key);
19✔
295
      policy.check_peer_key_acceptable(*kem_pub_key);
19✔
296

297
      try {
19✔
298
         return PK_KEM_Encryptor(*kem_pub_key, "Raw").encrypt(rng);
19✔
299
      } catch(const Decoding_Error& ex) {
1✔
300
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
×
301
      } catch(const Invalid_Argument& ex) {
1✔
302
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
1✔
303
      }
1✔
304
   } else {
19✔
305
      // TODO: We could use the KEX_to_KEM_Adapter to remove the case distinction
306
      //       of KEM and KEX. However, the workarounds in this adapter class
307
      //       should first be addressed.
308
      auto ephemeral_keypair = tls_generate_ephemeral_key(group, rng);
392✔
309
      BOTAN_ASSERT_NONNULL(ephemeral_keypair);
392✔
310
      return {ephemeral_keypair->public_value(),
784✔
311
              tls_ephemeral_key_agreement(group, *ephemeral_keypair, encoded_public_key, rng, policy)};
784✔
312
   }
380✔
313
}
314

315
secure_vector<uint8_t> TLS::Callbacks::tls_kem_decapsulate(TLS::Group_Params group,
502✔
316
                                                           const Private_Key& private_key,
317
                                                           const std::vector<uint8_t>& encapsulated_bytes,
318
                                                           RandomNumberGenerator& rng,
319
                                                           const Policy& policy) {
320
   if(group.is_kem()) {
502✔
321
      PK_KEM_Decryptor kemdec(private_key, rng, "Raw");
30✔
322
      if(encapsulated_bytes.size() != kemdec.encapsulated_key_length()) {
30✔
323
         throw TLS_Exception(Alert::IllegalParameter, "Invalid encapsulated key length");
4✔
324
      }
325
      try {
26✔
326
         return kemdec.decrypt(encapsulated_bytes, 0, {});
26✔
327
      } catch(const Decoding_Error& ex) {
1✔
328
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
×
329
      } catch(const Invalid_Argument& ex) {
1✔
330
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
1✔
331
      }
1✔
332
   }
30✔
333

334
   try {
472✔
335
      const auto& key_agreement_key = dynamic_cast<const PK_Key_Agreement_Key&>(private_key);
472✔
336
      return tls_ephemeral_key_agreement(group, key_agreement_key, encapsulated_bytes, rng, policy);
944✔
337
   } catch(const std::bad_cast&) {
12✔
338
      throw Invalid_Argument("provided ephemeral key is not a PK_Key_Agreement_Key");
×
339
   }
×
340
}
341

342
std::unique_ptr<PK_Key_Agreement_Key> TLS::Callbacks::tls_generate_ephemeral_key(
2,855✔
343
   const std::variant<TLS::Group_Params, DL_Group>& group, RandomNumberGenerator& rng) {
344
   if(is_dh_group(group)) {
2,855✔
345
      const DL_Group dl_group = get_dl_group(group);
18✔
346
      return std::make_unique<DH_PrivateKey>(rng, dl_group);
36✔
347
   }
18✔
348

349
   BOTAN_ASSERT_NOMSG(std::holds_alternative<TLS::Group_Params>(group));
2,837✔
350
   const auto group_params = std::get<TLS::Group_Params>(group);
2,837✔
351

352
   if(group_params.is_ecdh_named_curve()) {
2,837✔
353
      const auto ec_group = EC_Group::from_name(group_params.to_string().value());
256✔
354
      return std::make_unique<ECDH_PrivateKey>(rng, ec_group);
256✔
355
   }
128✔
356

357
#if defined(BOTAN_HAS_X25519)
358
   if(group_params.is_x25519()) {
2,709✔
359
      return std::make_unique<X25519_PrivateKey>(rng);
5,410✔
360
   }
361
#endif
362

363
#if defined(BOTAN_HAS_X448)
364
   if(group_params.is_x448()) {
4✔
365
      return std::make_unique<X448_PrivateKey>(rng);
8✔
366
   }
367
#endif
368

369
   if(group_params.is_kem()) {
×
370
      throw TLS_Exception(Alert::IllegalParameter, "cannot generate an ephemeral KEX key for a KEM");
×
371
   }
372

373
   throw TLS_Exception(Alert::DecodeError, "cannot create a key offering without a group definition");
×
374
}
375

376
std::unique_ptr<PK_Key_Agreement_Key> TLS::Callbacks::tls12_generate_ephemeral_ecdh_key(
49✔
377
   TLS::Group_Params group, RandomNumberGenerator& rng, EC_Point_Format tls12_ecc_pubkey_encoding_format) {
378
   // Delegating to the "universal" callback to obtain an ECDH key pair
379
   auto key = tls_generate_ephemeral_key(group, rng);
49✔
380

381
   // For ordinary ECDH key pairs (that are derived from `ECDH_PublicKey`), we
382
   // set the internal point encoding flag for the key before passing it on into
383
   // the TLS 1.2 implementation. For user-defined keypair types (e.g. to
384
   // offload to some crypto hardware) inheriting from Botan's `ECDH_PublicKey`
385
   // might not be feasible. Such users should consider overriding this
386
   // ECDH-specific callback and ensure that their custom class handles the
387
   // public point encoding as requested by `tls12_ecc_pubkey_encoding_format`.
388
   if(auto* ecc_key = dynamic_cast<ECDH_PublicKey*>(key.get())) {
49✔
389
      ecc_key->set_point_encoding(tls12_ecc_pubkey_encoding_format);
49✔
390
   }
391

392
   return key;
49✔
393
}
×
394

395
secure_vector<uint8_t> TLS::Callbacks::tls_ephemeral_key_agreement(
2,233✔
396
   const std::variant<TLS::Group_Params, DL_Group>& group,
397
   const PK_Key_Agreement_Key& private_key,
398
   const std::vector<uint8_t>& public_value,
399
   RandomNumberGenerator& rng,
400
   const Policy& policy) {
401
   const auto kex_pub_key = [&]() {
6,655✔
402
      try {
2,233✔
403
         return tls_deserialize_peer_public_key(group, public_value);
2,233✔
404
      } catch(const Decoding_Error& ex) {
44✔
405
         // This exception means that the public key was invalid. However,
406
         // TLS' DecodeError would imply that a protocol message was invalid.
407
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
44✔
408
      } catch(const Invalid_Argument& ex) {
44✔
409
         throw TLS_Exception(Alert::IllegalParameter, ex.what());
×
410
      }
×
411
   }();
2,233✔
412

413
   BOTAN_ASSERT_NONNULL(kex_pub_key);
2,189✔
414
   policy.check_peer_key_acceptable(*kex_pub_key);
2,189✔
415

416
   // RFC 8422 - 5.11.
417
   //   With X25519 and X448, a receiving party MUST check whether the
418
   //   computed premaster secret is the all-zero value and abort the
419
   //   handshake if so, as described in Section 6 of [RFC7748].
420
   //
421
   // This is done within the key agreement operation and throws
422
   // an Invalid_Argument exception if the shared secret is all-zero.
423
   try {
2,189✔
424
      const PK_Key_Agreement ka(private_key, rng, "Raw");
2,189✔
425
      return ka.derive_key(0, kex_pub_key->raw_public_key_bits()).bits_of();
6,563✔
426
   } catch(const Invalid_Argument& ex) {
2,193✔
427
      throw TLS_Exception(Alert::IllegalParameter, ex.what());
4✔
428
   }
4✔
429
}
2,185✔
430

431
void TLS::Callbacks::tls_session_established(const Session_Summary& session) {
72✔
432
   BOTAN_UNUSED(session);
72✔
433
}
72✔
434

435
std::vector<uint8_t> TLS::Callbacks::tls_provide_cert_status(const std::vector<X509_Certificate>& chain,
143✔
436
                                                             const Certificate_Status_Request& csr) {
437
   BOTAN_UNUSED(chain, csr);
143✔
438
   return std::vector<uint8_t>();
143✔
439
}
440

441
void TLS::Callbacks::tls_log_error(const char* err) {
×
442
   BOTAN_UNUSED(err);
×
443
}
×
444

445
void TLS::Callbacks::tls_log_debug(const char* what) {
×
446
   BOTAN_UNUSED(what);
×
447
}
×
448

449
void TLS::Callbacks::tls_log_debug_bin(const char* descr, const uint8_t val[], size_t val_len) {
×
450
   BOTAN_UNUSED(descr, val, val_len);
×
451
}
×
452

453
void TLS::Callbacks::tls_ssl_key_log_data(std::string_view label,
×
454
                                          std::span<const uint8_t> client_random,
455
                                          std::span<const uint8_t> secret) const {
456
   BOTAN_UNUSED(label, client_random, secret);
×
457
}
×
458

459
std::unique_ptr<KDF> TLS::Callbacks::tls12_protocol_specific_kdf(std::string_view prf_algo) const {
6,334✔
460
   if(prf_algo == "MD5" || prf_algo == "SHA-1") {
7,095✔
461
      return KDF::create_or_throw("TLS-12-PRF(SHA-256)");
761✔
462
   }
463

464
   return KDF::create_or_throw(Botan::fmt("TLS-12-PRF({})", prf_algo));
11,146✔
465
}
466

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