• 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

88.32
/src/tests/test_tls.cpp
1
/*
2
* (C) 2014,2015,2017,2018 Jack Lloyd
3
*
4
* Botan is released under the Simplified BSD License (see license.txt)
5
*/
6

7
#include "tests.h"
8
#include <fstream>
9
#include <memory>
10

11
#if defined(BOTAN_HAS_TLS)
12
   #include "test_rng.h"
13

14
   #include <botan/mem_ops.h>
15
   #include <botan/tls_alert.h>
16
   #include <botan/tls_external_psk.h>
17
   #include <botan/tls_policy.h>
18
   #include <botan/tls_session.h>
19
   #include <botan/tls_signature_scheme.h>
20
   #include <botan/tls_version.h>
21
   #include <botan/x509cert.h>
22
   #include <botan/internal/fmt.h>
23
   #include <set>
24

25
   #if defined(BOTAN_HAS_TLS_CBC)
26
      #include <botan/block_cipher.h>
27
      #include <botan/mac.h>
28
      #include <botan/internal/tls_cbc.h>
29
   #endif
30

31
   #if defined(BOTAN_HAS_TLS_NULL)
32
      #include <botan/internal/tls_null.h>
33
   #endif
34

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

39
   #if defined(BOTAN_HAS_TLS_12)
40
      #include <botan/credentials_manager.h>
41
      #include <botan/tls_callbacks.h>
42
      #include <botan/tls_client.h>
43
      #include <botan/tls_session_manager_memory.h>
44
   #endif
45
#endif
46

47
namespace Botan_Tests {
48

49
namespace {
50

51
#if defined(BOTAN_HAS_TLS)
52

53
class TLS_Session_Tests final : public Test {
1✔
54
   public:
55
      std::vector<Test::Result> run() override {
1✔
56
         Test::Result result("TLS::Session");
1✔
57

58
         const Botan::TLS::Session session(Botan::secure_vector<uint8_t>(48, 0xCC),
1✔
59
                                           Botan::TLS::Protocol_Version::TLS_V12,
60
                                           0xC02F,
61
                                           Botan::TLS::Connection_Side::Client,
62
                                           true,
63
                                           false,
64
                                           std::vector<Botan::X509_Certificate>(),
1✔
65
                                           Botan::TLS::Server_Information("server"),
1✔
66
                                           0x0000,
67
                                           std::chrono::system_clock::now());
2✔
68

69
         const std::string pem = session.PEM_encode();
1✔
70
         const Botan::TLS::Session session_from_pem(pem);
1✔
71
         result.test_bin_eq("Roundtrip from pem", session.DER_encode(), session_from_pem.DER_encode());
2✔
72

73
         const auto der = session.DER_encode();
1✔
74
         const Botan::TLS::Session session_from_der(der);
1✔
75
         result.test_bin_eq("Roundtrip from der", session.DER_encode(), session_from_der.DER_encode());
2✔
76

77
         const Botan::SymmetricKey key("ABCDEF");
1✔
78
         const std::vector<uint8_t> ctext1 = session.encrypt(key, this->rng());
1✔
79
         const std::vector<uint8_t> ctext2 = session.encrypt(key, this->rng());
1✔
80

81
         result.test_bin_ne("TLS session encryption is non-deterministic", ctext1, ctext2);
1✔
82

83
         result.test_bin_eq(
1✔
84
            "TLS session encryption same header", std::span{ctext1}.first(12), "068B5A9D396C0000F2322CAE");
85
         result.test_bin_eq(
1✔
86
            "TLS session encryption same header", std::span{ctext2}.first(12), "068B5A9D396C0000F2322CAE");
87

88
         const Botan::TLS::Session dsession = Botan::TLS::Session::decrypt(ctext1.data(), ctext1.size(), key);
1✔
89

90
         Fixed_Output_RNG frng1("00112233445566778899AABBCCDDEEFF802802802802802802802802");
1✔
91
         const std::vector<uint8_t> ctextf1 = session.encrypt(key, frng1);
1✔
92
         Fixed_Output_RNG frng2("00112233445566778899AABBCCDDEEFF802802802802802802802802");
1✔
93
         const std::vector<uint8_t> ctextf2 = session.encrypt(key, frng2);
1✔
94

95
         result.test_bin_eq("Only randomness comes from RNG", ctextf1, ctextf2);
1✔
96

97
         const Botan::TLS::Session session2(Botan::secure_vector<uint8_t>{0xCC, 0xEE},
1✔
98
                                            Botan::TLS::Protocol_Version::TLS_V12,
99
                                            0xBAAD,  // cipher suite does not exist
100
                                            Botan::TLS::Connection_Side::Client,
101
                                            true,
102
                                            false,
103
                                            std::vector<Botan::X509_Certificate>(),
1✔
104
                                            Botan::TLS::Server_Information("server"),
1✔
105
                                            0x0000,
106
                                            std::chrono::system_clock::now());
2✔
107
         const std::string pem_with_unknown_ciphersuite = session2.PEM_encode();
1✔
108

109
         result.test_throws("unknown ciphersuite during session parsing",
1✔
110
                            "Serialized TLS session contains unknown cipher suite (47789)",
111
                            [&] { Botan::TLS::Session{pem_with_unknown_ciphersuite}; });
2✔
112

113
         return {result};
3✔
114
      }
5✔
115
};
116

117
BOTAN_REGISTER_TEST("tls", "tls_session", TLS_Session_Tests);
118

119
   #if defined(BOTAN_HAS_TLS_CBC)
120

121
class TLS_CBC_Padding_Tests final : public Text_Based_Test {
×
122
   public:
123
      TLS_CBC_Padding_Tests() : Text_Based_Test("tls_cbc_padding.vec", "Record,Output") {}
2✔
124

125
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
22✔
126
         const std::vector<uint8_t> record = vars.get_req_bin("Record");
22✔
127
         const size_t output = vars.get_req_sz("Output");
22✔
128

129
         const uint16_t res = Botan::TLS::check_tls_cbc_padding(record.data(), record.size());
22✔
130

131
         Test::Result result("TLS CBC padding check");
22✔
132
         result.test_sz_eq("Expected", res, output);
22✔
133
         return result;
22✔
134
      }
22✔
135
};
136

137
BOTAN_REGISTER_TEST("tls", "tls_cbc_padding", TLS_CBC_Padding_Tests);
138

139
class TLS_CBC_Tests final : public Text_Based_Test {
×
140
   public:
141
      class ZeroMac : public Botan::MessageAuthenticationCode {
142
         public:
143
            explicit ZeroMac(size_t mac_len) : m_mac_len(mac_len) {}
10✔
144

145
            void clear() override {}
×
146

147
            std::string name() const override { return "ZeroMac"; }
16✔
148

149
            size_t output_length() const override { return m_mac_len; }
20✔
150

151
            void add_data(std::span<const uint8_t> /*input*/) override {}
26✔
152

153
            void final_result(std::span<uint8_t> out) override {
10✔
154
               for(size_t i = 0; i != m_mac_len; ++i) {
206✔
155
                  out[i] = 0;
196✔
156
               }
157
            }
10✔
158

159
            bool has_keying_material() const override { return true; }
×
160

161
            Botan::Key_Length_Specification key_spec() const override {
20✔
162
               return Botan::Key_Length_Specification(0, 0, 1);
20✔
163
            }
164

165
            std::unique_ptr<MessageAuthenticationCode> new_object() const override {
×
166
               return std::make_unique<ZeroMac>(m_mac_len);
×
167
            }
168

169
         private:
170
            void key_schedule(std::span<const uint8_t> /* key */) override {}
10✔
171

172
            size_t m_mac_len;
173
      };
174

175
      class Noop_Block_Cipher : public Botan::BlockCipher {
176
         public:
177
            explicit Noop_Block_Cipher(size_t bs) : m_bs(bs) {}
10✔
178

179
            void encrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override {
×
180
               Botan::copy_mem(out, in, blocks * m_bs);
×
181
            }
×
182

183
            void decrypt_n(const uint8_t in[], uint8_t out[], size_t blocks) const override {
10✔
184
               Botan::copy_mem(out, in, blocks * m_bs);
10✔
185
            }
10✔
186

187
            size_t block_size() const override { return m_bs; }
40✔
188

189
            void clear() override {}
×
190

191
            std::string name() const override { return "noop"; }
10✔
192

193
            bool has_keying_material() const override { return true; }
×
194

195
            Botan::Key_Length_Specification key_spec() const override {
30✔
196
               return Botan::Key_Length_Specification(0, 0, 1);
30✔
197
            }
198

199
            std::unique_ptr<BlockCipher> new_object() const override {
×
200
               return std::make_unique<Noop_Block_Cipher>(m_bs);
×
201
            }
202

203
         private:
204
            void key_schedule(std::span<const uint8_t> /*key*/) override {}
10✔
205

206
            size_t m_bs;
207
      };
208

209
      TLS_CBC_Tests() : Text_Based_Test("tls_cbc.vec", "Blocksize,MACsize,Record,Valid") {}
2✔
210

211
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
10✔
212
         Test::Result result("TLS CBC");
10✔
213

214
         const size_t block_size = vars.get_req_sz("Blocksize");
10✔
215
         const size_t mac_len = vars.get_req_sz("MACsize");
10✔
216
         const std::vector<uint8_t> record = vars.get_req_bin("Record");
10✔
217
         const bool is_valid = vars.get_req_sz("Valid") == 1;
10✔
218

219
         // todo test permutations
220
         const bool encrypt_then_mac = false;
10✔
221

222
         Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption tls_cbc(std::make_unique<Noop_Block_Cipher>(block_size),
30✔
223
                                                          std::make_unique<ZeroMac>(mac_len),
10✔
224
                                                          0,
225
                                                          0,
226
                                                          Botan::TLS::Protocol_Version::TLS_V12,
227
                                                          encrypt_then_mac);
20✔
228

229
         tls_cbc.set_key(std::vector<uint8_t>(0));
10✔
230
         std::vector<uint8_t> ad(13);
10✔
231
         tls_cbc.set_associated_data(ad.data(), ad.size());
10✔
232

233
         Botan::secure_vector<uint8_t> vec(record.begin(), record.end());
10✔
234

235
         try {
10✔
236
            tls_cbc.finish(vec, 0);
10✔
237
            if(is_valid) {
4✔
238
               result.test_success("Accepted valid TLS-CBC ciphertext");
4✔
239
            } else {
240
               result.test_failure("Accepted invalid TLS-CBC ciphertext");
×
241
            }
242
         } catch(std::exception&) {
6✔
243
            if(is_valid) {
6✔
244
               result.test_failure("Rejected valid TLS-CBC ciphertext");
×
245
            } else {
246
               result.test_success("Accepted invalid TLS-CBC ciphertext");
6✔
247
            }
248
         }
6✔
249

250
         return result;
20✔
251
      }
10✔
252
};
253

254
class TLS_CBC_KAT_Tests final : public Text_Based_Test {
×
255
   public:
256
      TLS_CBC_KAT_Tests() :
1✔
257
            Text_Based_Test(
258
               "tls_cbc_kat.vec",
259
               "BlockCipher,MAC,KeylenCipher,KeylenMAC,EncryptThenMAC,Protocol,Key,AssociatedData,Nonce,Plaintext,Ciphertext") {
2✔
260
      }
1✔
261

262
      Test::Result run_one_test(const std::string& /*header*/, const VarMap& vars) override {
10✔
263
         Test::Result result("TLS CBC KAT");
10✔
264

265
         run_kat<Botan::TLS::TLS_CBC_HMAC_AEAD_Encryption>(result, vars);
10✔
266
         run_kat<Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption>(result, vars);
10✔
267

268
         return result;
10✔
269
      }
×
270

271
      bool skip_this_test(const std::string& /*header*/, const VarMap& vars) override {
10✔
272
         try {
10✔
273
            std::ignore = get_cipher_and_mac(vars);
10✔
274
            return false;
10✔
275
         } catch(const Botan::Lookup_Error&) {
×
276
            return true;
×
277
         }
×
278
      }
279

280
   private:
281
      [[nodiscard]] static std::pair<std::unique_ptr<Botan::BlockCipher>,
282
                                     std::unique_ptr<Botan::MessageAuthenticationCode>>
283
      get_cipher_and_mac(const VarMap& vars) {
30✔
284
         return {
30✔
285
            Botan::BlockCipher::create_or_throw(vars.get_req_str("BlockCipher")),
60✔
286
            Botan::MessageAuthenticationCode::create_or_throw(vars.get_req_str("MAC")),
30✔
287
         };
60✔
288
      }
289

290
      template <typename T>
291
         requires(std::same_as<T, Botan::TLS::TLS_CBC_HMAC_AEAD_Encryption> ||
292
                  std::same_as<T, Botan::TLS::TLS_CBC_HMAC_AEAD_Decryption>)
293
      static void run_kat(Test::Result& result, const VarMap& vars) {
20✔
294
         constexpr bool encrypt = std::same_as<T, Botan::TLS::TLS_CBC_HMAC_AEAD_Encryption>;
20✔
295
         constexpr auto direction = [] {
20✔
296
            if constexpr(encrypt) {
297
               return "encryption";
298
            } else {
299
               return "decryption";
300
            }
301
         }();
302

303
         const auto keylen_cipher = vars.get_req_sz("KeylenCipher");
20✔
304
         const auto keylen_mac = vars.get_req_sz("KeylenMAC");
20✔
305
         const auto encrypt_then_mac = vars.get_req_bool("EncryptThenMAC");
20✔
306
         const auto protocol = [&] {
60✔
307
            const auto p = vars.get_req_str("Protocol");
20✔
308
            if(p == "TLS") {
20✔
309
               return Botan::TLS::Version_Code::TLS_V12;
310
            } else if(p == "DTLS") {
10✔
311
               return Botan::TLS::Version_Code::DTLS_V12;
312
            } else {
313
               throw Test_Error("unexpected protocol version");
×
314
            }
315
         }();
40✔
316

317
         const auto key = vars.get_req_bin("Key");
20✔
318
         const auto ad = vars.get_req_bin("AssociatedData");
20✔
319
         const auto nonce = vars.get_req_bin("Nonce");
20✔
320
         const auto pt = vars.get_req_bin("Plaintext");
20✔
321
         const auto ct = vars.get_req_bin("Ciphertext");
20✔
322

323
         auto [cipher, mac] = get_cipher_and_mac(vars);
20✔
324

325
         auto tls_cbc = T(std::move(cipher), std::move(mac), keylen_cipher, keylen_mac, protocol, encrypt_then_mac);
40✔
326

327
         tls_cbc.set_key(key);
20✔
328
         tls_cbc.set_associated_data(ad);
20✔
329

330
         std::vector<uint8_t> in(pt.begin(), pt.end());
20✔
331
         std::vector<uint8_t> out(ct.begin(), ct.end());
20✔
332

333
         if constexpr(!encrypt) {
334
            std::swap(in, out);
10✔
335
         }
336

337
         // Test 1: process the entire message at once
338
         std::vector<uint8_t> inout = in;
20✔
339
         tls_cbc.start(nonce);
20✔
340
         tls_cbc.finish(inout);  // in-place processing ('in' should now contain 'out')
20✔
341
         result.test_bin_eq(std::string("expected output of ") + direction, inout, out);
60✔
342

343
         // Test 2: process the message in chunks
344
         auto in_span = std::span{in};
20✔
345
         tls_cbc.start(nonce);
20✔
346
         constexpr size_t chunk_size = 7;
347
         while(in_span.size() >= chunk_size && in_span.size() > tls_cbc.minimum_final_size() + chunk_size) {
2,047✔
348
            tls_cbc.process(in_span.first(chunk_size));
2,027✔
349
            in_span = in_span.subspan(chunk_size);
2,027✔
350
         }
351

352
         std::vector<uint8_t> chunked_out(in_span.begin(), in_span.end());
20✔
353
         tls_cbc.finish(chunked_out);
20✔
354
         result.test_bin_eq(std::string("expected output with chunking of ") + direction, chunked_out, out);
60✔
355
      }
20✔
356
};
357

358
BOTAN_REGISTER_TEST("tls", "tls_cbc", TLS_CBC_Tests);
359
BOTAN_REGISTER_TEST("tls", "tls_cbc_kat", TLS_CBC_KAT_Tests);
360

361
   #endif
362

363
   #if defined(BOTAN_HAS_TLS_NULL)
364

365
class TLS_Null_Tests final : public Text_Based_Test {
×
366
   public:
367
      TLS_Null_Tests() : Text_Based_Test("tls_null.vec", "Hash,Key,AssociatedData,Message,Fragment") {}
2✔
368

369
      void encryption_test(Test::Result& result,
3✔
370
                           const std::string& hash,
371
                           const std::vector<uint8_t>& key,
372
                           const std::vector<uint8_t>& associated_data,
373
                           const std::vector<uint8_t>& message,
374
                           const std::vector<uint8_t>& expected_tls_fragment) {
375
         auto mac = Botan::MessageAuthenticationCode::create_or_throw(Botan::fmt("HMAC({})", hash));
3✔
376

377
         const auto mac_output_length = mac->output_length();
3✔
378
         Botan::TLS::TLS_NULL_HMAC_AEAD_Encryption tls_null_encrypt(std::move(mac), mac_output_length);
3✔
379

380
         tls_null_encrypt.set_key(key);
3✔
381
         tls_null_encrypt.set_associated_data(associated_data);
3✔
382
         tls_null_encrypt.start();
3✔
383

384
         Botan::secure_vector<uint8_t> buffer(message.begin(), message.end());
3✔
385
         tls_null_encrypt.finish(buffer);
3✔
386

387
         result.test_bin_eq("Encrypted TLS fragment matches expectation", buffer, expected_tls_fragment);
3✔
388
      }
3✔
389

390
      void decryption_test(Test::Result& result,
4✔
391
                           const std::string& hash,
392
                           const std::vector<uint8_t>& key,
393
                           const std::vector<uint8_t>& associated_data,
394
                           const std::vector<uint8_t>& expected_message,
395
                           const std::vector<uint8_t>& tls_fragment,
396
                           const std::string& header) {
397
         auto mac = Botan::MessageAuthenticationCode::create_or_throw(Botan::fmt("HMAC({})", hash));
4✔
398

399
         const auto mac_output_length = mac->output_length();
4✔
400
         Botan::TLS::TLS_NULL_HMAC_AEAD_Decryption tls_null_decrypt(std::move(mac), mac_output_length);
4✔
401

402
         tls_null_decrypt.set_key(key);
4✔
403
         tls_null_decrypt.set_associated_data(associated_data);
4✔
404
         tls_null_decrypt.start();
4✔
405

406
         Botan::secure_vector<uint8_t> buffer(tls_fragment.begin(), tls_fragment.end());
4✔
407

408
         if(header == "InvalidMAC") {
4✔
409
            result.test_throws("TLS_NULL_HMAC_AEAD_Decryption::finish()", "Message authentication failure", [&]() {
1✔
410
               tls_null_decrypt.finish(buffer, 0);
1✔
411
            });
412
         } else {
413
            tls_null_decrypt.finish(buffer, 0);
3✔
414
            result.test_bin_eq("Decrypted TLS fragment matches expectation", buffer, expected_message);
3✔
415
         }
416
      }
4✔
417

418
      void invalid_ad_length_test(Test::Result& result,
1✔
419
                                  const std::string& hash,
420
                                  const std::vector<uint8_t>& associated_data) {
421
         auto mac = Botan::MessageAuthenticationCode::create_or_throw(Botan::fmt("HMAC({})", hash));
1✔
422

423
         const auto mac_output_length = mac->output_length();
1✔
424
         Botan::TLS::TLS_NULL_HMAC_AEAD_Decryption tls_null_decrypt(std::move(mac), mac_output_length);
1✔
425

426
         result.test_throws<Botan::Invalid_Argument>("TLS_NULL_HMAC_AEAD_Decryption::set_associated_data()",
1✔
427
                                                     [&]() { tls_null_decrypt.set_associated_data(associated_data); });
2✔
428
      }
1✔
429

430
      Test::Result run_one_test(const std::string& header, const VarMap& vars) override {
5✔
431
         Test::Result result("TLS Null Cipher");
5✔
432

433
         const std::string hash = vars.get_req_str("Hash");
5✔
434
         const std::vector<uint8_t> key = vars.get_req_bin("Key");
5✔
435
         const std::vector<uint8_t> associated_data = vars.get_req_bin("AssociatedData");
5✔
436
         const std::vector<uint8_t> expected_message = vars.get_req_bin("Message");
5✔
437
         const std::vector<uint8_t> tls_fragment = vars.get_req_bin("Fragment");
5✔
438

439
         if(header.empty()) {
5✔
440
            encryption_test(result, hash, key, associated_data, expected_message, tls_fragment);
3✔
441
            decryption_test(result, hash, key, associated_data, expected_message, tls_fragment, header);
3✔
442
         }
443

444
         if(header == "InvalidMAC") {
5✔
445
            decryption_test(result, hash, key, associated_data, expected_message, tls_fragment, header);
1✔
446
         }
447

448
         if(header == "InvalidAssociatedDataLength") {
5✔
449
            invalid_ad_length_test(result, hash, associated_data);
1✔
450
         }
451

452
         return result;
10✔
453
      }
5✔
454
};
455

456
BOTAN_REGISTER_TEST("tls", "tls_null", TLS_Null_Tests);
457

458
   #endif
459

460
class Test_TLS_Alert_Strings : public Test {
1✔
461
   public:
462
      std::vector<Test::Result> run() override {
1✔
463
         Test::Result result("TLS::Alert::type_string");
1✔
464

465
         const std::vector<Botan::TLS::Alert::Type> alert_types = {
1✔
466
            Botan::TLS::Alert::CloseNotify,
467
            Botan::TLS::Alert::UnexpectedMessage,
468
            Botan::TLS::Alert::BadRecordMac,
469
            Botan::TLS::Alert::DecryptionFailed,
470
            Botan::TLS::Alert::RecordOverflow,
471
            Botan::TLS::Alert::DecompressionFailure,
472
            Botan::TLS::Alert::HandshakeFailure,
473
            Botan::TLS::Alert::NoCertificate,
474
            Botan::TLS::Alert::BadCertificate,
475
            Botan::TLS::Alert::UnsupportedCertificate,
476
            Botan::TLS::Alert::CertificateRevoked,
477
            Botan::TLS::Alert::CertificateExpired,
478
            Botan::TLS::Alert::CertificateUnknown,
479
            Botan::TLS::Alert::IllegalParameter,
480
            Botan::TLS::Alert::UnknownCA,
481
            Botan::TLS::Alert::AccessDenied,
482
            Botan::TLS::Alert::DecodeError,
483
            Botan::TLS::Alert::DecryptError,
484
            Botan::TLS::Alert::ExportRestriction,
485
            Botan::TLS::Alert::ProtocolVersion,
486
            Botan::TLS::Alert::InsufficientSecurity,
487
            Botan::TLS::Alert::InternalError,
488
            Botan::TLS::Alert::InappropriateFallback,
489
            Botan::TLS::Alert::UserCanceled,
490
            Botan::TLS::Alert::NoRenegotiation,
491
            Botan::TLS::Alert::MissingExtension,
492
            Botan::TLS::Alert::UnsupportedExtension,
493
            Botan::TLS::Alert::CertificateUnobtainable,
494
            Botan::TLS::Alert::UnrecognizedName,
495
            Botan::TLS::Alert::BadCertificateStatusResponse,
496
            Botan::TLS::Alert::BadCertificateHashValue,
497
            Botan::TLS::Alert::UnknownPSKIdentity,
498
            Botan::TLS::Alert::NoApplicationProtocol,
499
         };
1✔
500

501
         std::set<std::string> seen;
1✔
502

503
         for(auto alert : alert_types) {
34✔
504
            const std::string str = Botan::TLS::Alert(alert).type_string();
33✔
505
            result.test_sz_eq("No duplicate strings", seen.count(str), 0);
33✔
506
            seen.insert(str);
33✔
507
         }
33✔
508

509
         const Botan::TLS::Alert unknown_alert = Botan::TLS::Alert({01, 66});
1✔
510

511
         result.test_str_eq("Unknown alert str", unknown_alert.type_string(), "unrecognized_alert_66");
1✔
512

513
         return {result};
3✔
514
      }
2✔
515
};
516

517
BOTAN_REGISTER_TEST("tls", "tls_alert_strings", Test_TLS_Alert_Strings);
518

519
   #if defined(BOTAN_HAS_TLS_12)
520

521
// Test that NoRenegotiation warning only tears down a pending state if
522
// there is already an active state
523
class Test_TLS12_NoRenegotiation_During_Initial_Handshake : public Test {
1✔
524
   private:
525
      class Capture_Callbacks final : public Botan::TLS::Callbacks {
1✔
526
         public:
527
            void tls_emit_data(std::span<const uint8_t> bits) override {
1✔
528
               m_emitted.insert(m_emitted.end(), bits.begin(), bits.end());
1✔
529
            }
1✔
530

531
            void tls_record_received(uint64_t /*seq*/, std::span<const uint8_t> /*data*/) override {}
×
532

533
            void tls_alert(Botan::TLS::Alert alert) override { m_alerts.push_back(alert); }
1✔
534

535
            std::span<const Botan::TLS::Alert> alerts() const { return m_alerts; }
1✔
536

537
            std::span<const uint8_t> emitted() const { return m_emitted; }
1✔
538

539
         private:
540
            std::vector<uint8_t> m_emitted;
541
            std::vector<Botan::TLS::Alert> m_alerts;
542
      };
543

544
   public:
545
      std::vector<Test::Result> run() override {
1✔
546
         Test::Result result("TLS 1.2 NoRenegotiation alert during initial handshake");
1✔
547

548
         auto rng = Test::new_shared_rng(this->test_name());
1✔
549
         auto callbacks = std::make_shared<Capture_Callbacks>();
1✔
550
         auto sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
551
         auto creds = std::make_shared<Botan::Credentials_Manager>();
1✔
552
         auto policy = std::make_shared<Botan::TLS::Policy>();
1✔
553

554
         Botan::TLS::Client client(callbacks,
1✔
555
                                   sessions,
556
                                   creds,
557
                                   policy,
558
                                   rng,
559
                                   Botan::TLS::Server_Information("server.example.com"),
1✔
560
                                   Botan::TLS::Protocol_Version::TLS_V12);
6✔
561

562
         result.test_is_true("client emitted ClientHello", !callbacks->emitted().empty());
1✔
563
         result.test_is_true("client not active yet", !client.is_active());
1✔
564

565
         // RFC 5246: record header type=21 (alert) version=0x0303, length=2, followed by
566
         // alert level=1 (warning), description=100 (no_renegotiation)
567
         const std::vector<uint8_t> no_renegotiation_alert = {21, 0x03, 0x03, 0x00, 0x02, 1, 100};
1✔
568
         result.test_no_throw("alert record accepted", [&]() { (void)client.received_data(no_renegotiation_alert); });
2✔
569

570
         const auto alerts = callbacks->alerts();
1✔
571
         result.test_sz_eq("alert was delivered to application", alerts.size(), 1);
1✔
572
         if(!alerts.empty()) {
1✔
573
            result.test_is_true("alert was no_renegotiation", alerts[0].type() == Botan::TLS::Alert::NoRenegotiation);
1✔
574
         }
575
         result.test_is_true("client still not active", !client.is_active());
1✔
576
         result.test_is_true("client not closed", !client.is_closed());
1✔
577

578
         /*
579
         Here we use renegotiate as a probe as to the internal state.
580

581
         If there is already a pending state, renegotiate silently returns. But if
582
         the state is torn down then renegotiate will throw because there is neither
583
         an active or pending state.
584
         */
585
         result.test_no_throw("pending handshake state survived", [&]() { client.renegotiate(); });
2✔
586

587
         return {result};
3✔
588
      }
7✔
589
};
590

591
BOTAN_REGISTER_TEST("tls",
592
                    "tls12_no_renegotiation_during_initial_handshake",
593
                    Test_TLS12_NoRenegotiation_During_Initial_Handshake);
594

595
   #endif
596

597
   #if defined(BOTAN_HAS_TLS_12) && defined(BOTAN_HAS_TLS_13) && defined(BOTAN_HAS_TLS_13_PQC) && \
598
      defined(BOTAN_HAS_X25519) && defined(BOTAN_HAS_X448)
599

600
class Test_TLS_Policy_Text : public Test {
1✔
601
   public:
602
      std::vector<Test::Result> run() override {
1✔
603
         Test::Result result("TLS Policy");
1✔
604

605
         const std::vector<std::string> policies = {"default", "suiteb_128", "suiteb_192", "strict", "datagram", "bsi"};
1✔
606

607
         for(const std::string& policy : policies) {
7✔
608
            const std::string from_policy_obj = tls_policy_string(policy);
6✔
609

610
            const std::string policy_file = policy + (policy == "default" || policy == "strict" ? "_tls13" : "");
8✔
611

612
            const std::string from_file = read_tls_policy(policy_file);
6✔
613

614
            if(from_policy_obj != from_file) {
6✔
615
               const std::string d = diff(from_policy_obj, from_file);
×
616
               result.test_failure(Botan::fmt("Values for TLS policy from {} don't match (diff {})", policy_file, d));
×
617
            } else {
×
618
               result.test_success("Values from TLS policy from " + policy_file + " match");
12✔
619
            }
620
         }
6✔
621

622
         return {result};
3✔
623
      }
8✔
624

625
   private:
626
      static std::string diff(const std::string& a_str, const std::string& b_str) {
×
627
         std::istringstream a_ss(a_str);
×
628
         std::istringstream b_ss(b_str);
×
629

630
         std::ostringstream diff;
×
631

632
         for(;;) {
×
633
            if(!a_ss && !b_ss) {
×
634
               break;  // done
635
            }
636

637
            std::string a_line;
×
638
            std::getline(a_ss, a_line, '\n');
×
639

640
            std::string b_line;
×
641
            std::getline(b_ss, b_line, '\n');
×
642

643
            if(a_line != b_line) {
×
644
               diff << "- " << a_line << "\n"
×
645
                    << "+ " << b_line << "\n";
×
646
            }
647
         }
×
648

649
         return diff.str();
×
650
      }
×
651

652
      static std::string read_tls_policy(const std::string& policy_str) {
6✔
653
         const std::string fspath = Test::data_file("tls-policy/" + policy_str + ".txt");
18✔
654

655
         std::ifstream is(fspath.c_str());
6✔
656
         if(!is.good()) {
6✔
657
            throw Test_Error("Missing policy file " + fspath);
×
658
         }
659

660
         const Botan::TLS::Text_Policy policy(is);
6✔
661
         return policy.to_string();
6✔
662
      }
6✔
663

664
      static std::string tls_policy_string(const std::string& policy_str) {
6✔
665
         std::unique_ptr<Botan::TLS::Policy> policy;
6✔
666
         if(policy_str == "default") {
6✔
667
            policy = std::make_unique<Botan::TLS::Policy>();
1✔
668
         } else if(policy_str == "suiteb_128") {
5✔
669
            policy = std::make_unique<Botan::TLS::NSA_Suite_B_128>();
1✔
670
         } else if(policy_str == "suiteb_192") {
4✔
671
            policy = std::make_unique<Botan::TLS::NSA_Suite_B_192>();
1✔
672
         } else if(policy_str == "bsi") {
3✔
673
            policy = std::make_unique<Botan::TLS::BSI_TR_02102_2>();
1✔
674
         } else if(policy_str == "strict") {
2✔
675
            policy = std::make_unique<Botan::TLS::Strict_Policy>();
1✔
676
         } else if(policy_str == "datagram") {
1✔
677
            policy = std::make_unique<Botan::TLS::Datagram_Policy>();
1✔
678
         } else {
679
            throw Test_Error("Unknown TLS policy type '" + policy_str + "'");
×
680
         }
681

682
         return policy->to_string();
6✔
683
      }
6✔
684
};
685

686
BOTAN_REGISTER_TEST("tls", "tls_policy_text", Test_TLS_Policy_Text);
687
   #endif
688

689
class Test_TLS_Ciphersuites : public Test {
1✔
690
   public:
691
      std::vector<Test::Result> run() override {
1✔
692
         Test::Result result("TLS::Ciphersuite");
1✔
693

694
         for(size_t csuite_id = 0; csuite_id <= 0xFFFF; ++csuite_id) {
65,537✔
695
            const uint16_t csuite_id16 = static_cast<uint16_t>(csuite_id);
65,536✔
696
            auto ciphersuite = Botan::TLS::Ciphersuite::by_id(csuite_id16);
65,536✔
697

698
            if(ciphersuite && ciphersuite->valid()) {
65,536✔
699
               result.test_is_false("Valid Ciphersuite is not SCSV", Botan::TLS::Ciphersuite::is_scsv(csuite_id16));
102✔
700

701
               if(ciphersuite->cbc_ciphersuite() == false && ciphersuite->null_ciphersuite() == false) {
102✔
702
                  result.test_is_true("Expected AEAD ciphersuite", ciphersuite->aead_ciphersuite());
64✔
703
                  result.test_str_eq("Expected MAC name for AEAD ciphersuites", ciphersuite->mac_algo(), "AEAD");
64✔
704
               } else {
705
                  result.test_is_false("Did not expect AEAD ciphersuite", ciphersuite->aead_ciphersuite());
38✔
706
                  result.test_str_eq("MAC algo and PRF algo same for CBC and NULL suites",
38✔
707
                                     ciphersuite->prf_algo(),
76✔
708
                                     ciphersuite->mac_algo());
76✔
709
               }
710

711
               if(ciphersuite->null_ciphersuite()) {
102✔
712
                  result.test_str_eq("Expected NULL ciphersuite", ciphersuite->cipher_algo(), "NULL");
8✔
713
               };
714

715
               // TODO more tests here
716
            }
717
         }
718

719
         return {result};
3✔
720
      }
2✔
721
};
722

723
BOTAN_REGISTER_TEST("tls", "tls_ciphersuites", Test_TLS_Ciphersuites);
724

725
class Test_TLS_Algo_Strings : public Test {
1✔
726
   public:
727
      std::vector<Test::Result> run() override {
1✔
728
         std::vector<Test::Result> results;
1✔
729

730
         results.push_back(test_auth_method_strings());
2✔
731
         results.push_back(test_kex_algo_strings());
2✔
732
         results.push_back(test_tls_sig_method_strings());
2✔
733

734
         return results;
1✔
735
      }
×
736

737
   private:
738
      static Test::Result test_tls_sig_method_strings() {
1✔
739
         Test::Result result("TLS::Signature_Scheme");
1✔
740

741
         std::set<std::string> scheme_strs;
1✔
742
         for(auto scheme : Botan::TLS::Signature_Scheme::all_available_schemes()) {
10✔
743
            const std::string scheme_str = scheme.to_string();
9✔
744

745
            result.test_sz_eq("Scheme strings unique", scheme_strs.count(scheme_str), 0);
9✔
746

747
            scheme_strs.insert(scheme_str);
9✔
748
         }
9✔
749

750
         return result;
1✔
751
      }
1✔
752

753
      static Test::Result test_auth_method_strings() {
1✔
754
         Test::Result result("TLS::Auth_Method");
1✔
755

756
         const std::vector<Botan::TLS::Auth_Method> auth_methods({
1✔
757
            Botan::TLS::Auth_Method::RSA,
758
            Botan::TLS::Auth_Method::ECDSA,
759
            Botan::TLS::Auth_Method::IMPLICIT,
760
         });
1✔
761

762
         for(const Botan::TLS::Auth_Method meth : auth_methods) {
4✔
763
            const std::string meth_str = Botan::TLS::auth_method_to_string(meth);
3✔
764
            result.test_str_not_empty("Method string is not empty", meth_str);
3✔
765
            const Botan::TLS::Auth_Method meth2 = Botan::TLS::auth_method_from_string(meth_str);
3✔
766
            result.test_is_true("Decoded method matches", meth == meth2);
3✔
767
         }
3✔
768

769
         return result;
1✔
770
      }
1✔
771

772
      static Test::Result test_kex_algo_strings() {
1✔
773
         Test::Result result("TLS::Kex_Algo");
1✔
774

775
         const std::vector<Botan::TLS::Kex_Algo> kex_algos({Botan::TLS::Kex_Algo::STATIC_RSA,
1✔
776
                                                            Botan::TLS::Kex_Algo::DH,
777
                                                            Botan::TLS::Kex_Algo::ECDH,
778
                                                            Botan::TLS::Kex_Algo::PSK,
779
                                                            Botan::TLS::Kex_Algo::ECDHE_PSK});
1✔
780

781
         for(const Botan::TLS::Kex_Algo meth : kex_algos) {
6✔
782
            const std::string meth_str = Botan::TLS::kex_method_to_string(meth);
5✔
783
            result.test_str_not_empty("Method string is not empty", meth_str);
5✔
784
            const Botan::TLS::Kex_Algo meth2 = Botan::TLS::kex_method_from_string(meth_str);
5✔
785
            result.test_is_true("Decoded method matches", meth == meth2);
5✔
786
         }
5✔
787

788
         return result;
1✔
789
      }
1✔
790
};
791

792
BOTAN_REGISTER_TEST("tls", "tls_algo_strings", Test_TLS_Algo_Strings);
793

794
   #if defined(BOTAN_HAS_TLS_13)
795

796
class TLS13_PSK_Import_Tests final : public Text_Based_Test {
×
797
   public:
798
      TLS13_PSK_Import_Tests() :
1✔
799
            Text_Based_Test("tls_13_psk_import.vec", "Key,Identity,TargetHash,Output", "Context") {}
2✔
800

801
      Test::Result run_one_test(const std::string& hash_name, const VarMap& vars) override {
6✔
802
         Test::Result result("PSK Import " + hash_name);
6✔
803

804
         const auto key = vars.get_req_bin("Key");
6✔
805
         const auto identity = vars.get_req_bin("Identity");
6✔
806
         const auto context = vars.get_opt_bin("Context");
6✔
807
         const auto target_hash = vars.get_req_str("TargetHash");
6✔
808
         const auto expected = vars.get_req_bin("Output");
6✔
809

810
         const Botan::TLS::PSKImporter importer(key, identity, context, hash_name);
6✔
811
         auto psk = importer.derive_imported_psk(Botan::TLS::Protocol_Version::TLS_V13, target_hash);
6✔
812

813
         result.test_is_true("PSK is marked as imported", psk.is_imported());
6✔
814
         result.test_str_eq("PRF algo matches target hash", psk.prf_algo(), target_hash);
6✔
815
         result.test_bin_eq("Derived PSK", psk.extract_master_secret(), expected);
6✔
816

817
         return result;
6✔
818
      }
6✔
819
};
820

821
BOTAN_REGISTER_TEST("tls", "tls13_psk_import", TLS13_PSK_Import_Tests);
822

823
   #endif
824

825
#endif
826

827
}  // namespace
828

829
}  // namespace Botan_Tests
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