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

randombit / botan / 21768358452

06 Feb 2026 10:35PM UTC coverage: 90.064% (-0.003%) from 90.067%
21768358452

Pull #5289

github

web-flow
Merge f589db195 into 8ea0ca252
Pull Request #5289: Further misc header reductions, forward declarations, etc

102238 of 113517 relevant lines covered (90.06%)

11357432.36 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/stl_util.h>
17
   #include <botan/internal/tls_cipher_state.h>
18
   #include <botan/internal/tls_reader.h>
19

20
   #include <botan/internal/tls_channel_impl_13.h>
21
   #include <botan/internal/tls_record_layer_13.h>
22

23
   #include <array>
24

25
namespace Botan_Tests {
26

27
namespace {
28

29
namespace TLS = Botan::TLS;
30

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

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

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

41
   return rl;
44✔
42
}
43

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

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

52
   return rl;
12✔
53
}
54

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

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

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

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

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

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

99
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
100
                 }),
3✔
101

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

106
                    auto rl = record_layer_server();
1✔
107

108
                    rl.copy_data(two_ccs_records);
1✔
109

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

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

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

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

124
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
125
                 }),
4✔
126

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

132
                    auto read = rl.next_record();
1✔
133
                    result.confirm("received something", std::holds_alternative<TLS::Record>(read));
2✔
134

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

142
                    result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
143
                 }),
3✔
144

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

148
              auto rl = record_layer_server();
1✔
149
              rl.copy_data(payload);
1✔
150

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

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

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

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

168
              result.confirm("no more records", std::holds_alternative<TLS::BytesNeeded>(rl.next_record()));
3✔
169
           })};
9✔
170
}
3✔
171

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

179
   return {CHECK("'receive' empty data",
2✔
180
                 [&](auto& result) {
2✔
181
                    auto read = parse_records({});
2✔
182
                    result.require("needs bytes", std::holds_alternative<TLS::BytesNeeded>(read));
4✔
183
                    result.test_eq(
4✔
184
                       "need all the header bytes", std::get<TLS::BytesNeeded>(read), Botan::TLS::TLS_HEADER_SIZE);
2✔
185
                 }),
2✔
186

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

285
                    rl.disable_receiving_compat_mode();
2✔
286

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

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

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

302
   };
26✔
303
}
2✔
304

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

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

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

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

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

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

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

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

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

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

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

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

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

358
              wait_for_more_bytes(2, rl, {'\x03'}, result);
1✔
359

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

686
            auto client = record_layer_client(true);
1✔
687
            client.copy_data(coalesced);
1✔
688

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

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

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

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

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

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

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

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

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

744
               result.test_eq("CCS record is well-formed", record, Botan::hex_decode("140303000101"));
3✔
745
            }),
1✔
746

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

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

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

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

768
         const auto fragment_length2 = read_record_header(reader);
1✔
769
         reader.discard_next(fragment_length2);
1✔
770

771
         result.confirm("consumed all bytes", !reader.has_remaining());
2✔
772
      })};
6✔
773
}
3✔
774

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

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

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

794
      dr.assert_done();
795
      return true;
796
   };
797

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

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

809
                    rl.disable_sending_compat_mode();
1✔
810

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

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

822
                    rl.disable_sending_compat_mode();
1✔
823

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

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

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

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

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

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

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

883
   };
9✔
884
}
1✔
885

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

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

897
      return record_count;
10✔
898
   };
899

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

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

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

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

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

925
               result.test_eq("correct length record",
2✔
926
                              record_length(result, rls.next_record(css.get())),
2✔
927
                              Botan::TLS::MAX_PLAINTEXT_SIZE);
928
            }),
3✔
929

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

988
               rlc.set_record_size_limits(Botan::TLS::MAX_PLAINTEXT_SIZE + 1, 95 + 1);
1✔
989

990
               rlc.copy_data(Botan::concat(Botan::hex_decode("16 03 03 00 80"), std::vector<uint8_t>(128)));
4✔
991
               result.test_eq("correct length record", record_length(result, rlc.next_record()), 128);
3✔
992
            }),
1✔
993

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

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

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

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

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

1023
}  // namespace
1024

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

1037
}  // namespace Botan_Tests
1038

1039
#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