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

randombit / botan / 29388558087

14 Jul 2026 06:10PM UTC coverage: 91.656% (+2.3%) from 89.387%
29388558087

push

github

web-flow
Merge pull request #5722 from randombit/jack/python-fixes

Fix some errors in the Python binding

116292 of 126879 relevant lines covered (91.66%)

10459987.7 hits per line

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

99.83
/src/tests/test_tls_record_layer_13.cpp
1
/*
2
* (C) 2021 Jack Lloyd
3
* (C) 2021 Hannes Rantzsch, René Meusel - neXenio
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#if defined(BOTAN_HAS_TLS_13)
11

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

23
namespace Botan_Tests {
24

25
namespace {
26

27
namespace TLS = Botan::TLS;
28

29
using Records = std::vector<TLS::Record>;
30

31
TLS::Record_Layer record_layer_client(const bool skip_client_hello = false) {
44✔
32
   auto rl = TLS::Record_Layer(TLS::Connection_Side::Client);
11✔
33

34
   // this is relevant for tests that rely on the legacy version in the record
35
   if(skip_client_hello) {
44✔
36
      rl.disable_sending_compat_mode();
36✔
37
   }
38

39
   return rl;
44✔
40
}
41

42
TLS::Record_Layer record_layer_server(const bool skip_client_hello = false) {
23✔
43
   auto rl = TLS::Record_Layer(TLS::Connection_Side::Server);
11✔
44

45
   // this is relevant for tests that rely on the legacy version in the record
46
   if(skip_client_hello) {
12✔
47
      rl.disable_receiving_compat_mode();
4✔
48
   }
49

50
   return rl;
12✔
51
}
52

53
class Mocked_Secret_Logger : public Botan::TLS::Secret_Logger {
23✔
54
   public:
55
      void maybe_log_secret(std::string_view /*label*/, std::span<const uint8_t> /*secret*/) const override {}
49✔
56
};
57

58
std::unique_ptr<TLS::Cipher_State> rfc8448_rtt1_handshake_traffic(
23✔
59
   Botan::TLS::Connection_Side side = Botan::TLS::Connection_Side::Client) {
60
   const auto transcript_hash = Botan::hex_decode(
23✔
61
      "86 0c 06 ed c0 78 58 ee 8e 78 f0 e7 42 8c 58 ed"
62
      "d6 b4 3f 2c a3 e6 e9 5f 02 ed 06 3c f0 e1 ca d8");
23✔
63
   auto shared_secret = Botan::hex_decode_locked(
23✔
64
      "8b d4 05 4f b5 5b 9d 63 fd fb ac f9 f0 4b 9f 0d"
65
      "35 e6 d6 3f 53 75 63 ef d4 62 72 90 0f 89 49 2d");
23✔
66
   auto cipher = TLS::Ciphersuite::from_name("AES_128_GCM_SHA256").value();
23✔
67
   const Mocked_Secret_Logger logger;
23✔
68
   return TLS::Cipher_State::init_with_server_hello(side, std::move(shared_secret), cipher, transcript_hash, logger);
46✔
69
}
46✔
70

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

85
   return {CHECK("change cipher spec",
1✔
86
                 [&](auto& result) {
1✔
87
                    auto rl = record_layer_server();
1✔
88

89
                    rl.copy_data(ccs_record);
1✔
90
                    auto read = rl.next_record();
1✔
91
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
92

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

97
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
98
                 }),
2✔
99

100
           CHECK("two CCS messages",
101
                 [&](auto& result) {
1✔
102
                    const auto two_ccs_records = Botan::concat(ccs_record, ccs_record);
1✔
103

104
                    auto rl = record_layer_server();
1✔
105

106
                    rl.copy_data(two_ccs_records);
1✔
107

108
                    auto read = rl.next_record();
1✔
109
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
110
                    auto record = std::get<TLS::Record>(read);
1✔
111

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

115
                    read = rl.next_record();
3✔
116
                    result.require("received something", std::holds_alternative<TLS::Record>(read));
1✔
117
                    record = std::get<TLS::Record>(read);
1✔
118

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

122
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
123
                 }),
2✔
124

125
           CHECK("read full handshake message",
126
                 [&](auto& result) {
1✔
127
                    auto rl = record_layer_server();
1✔
128
                    rl.copy_data(client_hello_record);
1✔
129

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

133
                    auto rec = std::get<TLS::Record>(read);
1✔
134
                    result.test_is_true("received handshake record", rec.type == TLS::Record_Type::Handshake);
1✔
135
                    result.test_bin_eq("contains the full handshake message",
1✔
136
                                       Botan::secure_vector<uint8_t>(client_hello_record.begin() + TLS::TLS_HEADER_SIZE,
1✔
137
                                                                     client_hello_record.end()),
138
                                       rec.fragment);
139

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

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

146
              auto rl = record_layer_server();
1✔
147
              rl.copy_data(payload);
1✔
148

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

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

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

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

166
              result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
167
           })};
8✔
168
}
2✔
169

170
std::vector<Test::Result> basic_sanitization_parse_records(TLS::Connection_Side side) {
2✔
171
   auto parse_records = [side](const std::vector<uint8_t>& data, TLS::Cipher_State* cs = nullptr) {
24✔
172
      auto rl = ((side == TLS::Connection_Side::Client) ? record_layer_client(true) : record_layer_server());
33✔
173
      rl.copy_data(data);
36✔
174
      return rl.next_record(cs);
22✔
175
   };
8✔
176

177
   return {CHECK("'receive' empty data",
2✔
178
                 [&](auto& result) {
2✔
179
                    auto read = parse_records({});
2✔
180
                    result.require("needs bytes", std::holds_alternative<TLS::BytesNeeded>(read));
2✔
181
                    result.test_sz_eq("require no bytes on record boundary", std::get<TLS::BytesNeeded>(read), 0);
2✔
182
                 }),
2✔
183

184
           CHECK("incomplete header asks for more data",
185
                 [&](auto& result) {
2✔
186
                    const std::vector<uint8_t> partial_header{'\x23', '\x03', '\x03'};
2✔
187
                    auto read = parse_records(partial_header);
2✔
188
                    result.require("returned 'bytes needed'", std::holds_alternative<TLS::BytesNeeded>(read));
2✔
189

190
                    result.test_sz_eq("asks for some more bytes",
2✔
191
                                      std::get<TLS::BytesNeeded>(read),
2✔
192
                                      Botan::TLS::TLS_HEADER_SIZE - partial_header.size());
2✔
193
                 }),
2✔
194

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

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

204
           CHECK("received an empty record (that is not application data)",
205
                 [&](auto& result) {
2✔
206
                    const std::vector<uint8_t> empty_record{'\x16', '\x03', '\x03', '\x00', '\x00'};
2✔
207
                    result.test_throws("record empty", "empty record received", [&] { parse_records(empty_record); });
4✔
208
                 }),
2✔
209

210
           CHECK("received the maximum size of an unprotected record",
211
                 [&](auto& result) {
2✔
212
                    std::vector<uint8_t> full_record{'\x16', '\x03', '\x03', '\x40', '\x00'};
2✔
213
                    full_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE);
2✔
214
                    auto read = parse_records(full_record);
2✔
215
                    result.test_is_true("returned 'record'", !std::holds_alternative<TLS::BytesNeeded>(read));
2✔
216
                 }),
2✔
217

218
           CHECK("received too many bytes in one protected record",
219
                 [&](auto& result) {
2✔
220
                    std::vector<uint8_t> huge_record{'\x17', '\x03', '\x03', '\x41', '\x01'};
2✔
221
                    huge_record.resize(TLS::MAX_CIPHERTEXT_SIZE_TLS13 + TLS::TLS_HEADER_SIZE + 1);
2✔
222
                    result.test_throws("record too big", "Received an encrypted record that exceeds maximum size", [&] {
2✔
223
                       parse_records(huge_record);
2✔
224
                    });
225
                 }),
2✔
226

227
           CHECK("decryption would result in too large plaintext",
228
                 [&](auto& result) {
2✔
229
                    // In this case the ciphertext is within the allowed bounds, but the
230
                    // decrypted plaintext would be too large.
231
                    std::vector<uint8_t> huge_record{'\x17', '\x03', '\x03', '\x40', '\x12'};
2✔
232
                    huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE + 16 /* AES-GCM tag */
2✔
233
                                       + 1                                                 /* encrypted type */
234
                                       + 1 /* illegal */);
235

236
                    auto cs = rfc8448_rtt1_handshake_traffic();
2✔
237
                    result.test_throws("record too big",
2✔
238
                                       "Received an encrypted record that exceeds maximum plaintext size",
239
                                       [&] { parse_records(huge_record, cs.get()); });
4✔
240
                 }),
2✔
241

242
           CHECK("received too many bytes in one unprotected record",
243
                 [&](auto& result) {
2✔
244
                    std::vector<uint8_t> huge_record{'\x16', '\x03', '\x03', '\x40', '\x01'};
2✔
245
                    huge_record.resize(TLS::MAX_PLAINTEXT_SIZE + TLS::TLS_HEADER_SIZE + 1);
2✔
246
                    result.test_throws("record too big", "Received a record that exceeds maximum size", [&] {
2✔
247
                       parse_records(huge_record);
2✔
248
                    });
249
                 }),
2✔
250

251
           CHECK("invalid record type",
252
                 [&](auto& result) {
2✔
253
                    const std::vector<uint8_t> invalid_record_type{'\x42', '\x03', '\x03', '\x41', '\x01'};
2✔
254
                    result.test_throws("invalid record type", "TLS record type had unexpected value", [&] {
2✔
255
                       parse_records(invalid_record_type);
2✔
256
                    });
257
                 }),
2✔
258

259
           CHECK("invalid record version",
260
                 [&](auto& result) {
2✔
261
                    const std::vector<uint8_t> invalid_record_version{'\x17', '\x13', '\x37', '\x00', '\x01', '\x42'};
2✔
262
                    result.test_throws("invalid record version", "Received unexpected record version", [&] {
2✔
263
                       parse_records(invalid_record_version);
2✔
264
                    });
265
                 }),
2✔
266

267
           CHECK("initial received record versions might be 0x03XX ",
268
                 [&](auto& result) {
2✔
269
                    auto rl = record_layer_client();
2✔
270
                    rl.copy_data(std::vector<uint8_t>{0x16, 0x03, 0x00, 0x00, 0x01, 0x42});
2✔
271
                    result.test_no_throw("0x03 0x00 should be fine for first records", [&] { rl.next_record(); });
4✔
272

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

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

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

282
                    rl.disable_receiving_compat_mode();
2✔
283

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

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

291
           CHECK("malformed change cipher spec",
292
                 [&](auto& result) {
2✔
293
                    const std::vector<uint8_t> invalid_ccs_record{'\x14', '\x03', '\x03', '\x00', '\x01', '\x02'};
2✔
294
                    result.test_throws("invalid CCS record", "malformed change cipher spec record received", [&] {
2✔
295
                       parse_records(invalid_ccs_record);
2✔
296
                    });
297
                 })
2✔
298

299
   };
26✔
300
}
2✔
301

302
std::vector<Test::Result> basic_sanitization_parse_records_client() {
1✔
303
   return basic_sanitization_parse_records(TLS::Connection_Side::Client);
1✔
304
}
305

306
std::vector<Test::Result> basic_sanitization_parse_records_server() {
1✔
307
   return basic_sanitization_parse_records(TLS::Connection_Side::Server);
1✔
308
}
309

310
std::vector<Test::Result> read_fragmented_records() {
1✔
311
   TLS::Record_Layer rl = record_layer_client(true);
1✔
312

313
   auto wait_for_more_bytes =
1✔
314
      [](Botan::TLS::BytesNeeded bytes_needed, auto& record_layer, std::vector<uint8_t> bytes, auto& result) {
7✔
315
         record_layer.copy_data(bytes);
7✔
316
         const auto rlr = record_layer.next_record();
7✔
317
         if(result.test_is_true("waiting for bytes", std::holds_alternative<TLS::BytesNeeded>(rlr))) {
7✔
318
            result.test_sz_eq("right amount", std::get<TLS::BytesNeeded>(rlr), bytes_needed);
7✔
319
         }
320
      };
7✔
321

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

326
                    wait_for_more_bytes(4, rl, {'\x14'}, result);
1✔
327
                    wait_for_more_bytes(3, rl, {'\x03'}, result);
1✔
328
                    wait_for_more_bytes(2, rl, {'\x03'}, result);
1✔
329
                    wait_for_more_bytes(1, rl, {'\x00'}, result);
1✔
330
                    wait_for_more_bytes(1, rl, {'\x01'}, result);
1✔
331

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

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

340
                    result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
341
                 }),
2✔
342

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

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

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

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

355
              wait_for_more_bytes(2, rl, {'\x03'}, result);
1✔
356

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

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

364
              result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
365
           })};
7✔
366
}
2✔
367

368
std::vector<Test::Result> write_records() {
1✔
369
   auto cs = rfc8448_rtt1_handshake_traffic();
1✔
370
   return {
1✔
371
      CHECK("prepare an zero-length application data fragment",
372
            [&](auto& result) {
1✔
373
               auto record =
1✔
374
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::ApplicationData, {}, cs.get());
375

376
               result.require("record header was added",
2✔
377
                              record.size() > Botan::TLS::TLS_HEADER_SIZE + 1 /* encrypted content type */);
1✔
378
            }),
1✔
379
      CHECK("prepare a client hello",
380
            [&](auto& result) {
1✔
381
               const auto client_hello_msg = Botan::hex_decode(  // from RFC 8448
1✔
382
                  "01 00 00 c0 03 03 cb"
383
                  "34 ec b1 e7 81 63 ba 1c 38 c6 da cb 19 6a 6d ff a2 1a 8d 99 12"
384
                  "ec 18 a2 ef 62 83 02 4d ec e7 00 00 06 13 01 13 03 13 02 01 00"
385
                  "00 91 00 00 00 0b 00 09 00 00 06 73 65 72 76 65 72 ff 01 00 01"
386
                  "00 00 0a 00 14 00 12 00 1d 00 17 00 18 00 19 01 00 01 01 01 02"
387
                  "01 03 01 04 00 23 00 00 00 33 00 26 00 24 00 1d 00 20 99 38 1d"
388
                  "e5 60 e4 bd 43 d2 3d 8e 43 5a 7d ba fe b3 c0 6e 51 c1 3c ae 4d"
389
                  "54 13 69 1e 52 9a af 2c 00 2b 00 03 02 03 04 00 0d 00 20 00 1e"
390
                  "04 03 05 03 06 03 02 03 08 04 08 05 08 06 04 01 05 01 06 01 02"
391
                  "01 04 02 05 02 06 02 02 02 00 2d 00 02 01 01 00 1c 00 02 40 01");
392
               auto record =
1✔
393
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::Handshake, client_hello_msg);
1✔
394

395
               result.require("record header was added",
1✔
396
                              record.size() == client_hello_msg.size() + Botan::TLS::TLS_HEADER_SIZE);
1✔
397

398
               const auto header = std::vector<uint8_t>(record.cbegin(), record.cbegin() + Botan::TLS::TLS_HEADER_SIZE);
1✔
399
               result.test_bin_eq("record header is well-formed", header, "16030100c4");
1✔
400
            }),
1✔
401
      CHECK("prepare a dummy CCS",
402
            [&](auto& result) {
1✔
403
               std::array<uint8_t, 1> ccs_content = {0x01};
1✔
404
               auto record =
1✔
405
                  record_layer_client(true).prepare_records(Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content);
406
               result.require("record was created", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1);
1✔
407

408
               result.test_bin_eq("CCS record is well-formed", record, "140303000101");
1✔
409
            }),
1✔
410
      CHECK("cannot prepare non-dummy CCS",
411
            [&](auto& result) {
1✔
412
               result.test_throws("cannot create non-dummy CCS", "TLS 1.3 deprecated CHANGE_CIPHER_SPEC", [] {
2✔
413
                  const auto ccs_content = Botan::hex_decode("de ad be ef");
1✔
414
                  record_layer_client().prepare_records(Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content);
1✔
415
               });
1✔
416
            }),
1✔
417
      CHECK("large messages are sharded", [&](auto& result) {
1✔
418
         const std::vector<uint8_t> large_client_hello(Botan::TLS::MAX_PLAINTEXT_SIZE + 4096);
1✔
419
         auto record = record_layer_client().prepare_records(Botan::TLS::Record_Type::Handshake, large_client_hello);
1✔
420

421
         result.test_sz_gte("produces at least two record headers",
2✔
422
                            record.size(),
423
                            large_client_hello.size() + 2 * Botan::TLS::TLS_HEADER_SIZE);
1✔
424
      })};
8✔
425
}
2✔
426

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

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

474
   // the record above padded with 42 zeros
475
   const auto encrypted_record_with_padding = Botan::hex_decode(
1✔
476
      "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"
477
      "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"
478
      "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"
479
      "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"
480
      "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"
481
      "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"
482
      "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"
483
      "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"
484
      "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"
485
      "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"
486
      "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"
487
      "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"
488
      "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"
489
      "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"
490
      "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"
491
      "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"
492
      "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"
493
      "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"
494
      "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"
495
      "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"
496
      "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"
497
      "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"
498
      "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"
499
      "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"
500
      "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"
501
      "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"
502
      "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"
503
      "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"
504
      "fa 44 23 0a e0 c9 dd dd bb a8 be db d9 d7 f6 b8 3d 56 4c a5 47");
1✔
505

506
   auto parse_records = [](const std::vector<uint8_t>& data) {
12✔
507
      auto rl = record_layer_client(true);
11✔
508
      rl.copy_data(data);
11✔
509
      return rl;
11✔
510
   };
×
511

512
   return {
1✔
513
      CHECK("read encrypted server hello extensions",
514
            [&](Test::Result& result) {
1✔
515
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
516
               auto rl = parse_records(encrypted_record);
1✔
517

518
               auto res = rl.next_record(cs.get());
1✔
519
               result.require("some records decrypted", !std::holds_alternative<Botan::TLS::BytesNeeded>(res));
1✔
520
               auto record = std::get<TLS::Record>(res);
1✔
521

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

525
               result.test_is_true("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
2✔
526
            }),
2✔
527

528
      CHECK("premature application data",
529
            [&](Test::Result& result) {
1✔
530
               auto rl = record_layer_client(true);
1✔
531
               rl.copy_data(encrypted_record);
1✔
532

533
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
534
                  "cannot process encrypted data with uninitialized cipher state",
535
                  "premature Application Data received",
536
                  [&] { auto res = rl.next_record(nullptr); });
2✔
537
            }),
1✔
538

539
      CHECK("decryption fails due to bad MAC",
540
            [&](Test::Result& result) {
1✔
541
               auto tampered_encrypted_record = encrypted_record;
1✔
542
               tampered_encrypted_record.back() = '\x42';  // changing one payload byte causes the MAC check to fails
1✔
543

544
               result.test_throws<Botan::Invalid_Authentication_Tag>("broken record detected", [&] {
1✔
545
                  auto cs = rfc8448_rtt1_handshake_traffic();
1✔
546
                  auto rl = parse_records(tampered_encrypted_record);
1✔
547
                  rl.next_record(cs.get());
1✔
548
               });
1✔
549
            }),
1✔
550

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

555
               result.test_throws<Botan::TLS::TLS_Exception>("too short to decrypt", [&] {
1✔
556
                  auto cs = rfc8448_rtt1_handshake_traffic();
1✔
557
                  auto rl = parse_records(short_record);
1✔
558
                  rl.next_record(cs.get());
1✔
559
               });
1✔
560
            }),
1✔
561

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

567
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
568
                  "illegal state causes TLS alert", "protected change cipher spec received", [&] {
1✔
569
                     auto cs = rfc8448_rtt1_handshake_traffic();
1✔
570
                     auto rl = parse_records(protected_ccs);
1✔
571
                     rl.next_record(cs.get());
1✔
572
                  });
1✔
573
            }),
1✔
574

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

579
               result.test_no_throw("CCS is acceptable", [&] {
1✔
580
                  auto cs = rfc8448_rtt1_handshake_traffic();  // expect encrypted traffic
1✔
581
                  auto rl = parse_records(ccs_record);
1✔
582
                  rl.next_record(cs.get());
2✔
583
               });
1✔
584
            }),
1✔
585

586
      CHECK("unprotected Alert message might be legal",
587
            [&](Test::Result& result) {
1✔
588
               const auto alert = Botan::hex_decode("15030300020232");  // decode error
1✔
589
               const auto hsmsg = Botan::hex_decode(                    // factored 'certificate_request' message
1✔
590
                  "160303002a0d000027000024000d0020001e040305030603"
591
                  "020308040805080604010501060102010402050206020202");
1✔
592

593
               result.test_no_throw("Server allows unprotected alerts after its first flight", [&] {
1✔
594
                  auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Server);
1✔
595
                  auto rl = parse_records(alert);
1✔
596
                  rl.next_record(cs.get());
2✔
597
               });
1✔
598

599
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
600
                  "Unprotected handshake messages are not allowed for servers",
601
                  "unprotected record received where protected traffic was expected",
602
                  [&] {
1✔
603
                     auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Server);
1✔
604
                     auto rl = parse_records(hsmsg);
1✔
605
                     rl.next_record(cs.get());
1✔
606
                  });
1✔
607

608
               result.test_throws<Botan::TLS::TLS_Exception>(
1✔
609
                  "Clients don't allow unprotected alerts after Server Hello",
610
                  "unprotected record received where protected traffic was expected",
611
                  [&] {
1✔
612
                     auto cs = rfc8448_rtt1_handshake_traffic(TLS::Connection_Side::Client);
1✔
613
                     auto rl = parse_records(alert);
1✔
614
                     rl.next_record(cs.get());
1✔
615
                  });
1✔
616

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

627
      CHECK("unprotected traffic is illegal when encrypted traffic is expected",
628
            [&](Test::Result& result) {
1✔
629
               result.test_throws("unprotected record is unacceptable", [&] {
1✔
630
                  auto cs = rfc8448_rtt1_handshake_traffic();  // expect encrypted traffic
1✔
631
                  auto rl = parse_records(server_hello);
1✔
632
                  rl.next_record(cs.get());
1✔
633
               });
1✔
634
            }),
1✔
635

636
      CHECK("read fragmented application data",
637
            [&](Test::Result& result) {
1✔
638
               const auto encrypted = Botan::hex_decode(
1✔
639
                  "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"
640
                  "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"
641
                  "80 C2 E9 3D EC 84 3B 8D 41 30 D8 C8 C5 D8"
642
                  "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"
643
                  "14 72 39 6F A1 F3 D3 ");
1✔
644
               const std::vector<std::vector<uint8_t>> plaintext_records = {
1✔
645
                  Botan::hex_decode("00 01 02 03 04 05 06 07 08"),
646
                  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"),
647
                  Botan::hex_decode("20 21 22 23 24 25 26 27 28 29 2a 2b 2c 2d 2e 2f")};
4✔
648

649
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
650
               // advance with arbitrary hashes that were used to produce the input data
651
               const Mocked_Secret_Logger logger;
1✔
652
               cs->advance_with_server_finished(
1✔
653
                  Botan::hex_decode("e1935a480babfc4403b2517f0ad414bed0ca51fa671e2061804afa78fd71d55c"), logger);
2✔
654
               cs->advance_with_client_finished(
1✔
655
                  Botan::hex_decode("305e4a0a7cee581b282c571b251b20138a1a6a21918937a6bb95b1e9ba1b5cac"));
2✔
656

657
               auto rl = parse_records(encrypted);
1✔
658
               auto res = rl.next_record(cs.get());
1✔
659
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
660
               auto records = std::get<TLS::Record>(res);
1✔
661
               result.test_bin_eq("first record", records.fragment, plaintext_records.at(0));
1✔
662

663
               res = rl.next_record(cs.get());
2✔
664
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
665
               records = std::get<TLS::Record>(res);
1✔
666
               result.test_bin_eq("second record", records.fragment, plaintext_records.at(1));
1✔
667

668
               res = rl.next_record(cs.get());
2✔
669
               result.require("decrypted a record", std::holds_alternative<TLS::Record>(res));
1✔
670
               records = std::get<TLS::Record>(res);
1✔
671
               result.test_bin_eq("third record", records.fragment, plaintext_records.at(2));
1✔
672

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

676
      CHECK("read coalesced server hello and encrypted extensions",
677
            [&](Test::Result& result) {
1✔
678
               // contains the plaintext server hello and the encrypted extensions in one go
679
               auto coalesced = server_hello;
1✔
680
               coalesced.insert(coalesced.end(), encrypted_record.cbegin(), encrypted_record.cend());
1✔
681

682
               auto client = record_layer_client(true);
1✔
683
               client.copy_data(coalesced);
1✔
684

685
               const auto srv_hello = client.next_record(nullptr);
1✔
686
               result.test_is_true("read a record", std::holds_alternative<TLS::Record>(srv_hello));
1✔
687
               result.test_is_true("is handshake record",
1✔
688
                                   std::get<TLS::Record>(srv_hello).type == TLS::Record_Type::Handshake);
1✔
689

690
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
691
               const auto enc_exts = client.next_record(cs.get());
1✔
692
               result.test_is_true("read a record", std::holds_alternative<TLS::Record>(enc_exts));
1✔
693
               result.test_is_true("is handshake record",
2✔
694
                                   std::get<TLS::Record>(enc_exts).type == TLS::Record_Type::Handshake);
1✔
695
            }),
2✔
696

697
      CHECK("read a padded record",
698
            [&](Test::Result& result) {
1✔
699
               auto client = record_layer_client(true);
1✔
700
               client.copy_data(encrypted_record_with_padding);
1✔
701

702
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
703
               const auto record = client.next_record(cs.get());
1✔
704
               result.test_is_true("read a record with padding", std::holds_alternative<TLS::Record>(record));
1✔
705
            }),
1✔
706

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

711
         auto cs = rfc8448_rtt1_handshake_traffic();
1✔
712
         const auto record = client.next_record(cs.get());
1✔
713
         result.test_is_true("read an empty record", std::holds_alternative<TLS::Record>(record));
1✔
714
      })};
15✔
715
}
2✔
716

717
std::vector<Test::Result> write_encrypted_records() {
1✔
718
   auto plaintext_msg = Botan::hex_decode(
1✔
719
      "14 00 00 20 a8 ec 43 6d 67 76 34 ae"
720
      "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✔
721

722
   auto cs = rfc8448_rtt1_handshake_traffic();
1✔
723
   return {
1✔
724
      CHECK("write encrypted client handshake finished",
725
            [&](Test::Result& result) {
1✔
726
               auto ct =
1✔
727
                  record_layer_client(true).prepare_records(TLS::Record_Type::Handshake, plaintext_msg, cs.get());
1✔
728
               auto expected_ct = Botan::hex_decode(
1✔
729
                  "17 03 03 00 35 75 ec 4d c2 38 cc e6"
730
                  "0b 29 80 44 a7 1e 21 9c 56 cc 77 b0 51 7f e9 b9 3c 7a 4b fc 44 d8 7f"
731
                  "38 f8 03 38 ac 98 fc 46 de b3 84 bd 1c ae ac ab 68 67 d7 26 c4 05 46");
1✔
732
               result.test_bin_eq("produced the expected ciphertext", ct, expected_ct);
1✔
733
            }),
1✔
734

735
      CHECK("write a dummy CCS (that must not be encrypted)",
736
            [&](auto& result) {
1✔
737
               std::array<uint8_t, 1> ccs_content = {0x01};
1✔
738
               auto record = record_layer_client(true).prepare_records(
1✔
739
                  Botan::TLS::Record_Type::ChangeCipherSpec, ccs_content, cs.get());
740
               result.require("record was created and not encrypted", record.size() == Botan::TLS::TLS_HEADER_SIZE + 1);
1✔
741

742
               result.test_bin_eq("CCS record is well-formed", record, "140303000101");
1✔
743
            }),
1✔
744

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

751
         auto read_record_header = [&](auto& reader) {
3✔
752
            result.test_u8_eq(
2✔
753
               "APPLICATION_DATA", reader.get_byte(), static_cast<uint8_t>(TLS::Record_Type::ApplicationData));
2✔
754
            result.test_u16_eq("TLS legacy version", reader.get_uint16_t(), uint16_t(0x0303));
2✔
755

756
            const auto fragment_length = reader.get_uint16_t();
2✔
757
            result.test_sz_lte("TLS limits", fragment_length, TLS::MAX_CIPHERTEXT_SIZE_TLS13);
2✔
758
            result.require("enough data", fragment_length + Botan::TLS::TLS_HEADER_SIZE < ct.size());
2✔
759
            return fragment_length;
2✔
760
         };
1✔
761

762
         TLS::TLS_Data_Reader reader("test reader", ct);
1✔
763
         const auto fragment_length1 = read_record_header(reader);
1✔
764
         reader.discard_next(fragment_length1);
1✔
765

766
         const auto fragment_length2 = read_record_header(reader);
1✔
767
         reader.discard_next(fragment_length2);
1✔
768

769
         result.test_is_true("consumed all bytes", !reader.has_remaining());
1✔
770
      })};
6✔
771
}
2✔
772

773
std::vector<Test::Result> legacy_version_handling() {
1✔
774
   // RFC 8446 5.1:
775
   // legacy_record_version:  MUST be set to 0x0303 for all records
776
   //    generated by a TLS 1.3 implementation other than an initial
777
   //    ClientHello (i.e., one not generated after a HelloRetryRequest),
778
   //    where it MAY also be 0x0301 for compatibility purposes.
779

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

783
      while(dr.has_remaining()) {
18✔
784
         dr.discard_next(1);  // record type
13✔
785
         if(dr.get_uint16_t() != version) {
13✔
786
            return false;
787
         }
788
         const auto record_size = dr.get_uint16_t();
13✔
789
         dr.discard_next(record_size);
13✔
790
      }
791

792
      dr.assert_done();
793
      return true;
794
   };
795

796
   auto parse_record = [](auto& record_layer, const std::vector<uint8_t>& data) {
11✔
797
      record_layer.copy_data(data);
11✔
798
      return record_layer.next_record();
11✔
799
   };
800

801
   return {CHECK("client side starts with version 0x0301",
1✔
802
                 [&](Test::Result& result) {
1✔
803
                    auto rl = record_layer_client();
1✔
804
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
1✔
805
                    result.test_is_true("first record has version 0x0301", has_version(rec, 0x0301));
1✔
806

807
                    rl.disable_sending_compat_mode();
1✔
808

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

813
           CHECK("client side starts with version 0x0301 (even if multiple reconds are required)",
814
                 [&](Test::Result& result) {
1✔
815
                    auto rl = record_layer_client();
1✔
816
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake,
1✔
817
                                                  std::vector<uint8_t>(5 * Botan::TLS::MAX_PLAINTEXT_SIZE));
1✔
818
                    result.test_is_true("first record has version 0x0301", has_version(rec, 0x0301));
1✔
819

820
                    rl.disable_sending_compat_mode();
1✔
821

822
                    rec = rl.prepare_records(TLS::Record_Type::Handshake,
2✔
823
                                             std::vector<uint8_t>(5 * Botan::TLS::MAX_PLAINTEXT_SIZE));
3✔
824
                    result.test_is_true("next record has version 0x0303", has_version(rec, 0x0303));
1✔
825
                 }),
1✔
826

827
           CHECK("server side starts with version 0x0303",
828
                 [&](Test::Result& result) {
1✔
829
                    auto rl = record_layer_server(true);
1✔
830
                    auto rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
1✔
831
                    result.test_is_true("first record has version 0x0303", has_version(rec, 0x0303));
1✔
832
                 }),
1✔
833

834
           CHECK("server side accepts version 0x0301 for the first record",
835
                 [&](Test::Result& result) {
1✔
836
                    const auto first_record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00");
1✔
837
                    const auto second_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00");
1✔
838
                    auto rl = record_layer_server();
1✔
839
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record); });
2✔
840
                    result.test_no_throw("parsing second record", [&] { parse_record(rl, second_record); });
2✔
841
                 }),
1✔
842

843
           CHECK("server side accepts version 0x0301 for the first record for partial records",
844
                 [&](Test::Result& result) {
1✔
845
                    const auto first_part = Botan::hex_decode("16 03 01");
1✔
846
                    const auto second_part = Botan::hex_decode("00 05 00 00 00 00 00");
1✔
847
                    auto rl = record_layer_server();
1✔
848
                    result.test_no_throw("parsing initial part", [&] { parse_record(rl, first_part); });
2✔
849
                    result.test_no_throw("parsing second part", [&] { parse_record(rl, second_part); });
2✔
850
                 }),
1✔
851

852
           CHECK("server side accepts version 0x0303 for the first record",
853
                 [&](Test::Result& result) {
1✔
854
                    const auto first_record = Botan::hex_decode("16 03 03 00 05 00 00 00 00 00");
1✔
855
                    auto rl = record_layer_server();
1✔
856
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, first_record); });
2✔
857
                 }),
1✔
858

859
           CHECK("server side does not accept version 0x0301 after receiving client hello",
860
                 [&](Test::Result& result) {
1✔
861
                    const auto record = Botan::hex_decode("16 03 01 00 05 00 00 00 00 00");
1✔
862
                    auto rl = record_layer_server();
1✔
863
                    result.test_no_throw("parsing initial record", [&] { parse_record(rl, record); });
2✔
864
                    rl.disable_receiving_compat_mode();
1✔
865
                    result.test_throws("parsing second record", [&] { parse_record(rl, record); });
2✔
866
                 }),
1✔
867

868
           CHECK("server side does not accept other versions (after receiving client hello)",
869
                 [&](Test::Result& result) {
1✔
870
                    auto rl = record_layer_server(true);
1✔
871
                    result.test_throws("does not accept 0x0300",
1✔
872
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 00 00 05 00 00 00 00 00")); });
2✔
873
                    result.test_throws("does not accept 0x0302",
1✔
874
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 02 00 05 00 00 00 00 00")); });
2✔
875
                    result.test_throws("does not accept 0x0304",
1✔
876
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 04 00 05 00 00 00 00 00")); });
2✔
877
                    result.test_throws("does not accept 0x0305",
1✔
878
                                       [&] { parse_record(rl, Botan::hex_decode("16 03 05 00 05 00 00 00 00 00")); });
2✔
879
                 })
1✔
880

881
   };
9✔
882
}
1✔
883

884
std::vector<Test::Result> record_size_limits() {
1✔
885
   const auto count_records = [](auto& records) {
10✔
886
      Botan::TLS::TLS_Data_Reader reader("record counter", records);
10✔
887
      size_t record_count = 0;
10✔
888

889
      for(; reader.has_remaining(); ++record_count) {
25✔
890
         reader.discard_next(1);                               // record type
15✔
891
         BOTAN_ASSERT_NOMSG(reader.get_uint16_t() == 0x0303);  // record version
15✔
892
         reader.get_tls_length_value(2);                       // record length/content
15✔
893
      }
894

895
      return record_count;
10✔
896
   };
897

898
   const auto record_length = [](auto& result, auto record) {
3✔
899
      result.require("has record", std::holds_alternative<Botan::TLS::Record>(record));
3✔
900
      const auto& r = std::get<Botan::TLS::Record>(record);
3✔
901
      return r.fragment.size();
3✔
902
   };
903

904
   return {
1✔
905
      CHECK("no specified limits means protocol defaults",
906
            [&](Test::Result& result) {
1✔
907
               auto csc = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Client);
1✔
908
               auto rlc = record_layer_client(true);
1✔
909

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

914
               const auto rec2 = rlc.prepare_records(TLS::Record_Type::ApplicationData,
1✔
915
                                                     std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE + 1),
2✔
916
                                                     csc.get());
1✔
917
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
918

919
               auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Server);
1✔
920
               auto rls = record_layer_server(true);
1✔
921
               rls.copy_data(rec1);
1✔
922

923
               result.test_sz_eq("correct length record",
1✔
924
                                 record_length(result, rls.next_record(css.get())),
2✔
925
                                 Botan::TLS::MAX_PLAINTEXT_SIZE);
926
            }),
1✔
927

928
      CHECK("outgoing record size limit",
929
            [&](Test::Result& result) {
1✔
930
               auto cs = rfc8448_rtt1_handshake_traffic();
1✔
931
               auto rl = record_layer_client(true);
1✔
932

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

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

939
               const auto rec2 =
1✔
940
                  rl.prepare_records(TLS::Record_Type::ApplicationData, std::vector<uint8_t>(128), cs.get());
1✔
941
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
942
            }),
1✔
943

944
      CHECK(
945
         "outgoing record size limit can be changed",
946
         [&](Test::Result& result) {
1✔
947
            auto cs = rfc8448_rtt1_handshake_traffic();
1✔
948
            auto rl = record_layer_client(true);
1✔
949

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

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

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

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

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

967
      CHECK("outgoing record limit does not affect unencrypted records",
968
            [&](Test::Result& result) {
1✔
969
               auto rl = record_layer_client(true);
1✔
970

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

973
               const auto rec1 =
1✔
974
                  rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE));
1✔
975
               result.test_sz_eq("one record generated", count_records(rec1), 1);
1✔
976

977
               const auto rec2 = rl.prepare_records(TLS::Record_Type::Handshake,
1✔
978
                                                    std::vector<uint8_t>(Botan::TLS::MAX_PLAINTEXT_SIZE + 1));
1✔
979
               result.test_sz_eq("two records generated", count_records(rec2), 2);
1✔
980
            }),
1✔
981

982
      CHECK("incoming limit is not checked on unprotected records",
983
            [&](Test::Result& result) {
1✔
984
               auto rlc = record_layer_client(true);
1✔
985

986
               rlc.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 95 + 1);
1✔
987

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

992
      CHECK("incoming limit is checked on protected records",
993
            [&](Test::Result& result) {
1✔
994
               auto css = rfc8448_rtt1_handshake_traffic(Botan::TLS::Connection_Side::Server);
1✔
995
               auto rls = record_layer_server(true);
1✔
996

997
               rls.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 127 + 1);
1✔
998
               rls.copy_data(
1✔
999
                  Botan::hex_decode("170303009061ec4de29020a5664ef670094c7b5daa2796aa52e128cfa8808d15c1"
2✔
1000
                                    "ffc97a0aeeed62f9ea690bb753a03d000c5efac53c619face25ad234dffb63e611"
1001
                                    "4619fb045e3a3a0dde4f22e2399b4891029eccb79ea4a29c45a999e72fc74157f0"
1002
                                    "21db0afa05601af25b61df82fb728c772ad860081d96c86008c08d0c21f991cf0d"
1003
                                    "4a0eadc840d1ea8fb1f5dd852980d78fcc"));
1004

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

1007
               rls.copy_data(
1✔
1008
                  Botan::hex_decode("1703030091234d4a480092fa6a55f1443345ee8d2250cd9c676370be68f86234db"
2✔
1009
                                    "f5514c6dea8b3fa99c6146fefc780e36230858a53f4c0295b23a77dc5b495e0541"
1010
                                    "093aa05ee6cf6f4a4996d9ffc829b638c822e4c36e4da50f1cf2845c12e4388d58"
1011
                                    "e907e181f2dd38e61e78c13ebcbd562a23025fd327eb4db083330314e4641f3b4b"
1012
                                    "43bf11dbb09f7a82443193dc9ece34dabd15"));
1013

1014
               result.test_throws("overflow detected",
1✔
1015
                                  "Received an encrypted record that exceeds maximum plaintext size",
1016
                                  [&] { rls.next_record(css.get()); });
2✔
1017
            }),
1✔
1018
   };
7✔
1019
}
1✔
1020

1021
}  // namespace
1022

1023
BOTAN_REGISTER_TEST_FN("tls",
1024
                       "tls_record_layer_13",
1025
                       basic_sanitization_parse_records_client,
1026
                       basic_sanitization_parse_records_server,
1027
                       read_full_records,
1028
                       read_fragmented_records,
1029
                       write_records,
1030
                       read_encrypted_records,
1031
                       write_encrypted_records,
1032
                       legacy_version_handling,
1033
                       record_size_limits);
1034

1035
}  // namespace Botan_Tests
1036

1037
#endif
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