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

randombit / botan / 29979355985

22 Jul 2026 09:20PM UTC coverage: 89.414% (+0.002%) from 89.412%
29979355985

push

github

web-flow
Merge pull request #5751 from randombit/jack/ec-scalar-zeroize

Zeroize pcurves scalar objects on destruction

114540 of 128101 relevant lines covered (89.41%)

10711946.13 hits per line

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

99.85
/src/tests/test_tls_record_layer_13.cpp
1
/*
2
* (C) 2021 Jack Lloyd
3
* (C) 2021 Hannes Rantzsch, René Meusel - neXenio
4
* (C) 2026 Amos Treiber, René Meusel - Rohde & Schwarz Networks and Cybersecurity GmbH
5
*
6
* Botan is released under the Simplified BSD License (see license.txt)
7
*/
8

9
#include "tests.h"
10

11
#if defined(BOTAN_HAS_TLS_13)
12

13
   #include <botan/hex.h>
14
   #include <botan/tls_ciphersuite.h>
15
   #include <botan/tls_exceptn.h>
16
   #include <botan/tls_magic.h>
17
   #include <botan/tls_policy.h>
18
   #include <botan/internal/concat_util.h>
19
   #include <botan/internal/tls_channel_impl_13.h>
20
   #include <botan/internal/tls_cipher_state.h>
21
   #include <botan/internal/tls_reader.h>
22
   #include <botan/internal/tls_record_layer_13.h>
23
   #include <array>
24

25
namespace Botan_Tests {
26

27
namespace {
28

29
namespace TLS = Botan::TLS;
30

31
using Records = std::vector<TLS::Record>;
32

33
class Test_Policy : public Botan::TLS::Policy {
71✔
34
   public:
35
      explicit Test_Policy(std::optional<uint16_t> minimum_record_size = std::nullopt) :
72✔
36
            m_minimum_record_size(minimum_record_size) {}
72✔
37

38
      std::optional<uint16_t> minimum_record_size() const override { return m_minimum_record_size; }
72✔
39

40
   private:
41
      std::optional<uint16_t> m_minimum_record_size;
42
};
43

44
TLS::Record_Layer record_layer_client(const bool skip_client_hello = false,
49✔
45
                                      std::optional<uint16_t> minimum_record_size = {}) {
46
   auto policy = Test_Policy(minimum_record_size);
28✔
47
   auto rl = TLS::Record_Layer(TLS::Connection_Side::Client, policy);
49✔
48

49
   // this is relevant for tests that rely on the legacy version in the record
50
   if(skip_client_hello) {
48✔
51
      rl.disable_sending_compat_mode();
40✔
52
   }
53

54
   return rl;
48✔
55
}
48✔
56

57
TLS::Record_Layer record_layer_server(const bool skip_client_hello = false,
23✔
58
                                      std::optional<uint16_t> minimum_record_size = {}) {
59
   auto policy = Test_Policy(minimum_record_size);
8✔
60
   auto rl = TLS::Record_Layer(TLS::Connection_Side::Server, policy);
23✔
61

62
   // this is relevant for tests that rely on the legacy version in the record
63
   if(skip_client_hello) {
23✔
64
      rl.disable_receiving_compat_mode();
4✔
65
   }
66

67
   return rl;
23✔
68
}
23✔
69

70
class Mocked_Secret_Logger : public Botan::TLS::Secret_Logger {
24✔
71
   public:
72
      void maybe_log_secret(std::string_view /*label*/, std::span<const uint8_t> /*secret*/) const override {}
51✔
73
};
74

75
std::unique_ptr<TLS::Cipher_State> rfc8448_rtt1_handshake_traffic(
24✔
76
   Botan::TLS::Connection_Side side = Botan::TLS::Connection_Side::Client) {
77
   const auto transcript_hash = Botan::hex_decode(
24✔
78
      "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed"
79
      "d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8");
24✔
80
   auto shared_secret = Botan::hex_decode_locked(
24✔
81
      "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d"
82
      "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d");
24✔
83
   auto cipher = TLS::Ciphersuite::from_name("AES_128_GCM_SHA256").value();
24✔
84
   const Mocked_Secret_Logger logger;
24✔
85
   return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash, logger);
48✔
86
}
48✔
87

88
std::vector<Test::Result> read_full_records() {
1✔
89
   const auto client_hello_record = Botan::hex_decode(  // from RFC 8448
1✔
90
      "16 03 01 00 c4 01 00 00 c0 03 03 cb"
91
      "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12"
92
      "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00"
93
      "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01"
94
      "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02"
95
      "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d"
96
      "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d"
97
      "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e"
98
      "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02"
99
      "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01");
1✔
100
   const auto ccs_record = Botan::hex_decode("14 03 03 00 01 01");
1✔
101

102
   return {CHECK("change cipher spec",
1✔
103
                 [&](auto& result) {
1✔
104
                    auto rl = record_layer_server();
1✔
105

106
                    rl.copy_data(ccs_record);
1✔
107
                    auto read = rl.next_record();
1✔
108
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
109

110
                    auto record = std::get<TLS::Record>(read);
1✔
111
                    result.test_enum_eq("received CCS", record.type, TLS::Record_Type::ChangeCipherSpec);
1✔
112
                    result.test_bin_eq("CCS byte is 0x01", record.fragment, "01");
1✔
113

114
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
115
                 }),
2✔
116

117
           CHECK("two CCS messages",
118
                 [&](auto& result) {
1✔
119
                    const auto two_ccs_records = Botan::concat(ccs_record, ccs_record);
1✔
120

121
                    auto rl = record_layer_server();
1✔
122

123
                    rl.copy_data(two_ccs_records);
1✔
124

125
                    auto read = rl.next_record();
1✔
126
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
127
                    auto record = std::get<TLS::Record>(read);
1✔
128

129
                    result.test_enum_eq("received CCS 1", record.type, TLS::Record_Type::ChangeCipherSpec);
1✔
130
                    result.test_bin_eq("CCS byte is 0x01", record.fragment, "01");
1✔
131

132
                    read = rl.next_record();
3✔
133
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
134
                    record = std::get<TLS::Record>(read);
1✔
135

136
                    result.test_enum_eq("received CCS 2", record.type, TLS::Record_Type::ChangeCipherSpec);
1✔
137
                    result.test_bin_eq("CCS byte is 0x01", record.fragment, "01");
1✔
138

139
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
140
                 }),
2✔
141

142
           CHECK("read full handshake message",
143
                 [&](auto& result) {
1✔
144
                    auto rl = record_layer_server();
1✔
145
                    rl.copy_data(client_hello_record);
1✔
146

147
                    auto read = rl.next_record();
1✔
148
                    result.test_is_true("received something", std::holds_alternative<TLS::Record>(read));
1✔
149

150
                    auto rec = std::get<TLS::Record>(read);
1✔
151
                    result.test_is_true("received handshake record", rec.type == TLS::Record_Type::Handshake);
1✔
152
                    result.test_bin_eq("contains the full handshake message",
1✔
153
                                       Botan::secure_vector<uint8_t>(client_hello_record.begin() + TLS::TLS_HEADER_SIZE,
1✔
154
                                                                     client_hello_record.end()),
155
                                       rec.fragment);
156

157
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
158
                 }),
2✔
159

160
           CHECK("read full handshake message followed by CCS", [&](auto& result) {
1✔
161
              const auto payload = Botan::concat(client_hello_record, ccs_record);
1✔
162

163
              auto rl = record_layer_server();
1✔
164
              rl.copy_data(payload);
1✔
165

166
              auto read = rl.next_record();
1✔
167
              result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
168

169
              auto rec = std::get<TLS::Record>(read);
1✔
170
              result.test_is_true("received handshake record", rec.type == TLS::Record_Type::Handshake);
1✔
171
              result.test_bin_eq("contains the full handshake message",
1✔
172
                                 Botan::secure_vector<uint8_t>(client_hello_record.begin() + TLS::TLS_HEADER_SIZE,
1✔
173
                                                               client_hello_record.end()),
174
                                 rec.fragment);
175

176
              read = rl.next_record();
3✔
177
              result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
178

179
              rec = std::get<TLS::Record>(read);
1✔
180
              result.test_enum_eq("received CCS record", rec.type, TLS::Record_Type::ChangeCipherSpec);
1✔
181
              result.test_bin_eq("CCS byte is 0x01", rec.fragment, "01");
1✔
182

183
              result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
184
           })};
9✔
185
}
2✔
186

187
std::vector<Test::Result> basic_sanitization_parse_records(TLS::Connection_Side side) {
2✔
188
   auto parse_records = [side](const std::vector<uint8_t>& data, TLS::Cipher_State* cs = nullptr) {
24✔
189
      auto rl = ((side == TLS::Connection_Side::Client) ? record_layer_client(true) : record_layer_server());
22✔
190
      rl.copy_data(data);
36✔
191
      return rl.next_record(cs);
22✔
192
   };
8✔
193

194
   return {CHECK("'receive' empty data",
2✔
195
                 [&](auto& result) {
2✔
196
                    auto read = parse_records({});
2✔
197
                    result.require("needs bytes", std::holds_alternative<TLS::BytesNeeded>(read));
2✔
198
                    result.test_sz_eq("require no bytes on record boundary", std::get<TLS::BytesNeeded>(read), 0);
2✔
199
                 }),
2✔
200

201
           CHECK("incomplete header asks for more data",
202
                 [&](auto& result) {
2✔
203
                    const std::vector<uint8_t> partial_header{'\x23', '\x03', '\x03'};
2✔
204
                    auto read = parse_records(partial_header);
2✔
205
                    result.require("returned 'bytes needed'", std::holds_alternative<TLS::BytesNeeded>(read));
2✔
206

207
                    result.test_sz_eq("asks for some more bytes",
2✔
208
                                      std::get<TLS::BytesNeeded>(read),
2✔
209
                                      Botan::TLS::TLS_HEADER_SIZE - partial_header.size());
2✔
210
                 }),
2✔
211

212
           CHECK("complete header asks for enough data to finish processing the record",
213
                 [&](auto& result) {
2✔
214
                    const std::vector<uint8_t> full_header{'\x17', '\x03', '\x03', '\x00', '\x42'};
2✔
215
                    auto read = parse_records(full_header);
2✔
216
                    result.require("returned 'bytes needed'", std::holds_alternative<TLS::BytesNeeded>(read));
2✔
217

218
                    result.test_sz_eq("asks for many more bytes", std::get<TLS::BytesNeeded>(read), 0x42);
2✔
219
                 }),
2✔
220

221
           CHECK("received an empty record (that is not application data)",
222
                 [&](auto& result) {
2✔
223
                    const std::vector<uint8_t> empty_record{'\x16', '\x03', '\x03', '\x00', '\x00'};
2✔
224
                    result.test_throws("record empty", "empty record received", [&] { parse_records(empty_record); });
4✔
225
                 }),
2✔
226

227
           CHECK("received the maximum size of an unprotected record",
228
                 [&](auto& result) {
2✔
229
                    std::vector<uint8_t> full_record{'\x16', '\x03', '\x03', '\x40', '\x00'};
2✔
230
                    full_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE);
2✔
231
                    auto read = parse_records(full_record);
2✔
232
                    result.test_is_true("returned 'record'", !std::holds_alternative<TLS::BytesNeeded>(read));
2✔
233
                 }),
2✔
234

235
           CHECK("received too many bytes in one protected record",
236
                 [&](auto& result) {
2✔
237
                    std::vector<uint8_t> huge_record{'\x17', '\x03', '\x03', '\x41', '\x01'};
2✔
238
                    huge_record.resize(TLS::MAX_CIPHERTEXT_SIZE_TLS13 + TLS::TLS_HEADER_SIZE + 1);
2✔
239
                    result.test_throws("record too big", "Received an encrypted record that exceeds maximum size", [&] {
2✔
240
                       parse_records(huge_record);
2✔
241
                    });
242
                 }),
2✔
243

244
           CHECK("decryption would result in too large plaintext",
245
                 [&](auto& result) {
2✔
246
                    // In this case the ciphertext is within the allowed bounds, but the
247
                    // decrypted plaintext would be too large.
248
                    std::vector<uint8_t> huge_record{'\x17', '\x03', '\x03', '\x40', '\x12'};
2✔
249
                    huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE + 16 /* AES-GCM tag */
2✔
250
                                       + 1                                                 /* encrypted type */
251
                                       + 1 /* illegal */);
252

253
                    auto cs = rfc8448_rtt1_handshake_traffic();
2✔
254
                    result.test_throws("record too big",
2✔
255
                                       "Received an encrypted record that exceeds maximum plaintext size",
256
                                       [&] { parse_records(huge_record, cs.get()); });
4✔
257
                 }),
2✔
258

259
           CHECK("received too many bytes in one unprotected record",
260
                 [&](auto& result) {
2✔
261
                    std::vector<uint8_t> huge_record{'\x16', '\x03', '\x03', '\x40', '\x01'};
2✔
262
                    huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE + 1);
2✔
263
                    result.test_throws("record too big", "Received a record that exceeds maximum size", [&] {
2✔
264
                       parse_records(huge_record);
2✔
265
                    });
266
                 }),
2✔
267

268
           CHECK("invalid record type",
269
                 [&](auto& result) {
2✔
270
                    const std::vector<uint8_t> invalid_record_type{'\x42', '\x03', '\x03', '\x41', '\x01'};
2✔
271
                    result.test_throws("invalid record type", "TLS record type had unexpected value", [&] {
2✔
272
                       parse_records(invalid_record_type);
2✔
273
                    });
274
                 }),
2✔
275

276
           CHECK("invalid record version",
277
                 [&](auto& result) {
2✔
278
                    const std::vector<uint8_t> invalid_record_version{'\x17', '\x13', '\x37', '\x00', '\x01', '\x42'};
2✔
279
                    result.test_throws("invalid record version", "Received unexpected record version", [&] {
2✔
280
                       parse_records(invalid_record_version);
2✔
281
                    });
282
                 }),
2✔
283

284
           CHECK("initial received record versions might be 0x03XX ",
285
                 [&](auto& result) {
2✔
286
                    auto rl = record_layer_client();
2✔
287
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x00, 0x00, 0x01, 0x42});
2✔
288
                    result.test_no_throw("0x03 0x00 should be fine for first records", [&] { rl.next_record(); });
4✔
289

290
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x01, 0x00, 0x01, 0x42});
2✔
291
                    result.test_no_throw("0x03 0x01 should be fine for first records", [&] { rl.next_record(); });
4✔
292

293
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x02, 0x00, 0x01, 0x42});
2✔
294
                    result.test_no_throw("0x03 0x02 should be fine for first records", [&] { rl.next_record(); });
4✔
295

296
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x03, 0x00, 0x01, 0x42});
2✔
297
                    result.test_no_throw("0x03 0x03 should be fine for first records", [&] { rl.next_record(); });
4✔
298

299
                    rl.disable_receiving_compat_mode();
2✔
300

301
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x03, 0x00, 0x01, 0x42});
2✔
302
                    result.test_no_throw("0x03 0x03 is okay regardless", [&] { rl.next_record(); });
4✔
303

304
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x01, 0x00, 0x01, 0x42});
2✔
305
                    result.test_throws("0x03 0x01 not okay once client hello was received", [&] { rl.next_record(); });
4✔
306
                 }),
2✔
307

308
           CHECK("malformed change cipher spec",
309
                 [&](auto& result) {
2✔
310
                    const std::vector<uint8_t> invalid_ccs_record{'\x14', '\x03', '\x03', '\x00', '\x01', '\x02'};
2✔
311
                    result.test_throws("invalid CCS record", "malformed change cipher spec record received", [&] {
2✔
312
                       parse_records(invalid_ccs_record);
2✔
313
                    });
314
                 })
2✔
315

316
   };
26✔
317
}
2✔
318

319
std::vector<Test::Result> basic_sanitization_parse_records_client() {
1✔
320
   return basic_sanitization_parse_records(TLS::Connection_Side::Client);
1✔
321
}
322

323
std::vector<Test::Result> basic_sanitization_parse_records_server() {
1✔
324
   return basic_sanitization_parse_records(TLS::Connection_Side::Server);
1✔
325
}
326

327
std::vector<Test::Result> read_fragmented_records() {
1✔
328
   TLS::Record_Layer rl = record_layer_client(true);
1✔
329

330
   auto wait_for_more_bytes =
1✔
331
      [](Botan::TLS::BytesNeeded bytes_needed, auto& record_layer, std::vector<uint8_t> bytes, auto& result) {
7✔
332
         record_layer.copy_data(bytes);
7✔
333
         const auto rlr = record_layer.next_record();
7✔
334
         if(result.test_is_true("waiting for bytes", std::holds_alternative<TLS::BytesNeeded>(rlr))) {
7✔
335
            result.test_sz_eq("right amount", std::get<TLS::BytesNeeded>(rlr), bytes_needed);
7✔
336
         }
337
      };
7✔
338

339
   return {CHECK("change cipher spec in many small pieces",
1✔
340
                 [&](auto& result) {
1✔
341
                    const std::vector<uint8_t> ccs_record{'\x14', '\x03', '\x03', '\x00', '\x01', '\x01'};
1✔
342

343
                    wait_for_more_bytes(4, rl, {'\x14'}, result);
1✔
344
                    wait_for_more_bytes(3, rl, {'\x03'}, result);
1✔
345
                    wait_for_more_bytes(2, rl, {'\x03'}, result);
1✔
346
                    wait_for_more_bytes(1, rl, {'\x00'}, result);
1✔
347
                    wait_for_more_bytes(1, rl, {'\x01'}, result);
1✔
348

349
                    rl.copy_data(std::vector<uint8_t>{'\x01'});
1✔
350
                    auto res1 = rl.next_record();
1✔
351
                    result.require("received something 1", std::holds_alternative<TLS::Record>(res1));
1✔
352

353
                    auto rec1 = std::get<TLS::Record>(res1);
1✔
354
                    result.test_enum_eq("received CCS", rec1.type, TLS::Record_Type::ChangeCipherSpec);
1✔
355
                    result.test_bin_eq("CCS byte is 0x01", rec1.fragment, "01");
1✔
356

357
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
358
                 }),
2✔
359

360
           CHECK("two change cipher specs in several pieces", [&](auto& result) {
1✔
361
              wait_for_more_bytes(1, rl, {'\x14', '\x03', '\x03', '\x00'}, result);
1✔
362

363
              rl.copy_data(std::vector<uint8_t>{'\x01', '\x01', /* second CCS starts here */ '\x14', '\x03'});
1✔
364

365
              auto res2 = rl.next_record();
1✔
366
              result.require("received something 2", std::holds_alternative<TLS::Record>(res2));
1✔
367

368
              auto rec2 = std::get<TLS::Record>(res2);
1✔
369
              result.test_enum_eq("received CCS", rec2.type, TLS::Record_Type::ChangeCipherSpec);
1✔
370
              result.test_is_true("demands more bytes", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
1✔
371

372
              wait_for_more_bytes(2, rl, {'\x03'}, result);
1✔
373

374
              rl.copy_data(std::vector<uint8_t>{'\x00', '\x01', '\x01'});
1✔
375
              auto res3 = rl.next_record();
1✔
376
              result.require("received something 3", std::holds_alternative<TLS::Record>(res3));
1✔
377

378
              auto rec3 = std::get<TLS::Record>(res3);
1✔
379
              result.test_enum_eq("received CCS", rec3.type, TLS::Record_Type::ChangeCipherSpec);
1✔
380

381
              result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
382
           })};
7✔
383
}
2✔
384

385
std::vector<Test::Result> write_records() {
1✔
386
   auto cs = rfc8448_rtt1_handshake_traffic();
1✔
387
   return {
1✔
388
      CHECK("prepare an zero-length application data fragment",
389
            [&](auto& result) {
1✔
390
               auto record =
1✔
391
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::ApplicationData, {}, cs.get());
1✔
392

393
               result.require("record header was added",
2✔
394
                              record.size() > Botan::TLS::TLS_HEADER_SIZE + 1 /* encrypted content type */);
1✔
395
            }),
1✔
396
      CHECK("prepare a client hello",
397
            [&](auto& result) {
1✔
398
               const auto client_hello_msg = Botan::hex_decode(  // from RFC 8448
1✔
399
                  "01 00 00 c0 03 03 cb"
400
                  "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12"
401
                  "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00"
402
                  "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01"
403
                  "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02"
404
                  "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d"
405
                  "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d"
406
                  "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e"
407
                  "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02"
408
                  "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01");
409
               auto record =
1✔
410
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::Handshake, client_hello_msg);
1✔
411

412
               result.require("record header was added",
1✔
413
                              record.size() == client_hello_msg.size() + Botan::TLS::TLS_HEADER_SIZE);
1✔
414

415
               const auto header = std::vector<uint8_t>(record.cbegin(), record.cbegin() + Botan::TLS::TLS_HEADER_SIZE);
1✔
416
               result.test_bin_eq("record header is well-formed", header, "16030100c4");
1✔
417
            }),
1✔
418
      CHECK("prepare a dummy CCS",
419
            [&](auto& result) {
1✔
420
               std::array<uint8_t, 1> ccs_content = {0x01};
1✔
421
               auto record =
1✔
422
                  record_layer_client(true).prepare_records(Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content);
1✔
423
               result.require("record was created", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1);
1✔
424

425
               result.test_bin_eq("CCS record is well-formed", record, "140303000101");
1✔
426
            }),
1✔
427
      CHECK("cannot prepare non-dummy CCS",
428
            [&](auto& result) {
1✔
429
               result.test_throws("cannot create non-dummy CCS", "TLS 1.3 deprecated CHANGE_CIPHER_SPEC", [] {
2✔
430
                  const auto ccs_content = Botan::hex_decode("de ad be ef");
1✔
431
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content);
1✔
432
               });
1✔
433
            }),
1✔
434
      CHECK("large messages are sharded", [&](auto& result) {
1✔
435
         const std::vector<uint8_t> large_client_hello(Botan::TLS::MAX_PLAINTEXT_SIZE + 4096);
1✔
436
         auto record = record_layer_client().prepare_records(Botan::TLS::Record_Type::Handshake, large_client_hello);
1✔
437

438
         result.test_sz_gte("produces at least two record headers",
2✔
439
                            record.size(),
440
                            large_client_hello.size() + 2 * Botan::TLS::TLS_HEADER_SIZE);
1✔
441
      })};
8✔
442
}
2✔
443

444
std::vector<Test::Result> read_encrypted_records() {
1✔
445
   // this is the "complete record" server hello portion
446
   // from RFC 8448 page 7
447
   const auto server_hello = Botan::hex_decode(
1✔
448
      "16 03 03 00 5a 02 00 00 56 03 03 a6"
449
      "af 06 a4 12 18 60 dc 5e 6e 60 24 9c d3 4c 95 93 0c 8a c5 cb 14"
450
      "34 da c1 55 77 2e d3 e2 69 28 00 13 01 00 00 2e 00 33 00 24 00"
451
      "1d 00 20 c9 82 88 76 11 20 95 fe 66 76 2b db f7 c6 72 e1 56 d6"
452
      "cc 25 3b 83 3d f1 dd 69 b1 b0 4e 75 1f 0f 00 2b 00 02 03 04");
1✔
453

454
   // this is the "complete record" encrypted server hello portion
455
   // from RFC 8448 page 9
456
   const auto encrypted_record = Botan::hex_decode(
1✔
457
      "17 03 03 02 a2 d1 ff 33 4a 56 f5 bf"
458
      "f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45 e4 89 e7 f3 3a f3 5e df"
459
      "78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61 2e f9 f9 45"
460
      "cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3"
461
      "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b"
462
      "d9 ae fb 0e 57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9"
463
      "b1 18 3e f3 ab 20 e3 7d 57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf"
464
      "51 42 73 25 25 0c 7d 0e 50 92 89 44 4c 9b 3a 64 8f 1d 71 03 5d"
465
      "2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb b3 60 98 72 55"
466
      "cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a 8f"
467
      "d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6"
468
      "86 94 5b a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac"
469
      "66 27 2f d8 fb 33 0e f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea"
470
      "52 0a 56 a8 d6 50 f5 63 aa d2 74 09 96 0d ca 63 d3 e6 88 61 1e"
471
      "a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42 72 96 8a 26 4e d6"
472
      "54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a cb bb"
473
      "31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59"
474
      "62 22 45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e"
475
      "92 ea 33 0f ae ea 6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af"
476
      "36 87 90 18 e3 f2 52 10 7f 24 3d 24 3d c7 33 9d 56 84 c8 b0 37"
477
      "8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5 e8 28 0a 2b 48 05 2c"
478
      "f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6 6f 99 88"
479
      "2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80"
480
      "f8 5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69"
481
      "18 a3 96 fa 48 a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99"
482
      "2f 67 f8 af e6 7f 76 91 3f a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11"
483
      "c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b bf 10 dc 35 ae 69 f5 51"
484
      "56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30 38 eb ba 42"
485
      "f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f"
486
      "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd"
487
      "d5 02 78 40 16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af"
488
      "93 98 28 fd 4a e3 79 4e 44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da"
489
      "bf 02 53 fe 51 75 be 89 8e 75 0e dc 53 37 0d 2b");
1✔
490

491
   // the record above padded with 42 zeros
492
   const auto encrypted_record_with_padding = Botan::hex_decode(
1✔
493
      "17 03 03 02 cc d1 ff 33 4a 56 f5 bf f6 59 4a 07 cc 87 b5 80 23 3f 50 0f 45"
494
      "e4 89 e7 f3 3a f3 5e df 78 69 fc f4 0a a4 0a a2 b8 ea 73 f8 48 a7 ca 07 61"
495
      "2e f9 f9 45 cb 96 0b 40 68 90 51 23 ea 78 b1 11 b4 29 ba 91 91 cd 05 d2 a3"
496
      "89 28 0f 52 61 34 aa dc 7f c7 8c 4b 72 9d f8 28 b5 ec f7 b1 3b d9 ae fb 0e"
497
      "57 f2 71 58 5b 8e a9 bb 35 5c 7c 79 02 07 16 cf b9 b1 18 3e f3 ab 20 e3 7d"
498
      "57 a6 b9 d7 47 76 09 ae e6 e1 22 a4 cf 51 42 73 25 25 0c 7d 0e 50 92 89 44"
499
      "4c 9b 3a 64 8f 1d 71 03 5d 2e d6 5b 0e 3c dd 0c ba e8 bf 2d 0b 22 78 12 cb"
500
      "b3 60 98 72 55 cc 74 41 10 c4 53 ba a4 fc d6 10 92 8d 80 98 10 e4 b7 ed 1a"
501
      "8f d9 91 f0 6a a6 24 82 04 79 7e 36 a6 a7 3b 70 a2 55 9c 09 ea d6 86 94 5b"
502
      "a2 46 ab 66 e5 ed d8 04 4b 4c 6d e3 fc f2 a8 94 41 ac 66 27 2f d8 fb 33 0e"
503
      "f8 19 05 79 b3 68 45 96 c9 60 bd 59 6e ea 52 0a 56 a8 d6 50 f5 63 aa d2 74"
504
      "09 96 0d ca 63 d3 e6 88 61 1e a5 e2 2f 44 15 cf 95 38 d5 1a 20 0c 27 03 42"
505
      "72 96 8a 26 4e d6 54 0c 84 83 8d 89 f7 2c 24 46 1a ad 6d 26 f5 9e ca ba 9a"
506
      "cb bb 31 7b 66 d9 02 f4 f2 92 a3 6a c1 b6 39 c6 37 ce 34 31 17 b6 59 62 22"
507
      "45 31 7b 49 ee da 0c 62 58 f1 00 d7 d9 61 ff b1 38 64 7e 92 ea 33 0f ae ea"
508
      "6d fa 31 c7 a8 4d c3 bd 7e 1b 7a 6c 71 78 af 36 87 90 18 e3 f2 52 10 7f 24"
509
      "3d 24 3d c7 33 9d 56 84 c8 b0 37 8b f3 02 44 da 8c 87 c8 43 f5 e5 6e b4 c5"
510
      "e8 28 0a 2b 48 05 2c f9 3b 16 49 9a 66 db 7c ca 71 e4 59 94 26 f7 d4 61 e6"
511
      "6f 99 88 2b d8 9f c5 08 00 be cc a6 2d 6c 74 11 6d bd 29 72 fd a1 fa 80 f8"
512
      "5d f8 81 ed be 5a 37 66 89 36 b3 35 58 3b 59 91 86 dc 5c 69 18 a3 96 fa 48"
513
      "a1 81 d6 b6 fa 4f 9d 62 d5 13 af bb 99 2f 2b 99 2f 67 f8 af e6 7f 76 91 3f"
514
      "a3 88 cb 56 30 c8 ca 01 e0 c6 5d 11 c6 6a 1e 2a c4 c8 59 77 b7 c7 a6 99 9b"
515
      "bf 10 dc 35 ae 69 f5 51 56 14 63 6c 0b 9b 68 c1 9e d2 e3 1c 0b 3b 66 76 30"
516
      "38 eb ba 42 f3 b3 8e dc 03 99 f3 a9 f2 3f aa 63 97 8c 31 7f c9 fa 66 a7 3f"
517
      "60 f0 50 4d e9 3b 5b 84 5e 27 55 92 c1 23 35 ee 34 0b bc 4f dd d5 02 78 40"
518
      "16 e4 b3 be 7e f0 4d da 49 f4 b4 40 a3 0c b5 d2 af 93 98 28 fd 4a e3 79 4e"
519
      "44 f9 4d f5 a6 31 ed e4 2c 17 19 bf da 04 d8 68 77 bb e0 dc ce f9 01 ed 32"
520
      "59 50 7a 0c d0 62 3f 90 1b 5c 89 d4 b4 f2 d1 56 f6 da 4f 3e c5 fd 2d e5 e2"
521
      "fa 44 23 0a e0 c9 dd dd bb a8 be db d9 d7 f6 b8 3d 56 4c a5 47");
1✔
522

523
   auto parse_records = [](const std::vector<uint8_t>& data) {
12✔
524
      auto rl = record_layer_client(true);
11✔
525
      rl.copy_data(data);
11✔
526
      return rl;
11✔
527
   };
×
528

529
   return {
1✔
530
      CHECK("read encrypted server hello extensions",
531
            [&](Test::Result& result) {
1✔
532
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
533
               auto rl = parse_records(encrypted_record);
1✔
534

535
               auto res = rl.next_record(cs.get());
1✔
536
               result.require("some records decrypted", !std::holds_alternative<Botan::TLS::BytesNeeded>(res));
1✔
537
               auto record = std::get<TLS::Record>(res);
1✔
538

539
               result.test_enum_eq("inner type was 'HANDSHAKE'", record.type, Botan::TLS::Record_Type::Handshake);
1✔
540
               result.test_sz_eq("decrypted payload length", record.fragment.size(), 657 /* taken from RFC 8448 */);
1✔
541

542
               result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
543
            }),
2✔
544

545
      CHECK("premature application data",
546
            [&](Test::Result& result) {
1✔
547
               auto rl = record_layer_client(true);
1✔
548
               rl.copy_data(encrypted_record);
1✔
549

550
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
551
                  "cannot process encrypted data with uninitialized cipher state",
552
                  "premature Application Data received",
553
                  [&] { auto res = rl.next_record(nullptr); });
2✔
554
            }),
1✔
555

556
      CHECK("decryption fails due to bad MAC",
557
            [&](Test::Result& result) {
1✔
558
               auto tampered_encrypted_record = encrypted_record;
1✔
559
               tampered_encrypted_record.back() = '\x42';  // changing one payload byte causes the MAC check to fails
1✔
560

561
               result.test_throws<Botan::Invalid_Authentication_Tag>("broken record detected", [&] {
1✔
562
                  auto cs = rfc8448_rtt1_handshake_traffic();
1✔
563
                  auto rl = parse_records(tampered_encrypted_record);
1✔
564
                  rl.next_record(cs.get());
1✔
565
               });
1✔
566
            }),
1✔
567

568
      CHECK("decryption fails due to too short record",
569
            [&](Test::Result& result) {
1✔
570
               const auto short_record = Botan::hex_decode("17 03 03 00 08 de ad be ef ba ad f0 0d");
1✔
571

572
               result.test_throws<Botan::TLS::TLS_Exception>("too short to decrypt", [&] {
1✔
573
                  auto cs = rfc8448_rtt1_handshake_traffic();
1✔
574
                  auto rl = parse_records(short_record);
1✔
575
                  rl.next_record(cs.get());
1✔
576
               });
1✔
577
            }),
1✔
578

579
      CHECK("protected Change Cipher Spec message is illegal",
580
            [&](Test::Result& result) {
1✔
581
               // factored message, encrypted under the same key as `encrypted_record`
582
               const auto protected_ccs = Botan::hex_decode("1703030012D8EBBBE055C8167D5690EC67DEA9A525B036");
1✔
583

584
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
585
                  "illegal state causes TLS alert", "protected change cipher spec received", [&] {
1✔
586
                     auto cs = rfc8448_rtt1_handshake_traffic();
1✔
587
                     auto rl = parse_records(protected_ccs);
1✔
588
                     rl.next_record(cs.get());
1✔
589
                  });
1✔
590
            }),
1✔
591

592
      CHECK("unprotected CCS is legal when encrypted traffic is expected",
593
            [&](Test::Result& result) {
1✔
594
               const auto ccs_record = Botan::hex_decode("14 03 03 00 01 01");
1✔
595

596
               result.test_no_throw("CCS is acceptable", [&] {
1✔
597
                  auto cs = rfc8448_rtt1_handshake_traffic();  // expect encrypted traffic
1✔
598
                  auto rl = parse_records(ccs_record);
1✔
599
                  rl.next_record(cs.get());
2✔
600
               });
1✔
601
            }),
1✔
602

603
      CHECK("unprotected Alert message might be legal",
604
            [&](Test::Result& result) {
1✔
605
               const auto alert = Botan::hex_decode("15030300020232");  // decode error
1✔
606
               const auto hsmsg = Botan::hex_decode(                    // factored 'certificate_request' message
1✔
607
                  "160303002a0d000027000024000d0020001e040305030603"
608
                  "020308040805080604010501060102010402050206020202");
1✔
609

610
               result.test_no_throw("Server allows unprotected alerts after its first flight", [&] {
1✔
611
                  auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Server);
1✔
612
                  auto rl = parse_records(alert);
1✔
613
                  rl.next_record(cs.get());
2✔
614
               });
1✔
615

616
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
617
                  "Unprotected handshake messages are not allowed for servers",
618
                  "unprotected record received where protected traffic was expected",
619
                  [&] {
1✔
620
                     auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Server);
1✔
621
                     auto rl = parse_records(hsmsg);
1✔
622
                     rl.next_record(cs.get());
1✔
623
                  });
1✔
624

625
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
626
                  "Clients don't allow unprotected alerts after Server Hello",
627
                  "unprotected record received where protected traffic was expected",
628
                  [&] {
1✔
629
                     auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Client);
1✔
630
                     auto rl = parse_records(alert);
1✔
631
                     rl.next_record(cs.get());
1✔
632
                  });
1✔
633

634
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
635
                  "Unprotected handshake messages are not allowed for clients",
636
                  "unprotected record received where protected traffic was expected",
637
                  [&] {
1✔
638
                     auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Client);
1✔
639
                     auto rl = parse_records(hsmsg);
1✔
640
                     rl.next_record(cs.get());
1✔
641
                  });
1✔
642
            }),
1✔
643

644
      CHECK("unprotected traffic is illegal when encrypted traffic is expected",
645
            [&](Test::Result& result) {
1✔
646
               result.test_throws("unprotected record is unacceptable", [&] {
1✔
647
                  auto cs = rfc8448_rtt1_handshake_traffic();  // expect encrypted traffic
1✔
648
                  auto rl = parse_records(server_hello);
1✔
649
                  rl.next_record(cs.get());
1✔
650
               });
1✔
651
            }),
1✔
652

653
      CHECK("read fragmented application data",
654
            [&](Test::Result& result) {
1✔
655
               const auto encrypted = Botan::hex_decode(
1✔
656
                  "17 03 03 00 1A 90 78 6D 7E 6F A8 F7 67 1F 6D 05 F7 24 18 F5 DB 43 F7 0B 9E 48 A6 96 B6 5B EC"
657
                  "17 03 03 00 28 6C 21 B5 B8 D8 1B 85 5C 17 0E C7 9B 2C 28 85 85 51 29 2F 71 14 F3 D7 BD D5 D1"
658
                  "80 C2 E9 3D EC 84 3B 8D 41 30 D8 C8 C5 D8"
659
                  "17 03 03 00 21 29 9A B0 5A EA 3F 8A DE 05 12 E0 6B 4A 28 C3 E2 69 2F 58 82 F1 A3 45 04 EA 16"
660
                  "14 72 39 6F A1 F3 D3 ");
1✔
661
               const std::vector<std::vector<uint8_t>> plaintext_records = {
1✔
662
                  Botan::hex_decode("00 01 02 03 04 05 06 07 08"),
663
                  Botan::hex_decode("09 0a 0b 0c 0d 0e 0f 10 11 12 13 14 15 16 17 18 19 1a 1b 1c 1d 1e 1f"),
664
                  Botan::hex_decode("20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f")};
4✔
665

666
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
667
               // advance with arbitrary hashes that were used to produce the input data
668
               const Mocked_Secret_Logger logger;
1✔
669
               cs->advance_with_server_finished(
1✔
670
                  Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), logger);
2✔
671
               cs->advance_with_client_finished(
1✔
672
                  Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac"));
2✔
673

674
               auto rl = parse_records(encrypted);
1✔
675
               auto res = rl.next_record(cs.get());
1✔
676
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
677
               auto records = std::get<TLS::Record>(res);
1✔
678
               result.test_bin_eq("first record", records.fragment, plaintext_records.at(0));
1✔
679

680
               res = rl.next_record(cs.get());
2✔
681
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
682
               records = std::get<TLS::Record>(res);
1✔
683
               result.test_bin_eq("second record", records.fragment, plaintext_records.at(1));
1✔
684

685
               res = rl.next_record(cs.get());
2✔
686
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
687
               records = std::get<TLS::Record>(res);
1✔
688
               result.test_bin_eq("third record", records.fragment, plaintext_records.at(2));
1✔
689

690
               result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
691
            }),
4✔
692

693
      CHECK("read coalesced server hello and encrypted extensions",
694
            [&](Test::Result& result) {
1✔
695
               // contains the plaintext server hello and the encrypted extensions in one go
696
               auto coalesced = server_hello;
1✔
697
               coalesced.insert(coalesced.end(), encrypted_record.cbegin(), encrypted_record.cend());
1✔
698

699
               auto client = record_layer_client(true);
1✔
700
               client.copy_data(coalesced);
1✔
701

702
               const auto srv_hello = client.next_record(nullptr);
1✔
703
               result.test_is_true("read a record", std::holds_alternative<TLS::Record>(srv_hello));
1✔
704
               result.test_is_true("is handshake record",
1✔
705
                                   std::get<TLS::Record>(srv_hello).type == TLS::Record_Type::Handshake);
1✔
706

707
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
708
               const auto enc_exts = client.next_record(cs.get());
1✔
709
               result.test_is_true("read a record", std::holds_alternative<TLS::Record>(enc_exts));
1✔
710
               result.test_is_true("is handshake record",
2✔
711
                                   std::get<TLS::Record>(enc_exts).type == TLS::Record_Type::Handshake);
1✔
712
            }),
2✔
713

714
      CHECK("read a padded record",
715
            [&](Test::Result& result) {
1✔
716
               auto client = record_layer_client(true);
1✔
717
               client.copy_data(encrypted_record_with_padding);
1✔
718

719
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
720
               const auto record = client.next_record(cs.get());
1✔
721
               result.test_is_true("read a record with padding", std::holds_alternative<TLS::Record>(record));
1✔
722
            }),
1✔
723

724
      CHECK("read an empty encrypted record", [&](Test::Result& result) {
1✔
725
         auto client = record_layer_client(true);
1✔
726
         client.copy_data(Botan::hex_decode("1703030011CE43CA0D2F28336715E770071B2D5EE0FE"));
1✔
727

728
         auto cs = rfc8448_rtt1_handshake_traffic();
1✔
729
         const auto record = client.next_record(cs.get());
1✔
730
         result.test_is_true("read an empty record", std::holds_alternative<TLS::Record>(record));
1✔
731
      })};
15✔
732
}
2✔
733

734
std::vector<Test::Result> write_encrypted_records() {
1✔
735
   auto plaintext_msg = Botan::hex_decode(
1✔
736
      "14 00 00 20 a8 ec 43 6d 67 76 34 ae"
737
      "52 5a c1 fc eb e1 1a 03 9e c1 76 94 fa c6 e9 85 27 b6 42 f2 ed d5 ce 61");
1✔
738

739
   auto cs = rfc8448_rtt1_handshake_traffic();
1✔
740
   return {
1✔
741
      CHECK("write encrypted client handshake finished",
742
            [&](Test::Result& result) {
1✔
743
               auto ct =
1✔
744
                  record_layer_client(true).prepare_records(TLS::Record_Type::Handshake, plaintext_msg, cs.get());
1✔
745
               auto expected_ct = Botan::hex_decode(
1✔
746
                  "17 03 03 00 35 75 ec 4d c2 38 cc e6"
747
                  "0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44 d8 7f"
748
                  "38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7 26 c4 05 46");
1✔
749
               result.test_bin_eq("produced the expected ciphertext", ct, expected_ct);
1✔
750
            }),
1✔
751

752
      CHECK("write a dummy CCS (that must not be encrypted)",
753
            [&](auto& result) {
1✔
754
               std::array<uint8_t, 1> ccs_content = {0x01};
1✔
755
               auto record = record_layer_client(true).prepare_records(
1✔
756
                  Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content, cs.get());
757
               result.require("record was created and not encrypted", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1);
1✔
758

759
               result.test_bin_eq("CCS record is well-formed", record, "140303000101");
1✔
760
            }),
1✔
761

762
      CHECK("write a lot of data producing two protected records",
763
            [&](Test::Result& result) {
1✔
764
               std::vector<uint8_t> big_data(TLS::MAX_PLAINTEXT_SIZE + TLS::MAX_PLAINTEXT_SIZE / 2);
1✔
765
               auto ct =
1✔
766
                  record_layer_client(true).prepare_records(TLS::Record_Type::ApplicationData, big_data, cs.get());
1✔
767
               result.require("encryption added some MAC and record headers",
1✔
768
                              ct.size() > big_data.size() + Botan::TLS::TLS_HEADER_SIZE * 2);
1✔
769

770
               auto read_record_header = [&](auto& reader) {
3✔
771
                  result.test_u8_eq(
2✔
772
                     "APPLICATION_DATA", reader.get_byte(), static_cast<uint8_t>(TLS::Record_Type::ApplicationData));
2✔
773
                  result.test_u16_eq("TLS legacy version", reader.get_uint16_t(), uint16_t(0x0303));
2✔
774

775
                  const auto fragment_length = reader.get_uint16_t();
2✔
776
                  result.test_sz_lte("TLS limits", fragment_length, TLS::MAX_CIPHERTEXT_SIZE_TLS13);
2✔
777
                  result.require("enough data", fragment_length + Botan::TLS::TLS_HEADER_SIZE < ct.size());
2✔
778
                  return fragment_length;
2✔
779
               };
1✔
780

781
               TLS::TLS_Data_Reader reader("test reader", ct);
1✔
782
               const auto fragment_length1 = read_record_header(reader);
1✔
783
               reader.discard_next(fragment_length1);
1✔
784

785
               const auto fragment_length2 = read_record_header(reader);
1✔
786
               reader.discard_next(fragment_length2);
1✔
787

788
               result.test_is_true("consumed all bytes", !reader.has_remaining());
1✔
789
            }),
1✔
790

791
      CHECK("write a record with padding",
792
            [&](Test::Result& result) {
1✔
793
               std::vector<uint8_t> data(5);
1✔
794
               auto rl = record_layer_client(true, 128);
1✔
795

796
               auto ct = rl.prepare_records(TLS::Record_Type::Handshake, data, cs.get());
1✔
797

798
               // The content type byte that is appended to the plaintext does
799
               // count as ordinary plaintext, so the padding is added to six
800
               // bytes of plaintext, not five.
801
               const auto expected_length = cs->encrypt_output_length(128) + Botan::TLS::TLS_HEADER_SIZE;
1✔
802
               result.test_sz_eq("encryption added some padding", ct.size(), expected_length);
1✔
803
            }),
1✔
804
   };
6✔
805
}
2✔
806

807
std::vector<Test::Result> legacy_version_handling() {
1✔
808
   // RFC 8446 5.1:
809
   // legacy_record_version:  MUST be set to 0x0303 for all records
810
   //    generated by a TLS 1.3 implementation other than an initial
811
   //    ClientHello (i.e., one not generated after a HelloRetryRequest),
812
   //    where it MAY also be 0x0301 for compatibility purposes.
813

814
   auto has_version = [](const auto& record, const uint16_t version) -> bool {
5✔
815
      TLS::TLS_Data_Reader dr("header reader", record);
5✔
816

817
      while(dr.has_remaining()) {
18✔
818
         dr.discard_next(1);  // record type
13✔
819
         if(dr.get_uint16_t() != version) {
13✔
820
            return false;
821
         }
822
         const auto record_size = dr.get_uint16_t();
13✔
823
         dr.discard_next(record_size);
13✔
824
      }
825

826
      dr.assert_done();
827
      return true;
828
   };
829

830
   auto parse_record = [](auto& record_layer, const std::vector<uint8_t>& data) {
11✔
831
      record_layer.copy_data(data);
11✔
832
      return record_layer.next_record();
11✔
833
   };
834

835
   return {CHECK("client side starts with version 0x0301",
1✔
836
                 [&](Test::Result& result) {
1✔
837
                    auto rl = record_layer_client();
1✔
838
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
1✔
839
                    result.test_is_true("first record has version 0x0301", has_version(rec, 0x0301));
1✔
840

841
                    rl.disable_sending_compat_mode();
1✔
842

843
                    rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
1✔
844
                    result.test_is_true("next record has version 0x0303", has_version(rec, 0x0303));
1✔
845
                 }),
1✔
846

847
           CHECK("client side starts with version 0x0301 (even if multiple reconds are required)",
848
                 [&](Test::Result& result) {
1✔
849
                    auto rl = record_layer_client();
1✔
850
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake,
1✔
851
                                                  std::vector<uint8_t>(5 * Botan::TLS::MAX_PLAINTEXT_SIZE));
1✔
852
                    result.test_is_true("first record has version 0x0301", has_version(rec, 0x0301));
1✔
853

854
                    rl.disable_sending_compat_mode();
1✔
855

856
                    rec = rl.prepare_records(TLS::Record_Type::Handshake,
2✔
857
                                             std::vector<uint8_t>(5 * Botan::TLS::MAX_PLAINTEXT_SIZE));
3✔
858
                    result.test_is_true("next record has version 0x0303", has_version(rec, 0x0303));
1✔
859
                 }),
1✔
860

861
           CHECK("server side starts with version 0x0303",
862
                 [&](Test::Result& result) {
1✔
863
                    auto rl = record_layer_server(true);
1✔
864
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
1✔
865
                    result.test_is_true("first record has version 0x0303", has_version(rec, 0x0303));
1✔
866
                 }),
1✔
867

868
           CHECK("server side accepts version 0x0301 for the first record",
869
                 [&](Test::Result& result) {
1✔
870
                    const auto first_record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00");
1✔
871
                    const auto second_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00");
1✔
872
                    auto rl = record_layer_server();
1✔
873
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record); });
2✔
874
                    result.test_no_throw("parsing second record", [&] { parse_record(rl, second_record); });
2✔
875
                 }),
1✔
876

877
           CHECK("server side accepts version 0x0301 for the first record for partial records",
878
                 [&](Test::Result& result) {
1✔
879
                    const auto first_part = Botan::hex_decode("16 03 01");
1✔
880
                    const auto second_part = Botan::hex_decode("00 05 00 00 00 00 00");
1✔
881
                    auto rl = record_layer_server();
1✔
882
                    result.test_no_throw("parsing initial part", [&] { parse_record(rl, first_part); });
2✔
883
                    result.test_no_throw("parsing second part", [&] { parse_record(rl, second_part); });
2✔
884
                 }),
1✔
885

886
           CHECK("server side accepts version 0x0303 for the first record",
887
                 [&](Test::Result& result) {
1✔
888
                    const auto first_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00");
1✔
889
                    auto rl = record_layer_server();
1✔
890
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record); });
2✔
891
                 }),
1✔
892

893
           CHECK("server side does not accept version 0x0301 after receiving client hello",
894
                 [&](Test::Result& result) {
1✔
895
                    const auto record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00");
1✔
896
                    auto rl = record_layer_server();
1✔
897
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, record); });
2✔
898
                    rl.disable_receiving_compat_mode();
1✔
899
                    result.test_throws("parsing second record", [&] { parse_record(rl, record); });
2✔
900
                 }),
1✔
901

902
           CHECK("server side does not accept other versions (after receiving client hello)",
903
                 [&](Test::Result& result) {
1✔
904
                    auto rl = record_layer_server(true);
1✔
905
                    result.test_throws("does not accept 0x0300",
1✔
906
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 00 00 05 00 00 00 00 00")); });
2✔
907
                    result.test_throws("does not accept 0x0302",
1✔
908
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 02 00 05 00 00 00 00 00")); });
2✔
909
                    result.test_throws("does not accept 0x0304",
1✔
910
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 04 00 05 00 00 00 00 00")); });
2✔
911
                    result.test_throws("does not accept 0x0305",
1✔
912
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 05 00 05 00 00 00 00 00")); });
2✔
913
                 })
1✔
914

915
   };
9✔
916
}
1✔
917

918
std::vector<Test::Result> record_size_limits() {
1✔
919
   const auto count_records = [](auto& records) {
10✔
920
      Botan::TLS::TLS_Data_Reader reader("record counter", records);
10✔
921
      size_t record_count = 0;
10✔
922

923
      for(; reader.has_remaining(); ++record_count) {
25✔
924
         reader.discard_next(1);                               // record type
15✔
925
         BOTAN_ASSERT_NOMSG(reader.get_uint16_t() == 0x0303);  // record version
15✔
926
         reader.get_tls_length_value(2);                       // record length/content
15✔
927
      }
928

929
      return record_count;
10✔
930
   };
931

932
   const auto record_length = [](auto& result, auto record) {
3✔
933
      result.require("has record", std::holds_alternative<Botan::TLS::Record>(record));
3✔
934
      const auto& r = std::get<Botan::TLS::Record>(record);
3✔
935
      return r.fragment.size();
3✔
936
   };
937

938
   return {
1✔
939
      CHECK("no specified limits means protocol defaults",
940
            [&](Test::Result& result) {
1✔
941
               auto csc = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Client);
1✔
942
               auto rlc = record_layer_client(true);
1✔
943

944
               const auto rec1 = rlc.prepare_records(
1✔
945
                  TLS::Record_Type::ApplicationData, std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE), csc.get());
1✔
946
               result.test_sz_eq("one record generated", count_records(rec1), 1);
1✔
947

948
               const auto rec2 = rlc.prepare_records(TLS::Record_Type::ApplicationData,
1✔
949
                                                     std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE + 1),
2✔
950
                                                     csc.get());
1✔
951
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
952

953
               auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Server);
1✔
954
               auto rls = record_layer_server(true);
1✔
955
               rls.copy_data(rec1);
1✔
956

957
               result.test_sz_eq("correct length record",
1✔
958
                                 record_length(result, rls.next_record(css.get())),
2✔
959
                                 Botan::TLS::MAX_PLAINTEXT_SIZE);
960
            }),
1✔
961

962
      CHECK("outgoing record size limit",
963
            [&](Test::Result& result) {
1✔
964
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
965
               auto rl = record_layer_client(true);
1✔
966

967
               rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1);
1✔
968

969
               const auto rec1 =
1✔
970
                  rl.prepare_records(TLS::Record_Type::ApplicationData, std::vector<uint8_t>(127), cs.get());
1✔
971
               result.test_sz_eq("one record generated", count_records(rec1), 1);
1✔
972

973
               const auto rec2 =
1✔
974
                  rl.prepare_records(TLS::Record_Type::ApplicationData, std::vector<uint8_t>(128), cs.get());
1✔
975
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
976
            }),
1✔
977

978
      CHECK(
979
         "outgoing record size limit can be changed",
980
         [&](Test::Result& result) {
1✔
981
            auto cs = rfc8448_rtt1_handshake_traffic();
1✔
982
            auto rl = record_layer_client(true);
1✔
983

984
            const auto rec1 = rl.prepare_records(
1✔
985
               TLS::Record_Type::ApplicationData, std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE), cs.get());
1✔
986
            result.test_sz_eq("one record generated", count_records(rec1), 1);
1✔
987

988
            const auto rec2 = rl.prepare_records(
1✔
989
               TLS::Record_Type::ApplicationData, std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE + 1), cs.get());
1✔
990
            result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
991

992
            rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1);
1✔
993

994
            const auto r3 = rl.prepare_records(TLS::Record_Type::ApplicationData, std::vector<uint8_t>(127), cs.get());
1✔
995
            result.test_sz_eq("one record generated", count_records(r3), 1);
1✔
996

997
            const auto r4 = rl.prepare_records(TLS::Record_Type::ApplicationData, std::vector<uint8_t>(128), cs.get());
1✔
998
            result.test_sz_eq("two records generated", count_records(r4), 2);
1✔
999
         }),
1✔
1000

1001
      CHECK("outgoing record limit does not affect unencrypted records",
1002
            [&](Test::Result& result) {
1✔
1003
               auto rl = record_layer_client(true);
1✔
1004

1005
               rl.set_record_size_limits(127 + 1 /* content type byte */, Botan::TLS::MAX_PLAINTEXT_SIZE + 1);
1✔
1006

1007
               const auto rec1 =
1✔
1008
                  rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE));
1✔
1009
               result.test_sz_eq("one record generated", count_records(rec1), 1);
1✔
1010

1011
               const auto rec2 = rl.prepare_records(TLS::Record_Type::Handshake,
1✔
1012
                                                    std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE + 1));
1✔
1013
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
1014
            }),
1✔
1015

1016
      CHECK("incoming limit is not checked on unprotected records",
1017
            [&](Test::Result& result) {
1✔
1018
               auto rlc = record_layer_client(true);
1✔
1019

1020
               rlc.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 95 + 1);
1✔
1021

1022
               rlc.copy_data(Botan::concat(Botan::hex_decode("16 03 03 00 80"), std::vector<uint8_t>(128)));
2✔
1023
               result.test_sz_eq("correct length record", record_length(result, rlc.next_record()), 128);
2✔
1024
            }),
1✔
1025

1026
      CHECK("incoming limit is checked on protected records",
1027
            [&](Test::Result& result) {
1✔
1028
               auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Server);
1✔
1029
               auto rls = record_layer_server(true);
1✔
1030

1031
               rls.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 127 + 1);
1✔
1032
               rls.copy_data(
1✔
1033
                  Botan::hex_decode("170303009061ec4de29020a5664ef670094c7b5daa2796aa52e128cfa8808d15c1"
2✔
1034
                                    "ffc97a0aeeed62f9ea690bb753a03d000c5efac53c619face25ad234dffb63e611"
1035
                                    "4619fb045e3a3a0dde4f22e2399b4891029eccb79ea4a29c45a999e72fc74157f0"
1036
                                    "21db0afa05601af25b61df82fb728c772ad860081d96c86008c08d0c21f991cf0d"
1037
                                    "4a0eadc840d1ea8fb1f5dd852980d78fcc"));
1038

1039
               result.test_sz_eq("correct length record", record_length(result, rls.next_record(css.get())), 127);
1✔
1040

1041
               rls.copy_data(
1✔
1042
                  Botan::hex_decode("1703030091234d4a480092fa6a55f1443345ee8d2250cd9c676370be68f86234db"
2✔
1043
                                    "f5514c6dea8b3fa99c6146fefc780e36230858a53f4c0295b23a77dc5b495e0541"
1044
                                    "093aa05ee6cf6f4a4996d9ffc829b638c822e4c36e4da50f1cf2845c12e4388d58"
1045
                                    "e907e181f2dd38e61e78c13ebcbd562a23025fd327eb4db083330314e4641f3b4b"
1046
                                    "43bf11dbb09f7a82443193dc9ece34dabd15"));
1047

1048
               result.test_throws("overflow detected",
1✔
1049
                                  "Received an encrypted record that exceeds maximum plaintext size",
1050
                                  [&] { rls.next_record(css.get()); });
2✔
1051
            }),
1✔
1052

1053
      CHECK("record size limits incompatible with the padding size are rejected",
1054
            [&](Test::Result& result) {
1✔
1055
               constexpr uint16_t minimum_record_size = 1024;
1✔
1056
               auto rl = record_layer_client(true, minimum_record_size);
1✔
1057

1058
               const uint16_t valid_record_size_limit = minimum_record_size;
1✔
1059
               const uint16_t invalid_record_size_limit = minimum_record_size - 1;
1✔
1060

1061
               result.test_no_throw("padding size is compatible with record size limit", [&] {
1✔
1062
                  rl.set_record_size_limits(valid_record_size_limit, valid_record_size_limit);
1✔
1063
               });
1064

1065
               result.test_throws(
1✔
1066
                  "padding size exceeds record size limit",
1067
                  "Configured minimum record size is not compatible with the negotiated outgoing record size limit",
1068
                  [&] { rl.set_record_size_limits(invalid_record_size_limit, invalid_record_size_limit); });
2✔
1069

1070
               result.test_no_throw("incoming record size limit is not relevant", [&] {
1✔
1071
                  rl.set_record_size_limits(valid_record_size_limit, invalid_record_size_limit);
1✔
1072
               });
1073

1074
               const uint16_t content_type_byte = 1;
1✔
1075
               const uint16_t absolute_plaintext_size_limit = Botan::TLS::MAX_PLAINTEXT_SIZE + content_type_byte;
1✔
1076
               const uint16_t invalid_plaintext_size_limit = absolute_plaintext_size_limit + 1;
1✔
1077

1078
               result.test_no_throw("padding size exceeds record size limit",
1✔
1079
                                    [&] { record_layer_client(true, absolute_plaintext_size_limit); });
2✔
1080
               result.test_throws("padding size exceeds record size limit",
1✔
1081
                                  "Configured minimum record size is larger than the specified plaintext size limit",
1082
                                  [&] { record_layer_client(true, invalid_plaintext_size_limit); });
2✔
1083
            }),
1✔
1084

1085
      CHECK("preparing a record where minimum_record_size == maximum_size_limit",
1086
            [&](Test::Result& result) {
1✔
1087
               constexpr uint16_t limit = 1024;
1✔
1088
               auto rl = record_layer_client(true, /* minimum_record_size = */ limit);
1✔
1089
               rl.set_record_size_limits(/* outgoing_limit = */ limit,
1✔
1090
                                         /* incoming_limit = */ limit);
1091

1092
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
1093
               const std::array<uint8_t, 5> data = {0x01, 0x02, 0x03, 0x04, 0x05};
1✔
1094
               const auto ct = rl.prepare_records(TLS::Record_Type::ApplicationData, data, cs.get());
1✔
1095

1096
               const auto expected_length = cs->encrypt_output_length(limit) + Botan::TLS::TLS_HEADER_SIZE;
1✔
1097
               result.test_sz_eq("encryption result has the correct length", ct.size(), expected_length);
1✔
1098
            }),
1✔
1099
   };
9✔
1100
}
1✔
1101

1102
}  // namespace
1103

1104
BOTAN_REGISTER_TEST_FN("tls",
1105
                       "tls_record_layer_13",
1106
                       basic_sanitization_parse_records_client,
1107
                       basic_sanitization_parse_records_server,
1108
                       read_full_records,
1109
                       read_fragmented_records,
1110
                       write_records,
1111
                       read_encrypted_records,
1112
                       write_encrypted_records,
1113
                       legacy_version_handling,
1114
                       record_size_limits);
1115

1116
}  // namespace Botan_Tests
1117

1118
#endif
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc