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

randombit / botan / 21971640094

13 Feb 2026 01:54AM UTC coverage: 91.632% (+1.6%) from 90.067%
21971640094

Pull #5314

github

web-flow
Merge 9acca15d9 into e7443105f
Pull Request #5314: Reduce header dependencies in STL utility headers

104009 of 113507 relevant lines covered (91.63%)

11495635.96 hits per line

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

99.67
/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);
23✔
69
}
69✔
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));
2✔
92

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

97
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
98
                 }),
3✔
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));
2✔
110
                    auto record = std::get<TLS::Record>(read);
1✔
111

112
                    result.confirm("received CCS 1", record.type == TLS::Record_Type::ChangeCipherSpec);
2✔
113
                    result.test_eq("CCS byte is 0x01", record.fragment, Botan::hex_decode("01"));
2✔
114

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

119
                    result.confirm("received CCS 2", record.type == TLS::Record_Type::ChangeCipherSpec);
2✔
120
                    result.test_eq("CCS byte is 0x01", record.fragment, Botan::hex_decode("01"));
2✔
121

122
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
123
                 }),
4✔
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.confirm("received something", std::holds_alternative<TLS::Record>(read));
2✔
132

133
                    auto rec = std::get<TLS::Record>(read);
1✔
134
                    result.confirm("received handshake record", rec.type == TLS::Record_Type::Handshake);
2✔
135
                    result.test_eq("contains the full handshake message",
2✔
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.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
141
                 }),
3✔
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));
2✔
151

152
              auto rec = std::get<TLS::Record>(read);
1✔
153
              result.confirm("received handshake record", rec.type == TLS::Record_Type::Handshake);
2✔
154
              result.test_eq("contains the full handshake message",
2✔
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));
2✔
161

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

166
              result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
167
           })};
9✔
168
}
3✔
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));
4✔
181
                    result.test_eq(
4✔
182
                       "need all the header bytes", std::get<TLS::BytesNeeded>(read), Botan::TLS::TLS_HEADER_SIZE);
2✔
183
                 }),
2✔
184

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

283
                    rl.disable_receiving_compat_mode();
2✔
284

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

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

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

300
   };
26✔
301
}
2✔
302

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

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

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

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

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

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

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

337
                    auto rec1 = std::get<TLS::Record>(res1);
1✔
338
                    result.confirm("received CCS", rec1.type == TLS::Record_Type::ChangeCipherSpec);
2✔
339
                    result.test_eq("CCS byte is 0x01", rec1.fragment, Botan::hex_decode("01"));
2✔
340

341
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
342
                 }),
3✔
343

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

526
               result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
527
            }),
3✔
528

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

674
               result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
675
            }),
6✔
676

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

684
            auto client = record_layer_client(true);
1✔
685
            client.copy_data(coalesced);
1✔
686

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

691
            auto cs = rfc8448_rtt1_handshake_traffic();
1✔
692
            const auto enc_exts = client.next_record(cs.get());
1✔
693
            result.confirm("read a record", std::holds_alternative<TLS::Record>(enc_exts));
2✔
694
            result.confirm("is handshake record", std::get<TLS::Record>(enc_exts).type == TLS::Record_Type::Handshake);
2✔
695
         }),
4✔
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.confirm("read a record with padding", std::holds_alternative<TLS::Record>(record));
2✔
705
            }),
2✔
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.confirm("read an empty record", std::holds_alternative<TLS::Record>(record));
2✔
714
      })};
15✔
715
}
4✔
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_eq("produced the expected ciphertext", ct, expected_ct);
2✔
733
            }),
2✔
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);
2✔
741

742
               result.test_eq("CCS record is well-formed", record, Botan::hex_decode("140303000101"));
3✔
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_is_eq(
2✔
753
               "APPLICATION_DATA", reader.get_byte(), static_cast<uint8_t>(TLS::Record_Type::ApplicationData));
2✔
754
            result.test_is_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_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.confirm("consumed all bytes", !reader.has_remaining());
2✔
770
      })};
6✔
771
}
3✔
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.confirm("first record has version 0x0301", has_version(rec, 0x0301));
2✔
806

807
                    rl.disable_sending_compat_mode();
1✔
808

809
                    rec = rl.prepare_records(TLS::Record_Type::Handshake, std::vector<uint8_t>(5));
2✔
810
                    result.confirm("next record has version 0x0303", has_version(rec, 0x0303));
2✔
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.confirm("first record has version 0x0301", has_version(rec, 0x0301));
2✔
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));
2✔
824
                    result.confirm("next record has version 0x0303", has_version(rec, 0x0303));
2✔
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.confirm("first record has version 0x0303", has_version(rec, 0x0303));
2✔
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); });
3✔
840
                    result.test_no_throw("parsing second record", [&] { parse_record(rl, second_record); });
4✔
841
                 }),
3✔
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); });
3✔
849
                    result.test_no_throw("parsing second part", [&] { parse_record(rl, second_part); });
4✔
850
                 }),
3✔
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); });
4✔
857
                 }),
2✔
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); });
3✔
864
                    rl.disable_receiving_compat_mode();
1✔
865
                    result.test_throws("parsing second record", [&] { parse_record(rl, record); });
4✔
866
                 }),
2✔
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",
2✔
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",
2✔
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",
2✔
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",
3✔
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
30✔
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));
6✔
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_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),
1✔
916
                                                     csc.get());
1✔
917
               result.test_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_eq("correct length record",
2✔
924
                              record_length(result, rls.next_record(css.get())),
2✔
925
                              Botan::TLS::MAX_PLAINTEXT_SIZE);
926
            }),
3✔
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_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_eq("two records generated", count_records(rec2), 2);
2✔
942
            }),
2✔
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_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_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_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_eq("two records generated", count_records(r4), 2);
2✔
965
         }),
4✔
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_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_eq("two records generated", count_records(rec2), 2);
2✔
980
            }),
2✔
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)));
4✔
989
               result.test_eq("correct length record", record_length(result, rlc.next_record()), 128);
3✔
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"
1✔
1000
                                    "ffc97a0aeeed62f9ea690bb753a03d000c5efac53c619face25ad234dffb63e611"
1001
                                    "4619fb045e3a3a0dde4f22e2399b4891029eccb79ea4a29c45a999e72fc74157f0"
1002
                                    "21db0afa05601af25b61df82fb728c772ad860081d96c86008c08d0c21f991cf0d"
1003
                                    "4a0eadc840d1ea8fb1f5dd852980d78fcc"));
1004

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

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

1014
               result.test_throws("overflow detected",
3✔
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