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

randombit / botan / 31035560655

05 Aug 2026 05:18PM UTC coverage: 91.916% (+2.2%) from 89.682%
31035560655

push

github

web-flow
Merge pull request #5799 from fcasal/fix-mceliece-gf2m-cache-race

Make legacy McEliece GF2m caches thread-safe

122141 of 132884 relevant lines covered (91.92%)

9988116.5 hits per line

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

96.95
/src/tests/test_dtls12.cpp
1
/*
2
* (C) 2014,2015,2018,2026 Jack Lloyd
3
*     2016 Matthias Gierlings
4
*     2017 René Korthaus, Rohde & Schwarz Cybersecurity
5
*     2017 Harry Reimann, Rohde & Schwarz Cybersecurity
6
*     2023 René Meusel, Rohde & Schwarz Cybersecurity
7
*     2026 René Meusel, Rohde & Schwarz Networks and Cybersecurity
8
*     2025 Lars Dürkop, CARIAD SE
9
*     2026 Thiesius
10
*
11
* Botan is released under the Simplified BSD License (see license.txt)
12
*/
13

14
#include "tests.h"
15

16
#include <array>
17
#include <memory>
18
#include <optional>
19
#include <string>
20
#include <vector>
21

22
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_12)
23

24
   #include <botan/credentials_manager.h>
25
   #include <botan/rng.h>
26
   #include <botan/tls_callbacks.h>
27
   #include <botan/tls_client.h>
28
   #include <botan/tls_exceptn.h>
29
   #include <botan/tls_policy.h>
30
   #include <botan/tls_server.h>
31
   #include <botan/tls_session_manager_memory.h>
32
   #include <botan/tls_session_manager_noop.h>
33
   #include <botan/tls_session_manager_stateless.h>
34

35
#endif
36

37
namespace Botan_Tests {
38

39
namespace {
40

41
#if defined(BOTAN_HAS_TLS) && defined(BOTAN_HAS_TLS_12)
42

43
/*
44
These tests exercise the DTLS core without opening real UDP sockets. The test
45
callbacks collect bytes emitted by TLS::Client/TLS::Server, and deliver() feeds
46
those bytes into the peer's received_data(). Clearing, splitting, copying, or
47
delaying those buffers simulates datagram loss, retransmission, duplicates, and
48
partial flights while still running the real DTLS record, handshake, epoch, and
49
retransmission logic.
50
*/
51

52
class DTLS_Test_Callbacks final : public Botan::TLS::Callbacks {
56✔
53
   public:
54
      DTLS_Test_Callbacks(Test::Result& result, std::vector<uint8_t>& outbound, std::vector<uint8_t>& received) :
56✔
55
            m_result(result), m_outbound(outbound), m_received(received) {}
56✔
56

57
      void tls_emit_data(std::span<const uint8_t> bits) override {
398✔
58
         m_outbound.insert(m_outbound.end(), bits.begin(), bits.end());
398✔
59
      }
398✔
60

61
      void tls_record_received(uint64_t /*seq*/, std::span<const uint8_t> bits) override {
11✔
62
         m_received.insert(m_received.end(), bits.begin(), bits.end());
11✔
63
      }
11✔
64

65
      void tls_alert(Botan::TLS::Alert alert) override {
×
66
         ++m_alerts;
×
67
         m_last_alert = alert;
×
68

69
         if(alert.is_fatal() && !m_tolerate_fatal_alerts) {
×
70
            m_result.test_failure("unexpected fatal alert: " + alert.type_string());
×
71
         }
72
      }
×
73

74
      // A DTLS server requires a non-empty peer identity to bind the cookie to.
75
      std::string tls_peer_network_identity() override { return "test-peer"; }
58✔
76

77
      void tls_session_established(const Botan::TLS::Session_Summary& session) override {
54✔
78
         m_session_was_resumption = session.was_resumption();
54✔
79
         ++m_sessions_established;
54✔
80
      }
54✔
81

82
      // All DTLS timers in these tests run on this synthetic clock, which
83
      // moves only when a test calls advance_clock_ms. Retransmission
84
      // behavior is therefore independent of real scheduling delays.
85
      uint64_t tls_current_monotonic_clock_ms() override { return m_now_ms; }
306✔
86

87
      void advance_clock_ms(uint64_t delta) { m_now_ms += delta; }
25✔
88

89
      // For endpoints a test expects to fail; without this a fatal alert marks
90
      // the whole result failed.
91
      void tolerate_fatal_alerts() { m_tolerate_fatal_alerts = true; }
×
92

93
      size_t sessions_established() const { return m_sessions_established; }
5✔
94

95
      size_t alerts_received() const { return m_alerts; }
96

97
      std::optional<Botan::TLS::Alert> last_alert() const { return m_last_alert; }
98

99
      std::optional<bool> session_was_resumption() const { return m_session_was_resumption; }
10✔
100

101
   private:
102
      Test::Result& m_result;
103
      std::vector<uint8_t>& m_outbound;
104
      std::vector<uint8_t>& m_received;
105
      size_t m_sessions_established = 0;
106
      size_t m_alerts = 0;
107
      std::optional<Botan::TLS::Alert> m_last_alert;
108
      std::optional<bool> m_session_was_resumption;
109
      uint64_t m_now_ms = 0;
110
      bool m_tolerate_fatal_alerts = false;
111
};
112

113
class DTLS_PSK_Credentials final : public Botan::Credentials_Manager {
48✔
114
   public:
115
      Botan::SymmetricKey psk(const std::string& type,
136✔
116
                              const std::string& context,
117
                              const std::string& /*identity*/) override {
118
         if(type == "tls-server" && context == "session-ticket") {
136✔
119
            return Botan::SymmetricKey("AABBCCDDEEFF012345678012345678");
×
120
         }
121

122
         if(type == "tls-server" && context == "dtls-cookie-secret") {
136✔
123
            ++m_dtls_cookie_secret_requests;
85✔
124
            return m_cookie_secret_rotated ? Botan::SymmetricKey("0123456789ABCDEF0123456789ABCDEF")
85✔
125
                                           : Botan::SymmetricKey("4AEA5EAD279CADEB537A594DA0E9DE3A");
85✔
126
         }
127

128
         if(context == "localhost" && (type == "tls-client" || type == "tls-server")) {
51✔
129
            return Botan::SymmetricKey("20B602D1475F2DF888FCB60D2AE03AFD");
51✔
130
         }
131

132
         throw Test_Error("No PSK set for " + type + "/" + context);
×
133
      }
134

135
      size_t dtls_cookie_secret_requests() const { return m_dtls_cookie_secret_requests; }
1✔
136

137
      void rotate_cookie_secret() { m_cookie_secret_rotated = true; }
1✔
138

139
   private:
140
      size_t m_dtls_cookie_secret_requests = 0;
141
      bool m_cookie_secret_rotated = false;
142
};
143

144
// Settings a test wants to differ from the shared PSK-over-DTLS baseline. The
145
// timeouts are measured against the synthetic clock in DTLS_Test_Callbacks;
146
// everything else follows Botan::TLS::Policy, which the unset optionals
147
// below defer to.
148
struct DTLS_Policy_Options final {
2✔
149
      std::optional<size_t> max_retransmissions;
150
      std::optional<std::string> cipher;
151
      bool allow_epoch0_restart = true;
152
      bool reuse_session_tickets = false;
153
      size_t initial_timeout_ms = 1;
154
      size_t maximum_timeout_ms = 8;
155
};
156

157
class DTLS_PSK_Policy final : public Botan::TLS::Policy {
24✔
158
   public:
159
      DTLS_PSK_Policy() = default;
22✔
160

161
      explicit DTLS_PSK_Policy(DTLS_Policy_Options opts) : m_opts(std::move(opts)) {}
2✔
162

163
      std::optional<size_t> dtls_maximum_retransmissions() const override {
65✔
164
         // TODO(C++23) std::optional::or_else
165
         if(m_opts.max_retransmissions.has_value()) {
65✔
166
            return m_opts.max_retransmissions;
5✔
167
         } else {
168
            return Botan::TLS::Policy::dtls_maximum_retransmissions();
60✔
169
         }
170
      }
171

172
      std::vector<std::string> allowed_macs() const override { return {"AEAD"}; }
1,083✔
173

174
      std::vector<std::string> allowed_key_exchange_methods() const override { return {"PSK"}; }
60✔
175

176
      std::vector<std::string> allowed_ciphers() const override {
5,883✔
177
         if(m_opts.cipher.has_value()) {
5,883✔
178
            return {m_opts.cipher.value()};
×
179
         }
180
         return Botan::TLS::Policy::allowed_ciphers();
5,883✔
181
      }
×
182

183
      bool allow_tls12() const override { return false; }
92✔
184

185
      bool allow_dtls12() const override { return true; }
302✔
186

187
      bool allow_dtls_epoch0_restart() const override { return m_opts.allow_epoch0_restart; }
112✔
188

189
      bool allow_server_initiated_renegotiation() const override { return true; }
1✔
190

191
      bool allow_client_initiated_renegotiation() const override { return true; }
2✔
192

193
      bool reuse_session_tickets() const override { return m_opts.reuse_session_tickets; }
29✔
194

195
      size_t dtls_initial_timeout() const override { return m_opts.initial_timeout_ms; }
65✔
196

197
      size_t dtls_maximum_timeout() const override { return m_opts.maximum_timeout_ms; }
65✔
198

199
   private:
200
      DTLS_Policy_Options m_opts;
201
};
202

203
// The variants the tests below need. Everything not named here follows the
204
// baseline above.
205

206
std::shared_ptr<DTLS_PSK_Policy> dtls_policy_with_max_retransmissions(size_t limit) {
2✔
207
   DTLS_Policy_Options opts;
2✔
208
   opts.max_retransmissions = limit;
2✔
209
   return std::make_shared<DTLS_PSK_Policy>(std::move(opts));
2✔
210
}
2✔
211

212
std::unique_ptr<Botan::TLS::Client> make_dtls_client(const std::shared_ptr<Botan::TLS::Callbacks>& callbacks,
20✔
213
                                                     const std::shared_ptr<Botan::TLS::Session_Manager>& sessions,
214
                                                     const std::shared_ptr<Botan::Credentials_Manager>& creds,
215
                                                     const std::shared_ptr<const Botan::TLS::Policy>& policy,
216
                                                     const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
217
   return std::make_unique<Botan::TLS::Client>(callbacks,
20✔
218
                                               sessions,
219
                                               creds,
220
                                               policy,
221
                                               rng,
222
                                               Botan::TLS::Server_Information("localhost"),
20✔
223
                                               Botan::TLS::Protocol_Version::latest_dtls_version());
60✔
224
}
225

226
std::unique_ptr<Botan::TLS::Server> make_dtls_server(const std::shared_ptr<Botan::TLS::Callbacks>& callbacks,
19✔
227
                                                     const std::shared_ptr<Botan::TLS::Session_Manager>& sessions,
228
                                                     const std::shared_ptr<Botan::Credentials_Manager>& creds,
229
                                                     const std::shared_ptr<const Botan::TLS::Policy>& policy,
230
                                                     const std::shared_ptr<Botan::RandomNumberGenerator>& rng) {
231
   return std::make_unique<Botan::TLS::Server>(callbacks, sessions, creds, policy, rng, true /* is_datagram */);
38✔
232
}
233

234
void deliver(Test::Result& result,
170✔
235
             const std::string& label,
236
             std::vector<uint8_t>& outbound,
237
             Botan::TLS::Channel& peer) {
238
   if(!result.test_is_true(label + " has data", !outbound.empty())) {
170✔
239
      return;
×
240
   }
241

242
   std::vector<uint8_t> input;
170✔
243
   std::swap(input, outbound);
170✔
244
   result.test_no_throw(label, [&] { peer.received_data(input.data(), input.size()); });
510✔
245
}
170✔
246

247
void deliver_copy(Test::Result& result,
13✔
248
                  const std::string& label,
249
                  const std::vector<uint8_t>& outbound,
250
                  Botan::TLS::Channel& peer) {
251
   if(!result.test_is_true(label + " has data", !outbound.empty())) {
13✔
252
      return;
253
   }
254

255
   result.test_no_throw(label, [&] { peer.received_data(outbound.data(), outbound.size()); });
39✔
256
}
257

258
// Hand each waiting buffer to its peer until `done` holds or both run dry.
259
// Bounded so that a regression cannot hang the suite.
260
template <typename Predicate>
261
bool pump_records(std::vector<uint8_t>& c2s,
262
                  Botan::TLS::Channel& server,
263
                  std::vector<uint8_t>& s2c,
264
                  Botan::TLS::Channel& client,
265
                  Predicate done,
266
                  size_t max_rounds = 64) {
267
   for(size_t i = 0; i != max_rounds; ++i) {
268
      if(done()) {
269
         return true;
270
      }
271

272
      if(!c2s.empty()) {
273
         std::vector<uint8_t> in;
274
         std::swap(c2s, in);
275
         server.received_data(in.data(), in.size());
276
      } else if(!s2c.empty()) {
277
         std::vector<uint8_t> in;
278
         std::swap(s2c, in);
279
         client.received_data(in.data(), in.size());
280
      } else {
281
         break;
282
      }
283
   }
284

285
   return done();
286
}
287

288
// One client/server pair plus the buffers carrying records between them. The
289
// buffers are declared ahead of the callbacks that write into them.
290
struct DTLS_Association final {
19✔
291
      // NOLINTBEGIN(misc-non-private-member-variables-in-classes)
292
      std::vector<uint8_t> c2s;
293
      std::vector<uint8_t> s2c;
294
      std::vector<uint8_t> client_recv;
295
      std::vector<uint8_t> server_recv;
296
      std::shared_ptr<DTLS_Test_Callbacks> client_cb;
297
      std::shared_ptr<DTLS_Test_Callbacks> server_cb;
298
      std::shared_ptr<Botan::TLS::Session_Manager> client_sessions;
299
      std::shared_ptr<Botan::TLS::Session_Manager> server_sessions;
300
      std::shared_ptr<DTLS_PSK_Credentials> creds;
301
      // Retained so that a test can attach a further client to this server.
302
      std::shared_ptr<const Botan::TLS::Policy> client_policy;
303
      std::unique_ptr<Botan::TLS::Server> server;
304
      std::unique_ptr<Botan::TLS::Client> client;
305

306
      // NOLINTEND(misc-non-private-member-variables-in-classes)
307

308
      bool both_active() const { return client->is_active() && server->is_active(); }
5✔
309

310
      // Hand one waiting buffer to its peer. Returns false when both are empty.
311
      bool pump_one() {
312
         if(!c2s.empty()) {
313
            std::vector<uint8_t> in;
314
            std::swap(c2s, in);
315
            server->received_data(in.data(), in.size());
316
            return true;
317
         }
318
         if(!s2c.empty()) {
319
            std::vector<uint8_t> in;
320
            std::swap(s2c, in);
321
            client->received_data(in.data(), in.size());
322
            return true;
323
         }
324
         return false;
325
      }
326

327
      template <typename Predicate>
328
      bool pump_until(Predicate done, size_t max_rounds = 64) {
329
         return pump_records(c2s, *server, s2c, *client, done, max_rounds);
330
      }
331

332
      bool pump(size_t max_rounds = 64) {
333
         return pump_until([this] { return both_active(); }, max_rounds);
334
      }
335
};
336

337
// Options for the association builder. `stateless_tickets` selects a server
338
// that issues session tickets rather than session IDs, which is the case the
339
// ServerHello session ID cannot identify.
340
struct DTLS_Association_Options final {
2✔
341
      std::shared_ptr<const Botan::TLS::Policy> policy;
342
      std::shared_ptr<const Botan::TLS::Policy> client_policy;
343
      std::shared_ptr<Botan::TLS::Session_Manager> client_sessions;
344
      bool stateless_tickets = false;
345
      // For tests whose subject is a fatal alert, which otherwise fails the
346
      // result the moment the callbacks see it.
347
      bool tolerate_fatal_alerts = false;
348
};
349

350
std::unique_ptr<DTLS_Association> make_association(Test::Result& result,
19✔
351
                                                   const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
352
                                                   DTLS_Association_Options opts = {}) {
353
   auto assoc = std::make_unique<DTLS_Association>();
19✔
354

355
   auto policy = opts.policy ? opts.policy : std::make_shared<DTLS_PSK_Policy>();
36✔
356
   assoc->client_policy = opts.client_policy ? opts.client_policy : policy;
19✔
357
   assoc->creds = std::make_shared<DTLS_PSK_Credentials>();
19✔
358

359
   if(opts.stateless_tickets) {
19✔
360
      assoc->server_sessions = std::make_shared<Botan::TLS::Session_Manager_Stateless>(assoc->creds, rng);
×
361
   } else {
362
      assoc->server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
19✔
363
   }
364

365
   assoc->client_sessions = opts.client_sessions ? std::move(opts.client_sessions)
38✔
366
                                                 : std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
37✔
367

368
   assoc->server_cb = std::make_shared<DTLS_Test_Callbacks>(result, assoc->s2c, assoc->server_recv);
19✔
369
   assoc->server = make_dtls_server(assoc->server_cb, assoc->server_sessions, assoc->creds, policy, rng);
38✔
370

371
   assoc->client_cb = std::make_shared<DTLS_Test_Callbacks>(result, assoc->c2s, assoc->client_recv);
19✔
372
   assoc->client = make_dtls_client(assoc->client_cb, assoc->client_sessions, assoc->creds, assoc->client_policy, rng);
38✔
373

374
   if(opts.tolerate_fatal_alerts) {
19✔
375
      assoc->server_cb->tolerate_fatal_alerts();
×
376
      assoc->client_cb->tolerate_fatal_alerts();
×
377
   }
378

379
   return assoc;
19✔
380
}
19✔
381

382
// Both endpoints under a policy other than the baseline, which is by far the
383
// most common reason to reach for the options above.
384
std::unique_ptr<DTLS_Association> make_association(Test::Result& result,
2✔
385
                                                   const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
386
                                                   std::shared_ptr<const Botan::TLS::Policy> policy) {
387
   DTLS_Association_Options opts;
2✔
388
   opts.policy = std::move(policy);
2✔
389
   return make_association(result, rng, std::move(opts));
4✔
390
}
2✔
391

392
// The full cookie exchange and handshake, step by step, so that a failure names
393
// the flight it stalled on. Tests that only need an established association can
394
// use DTLS_Association::pump() instead.
395
bool complete_dtls_handshake(Test::Result& result, DTLS_Association& assoc, const std::string& prefix = "") {
5✔
396
   deliver(result, prefix + "client hello 1", assoc.c2s, *assoc.server);
5✔
397
   deliver(result, prefix + "hello verify request", assoc.s2c, *assoc.client);
5✔
398
   deliver(result, prefix + "client hello 2", assoc.c2s, *assoc.server);
5✔
399
   deliver(result, prefix + "server handshake flight", assoc.s2c, *assoc.client);
5✔
400
   deliver(result, prefix + "client final flight", assoc.c2s, *assoc.server);
5✔
401
   deliver(result, prefix + "server final flight", assoc.s2c, *assoc.client);
5✔
402

403
   return result.test_is_true(prefix + "handshake completed", assoc.both_active());
5✔
404
}
405

406
// A DTLS record with a plaintext body, ie one the record layer will either
407
// reject or (at epoch 0) hand straight to the handshake layer.
408
std::vector<uint8_t> unprotected_dtls_record(Botan::TLS::Record_Type type,
3✔
409
                                             uint64_t sequence,
410
                                             std::span<const uint8_t> payload) {
411
   std::vector<uint8_t> record;
3✔
412
   record.reserve(13 + payload.size());
3✔
413

414
   record.push_back(static_cast<uint8_t>(type));
3✔
415
   record.push_back(0xFE);
3✔
416
   record.push_back(0xFD);
3✔
417
   for(size_t i = 0; i != 8; ++i) {
27✔
418
      record.push_back(static_cast<uint8_t>(sequence >> (56 - 8 * i)));
24✔
419
   }
420
   record.push_back(static_cast<uint8_t>(payload.size() >> 8));
3✔
421
   record.push_back(static_cast<uint8_t>(payload.size()));
3✔
422
   record.insert(record.end(), payload.begin(), payload.end());
3✔
423

424
   return record;
3✔
425
}
×
426

427
// A 12-byte DTLS handshake header followed by `fragment`, which may be a piece
428
// of the message or all of it.
429
std::vector<uint8_t> dtls_handshake_fragment(Botan::TLS::Handshake_Type type,
1✔
430
                                             size_t message_length,
431
                                             uint16_t message_sequence,
432
                                             size_t fragment_offset,
433
                                             std::span<const uint8_t> fragment) {
434
   const auto push_be24 = [](std::vector<uint8_t>& out, size_t value) {
4✔
435
      out.push_back(static_cast<uint8_t>((value >> 16) & 0xFF));
3✔
436
      out.push_back(static_cast<uint8_t>((value >> 8) & 0xFF));
3✔
437
      out.push_back(static_cast<uint8_t>(value & 0xFF));
3✔
438
   };
3✔
439

440
   std::vector<uint8_t> msg;
1✔
441
   msg.reserve(12 + fragment.size());
1✔
442

443
   msg.push_back(static_cast<uint8_t>(type));
1✔
444
   push_be24(msg, message_length);
1✔
445
   msg.push_back(static_cast<uint8_t>(message_sequence >> 8));
1✔
446
   msg.push_back(static_cast<uint8_t>(message_sequence));
1✔
447
   push_be24(msg, fragment_offset);
1✔
448
   push_be24(msg, fragment.size());
1✔
449
   msg.insert(msg.end(), fragment.begin(), fragment.end());
1✔
450

451
   return msg;
1✔
452
}
×
453

454
// A record carrying only a handshake header: it declares `message_length` bytes
455
// but delivers none of them.
456
std::vector<uint8_t> empty_dtls_handshake_fragment(Botan::TLS::Handshake_Type type,
1✔
457
                                                   size_t message_length,
458
                                                   uint16_t message_sequence) {
459
   return unprotected_dtls_record(
1✔
460
      Botan::TLS::Record_Type::Handshake, 0, dtls_handshake_fragment(type, message_length, message_sequence, 0, {}));
2✔
461
}
462

463
/*
464
These tests exercise the DTLS core without opening real UDP sockets. The test
465
callbacks collect bytes emitted by TLS::Client/TLS::Server, and deliver() feeds
466
those bytes into the peer's received_data(). Clearing, splitting, copying, or
467
delaying those buffers simulates datagram loss, retransmission, duplicates, and
468
partial flights while still running the real DTLS record, handshake, epoch, and
469
retransmission logic.
470
*/
471
class DTLS_Core_Regression_Tests final : public Test {
1✔
472
   private:
473
      static bool split_first_dtls_record(Test::Result& result,
6✔
474
                                          const std::vector<uint8_t>& records,
475
                                          std::vector<uint8_t>& first_record,
476
                                          std::vector<uint8_t>& remaining_records) {
477
         constexpr size_t dtls_header_len = 13;
6✔
478

479
         if(!result.test_is_true("DTLS records include a complete header", records.size() >= dtls_header_len)) {
6✔
480
            return false;
481
         }
482

483
         const size_t record_len = static_cast<size_t>((static_cast<uint16_t>(records[11]) << 8) | records[12]);
6✔
484
         const size_t total_len = dtls_header_len + record_len;
6✔
485

486
         if(!result.test_is_true("DTLS records include a complete first record", records.size() >= total_len)) {
6✔
487
            return false;
488
         }
489

490
         first_record.assign(records.begin(), records.begin() + total_len);
6✔
491
         remaining_records.assign(records.begin() + total_len, records.end());
6✔
492
         return result.test_is_true("DTLS server flight has more than one record", !remaining_records.empty());
6✔
493
      }
494

495
      // Pack the handshake payloads of two DTLS records into a single record,
496
      // reusing the first record's header. RFC 6347 4.2.3 allows this for
497
      // messages "part of the same flight"; a stale duplicate is not, which is
498
      // the point of the test using it.
499
      static std::vector<uint8_t> coalesce_dtls_records(const std::vector<uint8_t>& first,
500
                                                        const std::vector<uint8_t>& second) {
501
         constexpr size_t dtls_header_len = 13;
502

503
         const size_t first_len = (static_cast<size_t>(first[11]) << 8) | first[12];
504
         const size_t second_len = (static_cast<size_t>(second[11]) << 8) | second[12];
505

506
         std::vector<uint8_t> record(first.begin(), first.begin() + dtls_header_len);
507
         record[11] = static_cast<uint8_t>((first_len + second_len) >> 8);
508
         record[12] = static_cast<uint8_t>(first_len + second_len);
509
         record.insert(record.end(), first.begin() + dtls_header_len, first.begin() + dtls_header_len + first_len);
510
         record.insert(record.end(), second.begin() + dtls_header_len, second.begin() + dtls_header_len + second_len);
511
         return record;
512
      }
513

514
      static std::vector<Botan::TLS::Handshake_Type> dtls_handshake_types(const std::vector<uint8_t>& records) {
11✔
515
         // Lightweight test-only DTLS record inspection. This deliberately
516
         // looks only at unencrypted handshake records, so labels like
517
         // "retransmitted HelloVerifyRequest" are backed by wire data without
518
         // turning the test into a second DTLS implementation.
519
         constexpr size_t dtls_header_len = 13;
11✔
520
         constexpr size_t dtls_handshake_header_len = 12;
11✔
521

522
         std::vector<Botan::TLS::Handshake_Type> types;
11✔
523

524
         size_t offset = 0;
11✔
525
         while(offset + dtls_header_len <= records.size()) {
24✔
526
            const auto record_type = static_cast<Botan::TLS::Record_Type>(records[offset]);
13✔
527
            const size_t record_len =
13✔
528
               static_cast<size_t>((static_cast<uint16_t>(records[offset + 11]) << 8) | records[offset + 12]);
13✔
529
            const size_t record_end = offset + dtls_header_len + record_len;
13✔
530

531
            if(record_end > records.size()) {
13✔
532
               break;
533
            }
534

535
            if(record_type == Botan::TLS::Record_Type::Handshake) {
13✔
536
               size_t hs_offset = offset + dtls_header_len;
537
               while(hs_offset + dtls_handshake_header_len <= record_end) {
26✔
538
                  const auto handshake_type = static_cast<Botan::TLS::Handshake_Type>(records[hs_offset]);
13✔
539
                  const size_t fragment_len = (static_cast<size_t>(records[hs_offset + 9]) << 16) |
13✔
540
                                              (static_cast<size_t>(records[hs_offset + 10]) << 8) |
13✔
541
                                              records[hs_offset + 11];
13✔
542
                  const size_t handshake_end = hs_offset + dtls_handshake_header_len + fragment_len;
13✔
543

544
                  if(handshake_end > record_end) {
13✔
545
                     break;
546
                  }
547

548
                  types.push_back(handshake_type);
13✔
549
                  hs_offset = handshake_end;
13✔
550
               }
551
            }
552

553
            offset = record_end;
554
         }
555

556
         return types;
11✔
557
      }
×
558

559
      static std::vector<Botan::TLS::Record_Type> dtls_record_types(const std::vector<uint8_t>& records) {
6✔
560
         constexpr size_t dtls_header_len = 13;
6✔
561

562
         std::vector<Botan::TLS::Record_Type> types;
6✔
563

564
         size_t offset = 0;
6✔
565
         while(offset + dtls_header_len <= records.size()) {
14✔
566
            const auto record_type = static_cast<Botan::TLS::Record_Type>(records[offset]);
8✔
567
            const size_t record_len =
8✔
568
               static_cast<size_t>((static_cast<uint16_t>(records[offset + 11]) << 8) | records[offset + 12]);
8✔
569
            const size_t record_end = offset + dtls_header_len + record_len;
8✔
570

571
            if(record_end > records.size()) {
8✔
572
               break;
573
            }
574

575
            types.push_back(record_type);
8✔
576
            offset = record_end;
8✔
577
         }
578

579
         return types;
6✔
580
      }
×
581

582
      static bool contains_dtls_handshake_type(const std::vector<uint8_t>& records,
11✔
583
                                               Botan::TLS::Handshake_Type expected) {
584
         for(auto type : dtls_handshake_types(records)) {
13✔
585
            if(type == expected) {
13✔
586
               return true;
11✔
587
            }
588
         }
11✔
589

590
         return false;
×
591
      }
592

593
      static bool contains_dtls_record_type(const std::vector<uint8_t>& records, Botan::TLS::Record_Type expected) {
6✔
594
         for(auto type : dtls_record_types(records)) {
9✔
595
            if(type == expected) {
7✔
596
               return true;
4✔
597
            }
598
         }
6✔
599

600
         return false;
2✔
601
      }
602

603
      // Time comes solely from the synthetic clock, so advancing it by the
604
      // reported remaining time must expire the retransmission timer.
605
      static void fire_retransmission_timer(Test::Result& result,
16✔
606
                                            DTLS_Test_Callbacks& cb,
607
                                            Botan::TLS::Channel& channel,
608
                                            std::vector<uint8_t>& outbound) {
609
         const auto remaining = channel.next_retransmission_timeout();
16✔
610
         if(!remaining.has_value()) {
16✔
611
            result.test_failure("DTLS retransmission timer was not armed");
×
612
            return;
×
613
         }
614

615
         cb.advance_clock_ms(static_cast<uint64_t>(remaining->count()));
16✔
616

617
         if(!channel.timeout_check() || outbound.empty()) {
16✔
618
            result.test_failure("DTLS retransmit was not produced");
×
619
         }
620
      }
621

622
      static Test::Result test_timeout_check_paces_retransmissions() {
1✔
623
         Test::Result result("DTLS timeout_check retransmit pacing");
1✔
624

625
         auto rng = Test::new_shared_rng("dtls-core-timeout-pacing");
1✔
626
         auto assoc = make_association(result, rng);
1✔
627
         auto& client = *assoc->client;
1✔
628
         auto& server = *assoc->server;
1✔
629
         auto& cb = *assoc->client_cb;
1✔
630
         auto& c2s = assoc->c2s;
1✔
631
         auto& s2c = assoc->s2c;
1✔
632

633
         deliver(result, "client hello 1", c2s, server);
1✔
634
         if(!result.test_is_true("hello verify request was produced", !s2c.empty())) {
1✔
635
            return result;
636
         }
637
         s2c.clear();  // simulate losing the HelloVerifyRequest
1✔
638

639
         // The ClientHello went out at synthetic time 0 under a 1 ms initial timeout
640
         result.test_is_false("timer does not fire before the initial timeout", client.timeout_check());
1✔
641
         result.test_is_true("no retransmit before the initial timeout", c2s.empty());
1✔
642

643
         cb.advance_clock_ms(1);
1✔
644
         result.test_is_true("timer fires once the initial timeout elapses", client.timeout_check());
1✔
645
         const auto retransmit_size = c2s.size();
1✔
646
         result.test_is_true("retransmission was produced", retransmit_size > 0);
1✔
647

648
         const auto rearmed = client.next_retransmission_timeout();
1✔
649
         if(result.test_is_true("next retransmission timeout remains available", rearmed.has_value())) {
1✔
650
            // RFC 6347 4.2.4.1: "double the value at each retransmission"
651
            result.test_sz_eq("re-armed timeout doubles the initial timeout", size_t(rearmed->count()), 2);
1✔
652
         }
653

654
         result.test_is_false("immediate second timeout is suppressed", client.timeout_check());
1✔
655
         result.test_sz_eq("no immediate second retransmit", c2s.size(), retransmit_size);
1✔
656

657
         cb.advance_clock_ms(1);
1✔
658
         result.test_is_false("initial interval no longer expires the timer", client.timeout_check());
1✔
659
         result.test_sz_eq("still no second retransmit", c2s.size(), retransmit_size);
1✔
660

661
         cb.advance_clock_ms(1);
1✔
662
         result.test_is_true("timer fires once the doubled timeout elapses", client.timeout_check());
1✔
663
         result.test_sz_eq("second retransmit was produced", c2s.size(), 2 * retransmit_size);
1✔
664

665
         return result;
1✔
666
      }
2✔
667

668
      static Test::Result test_retransmitted_epoch_transition_flight_includes_ccs() {
1✔
669
         Test::Result result("DTLS retransmitted epoch-1 flight includes CCS");
1✔
670

671
         auto rng = Test::new_shared_rng("dtls-core-retransmitted-ccs");
1✔
672
         auto assoc = make_association(result, rng);
1✔
673
         auto& client = *assoc->client;
1✔
674
         auto& server = *assoc->server;
1✔
675
         auto& c2s = assoc->c2s;
1✔
676
         auto& s2c = assoc->s2c;
1✔
677

678
         deliver(result, "client hello 1", c2s, server);
1✔
679
         deliver(result, "hello verify request", s2c, client);
1✔
680
         deliver(result, "client hello 2", c2s, server);
1✔
681
         deliver(result, "server handshake flight", s2c, client);
1✔
682
         deliver(result, "client final flight", c2s, server);
1✔
683

684
         result.test_is_true("server became active", server.is_active());
1✔
685
         if(!result.test_is_true("server final flight was produced", !s2c.empty())) {
1✔
686
            return result;
687
         }
688
         s2c.clear();  // simulate losing the server final flight
1✔
689

690
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
691
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
692

693
         result.test_is_true("retransmitted final flight includes CCS",
1✔
694
                             contains_dtls_record_type(s2c, Botan::TLS::Record_Type::ChangeCipherSpec));
1✔
695
         result.test_is_true("retransmitted final flight includes Finished record",
1✔
696
                             contains_dtls_record_type(s2c, Botan::TLS::Record_Type::Handshake));
1✔
697

698
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
699
         result.test_is_true("client became active", client.is_active());
1✔
700

701
         return result;
1✔
702
      }
2✔
703

704
      static Test::Result test_lost_hello_verify_request_retransmits() {
1✔
705
         Test::Result result("DTLS lost HelloVerifyRequest retransmits");
1✔
706

707
         auto rng = Test::new_shared_rng("dtls-core-lost-hvr");
1✔
708
         auto assoc = make_association(result, rng);
1✔
709
         auto& client = *assoc->client;
1✔
710
         auto& server = *assoc->server;
1✔
711
         auto& c2s = assoc->c2s;
1✔
712
         auto& s2c = assoc->s2c;
1✔
713

714
         deliver(result, "client hello 1", c2s, server);
1✔
715
         if(!result.test_is_true("hello verify request was produced", !s2c.empty())) {
1✔
716
            return result;
717
         }
718
         result.test_is_true("server response is HelloVerifyRequest",
1✔
719
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
720
         result.test_is_false("server does not arm a HelloVerifyRequest retransmission timer",
3✔
721
                              server.next_retransmission_timeout().has_value());
1✔
722
         s2c.clear();  // simulate losing the HelloVerifyRequest
1✔
723

724
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
725
         deliver(result, "client hello 1 retransmit", c2s, server);
1✔
726
         if(!result.test_is_true("server retransmitted hello verify request", !s2c.empty())) {
1✔
727
            return result;
728
         }
729
         result.test_is_true("server retransmission is HelloVerifyRequest",
1✔
730
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
731

732
         deliver(result, "retransmitted hello verify request", s2c, client);
1✔
733
         deliver(result, "client hello 2", c2s, server);
1✔
734
         deliver(result, "server handshake flight", s2c, client);
1✔
735
         deliver(result, "client final flight", c2s, server);
1✔
736
         deliver(result, "server final flight", s2c, client);
1✔
737

738
         result.test_is_true("client became active", client.is_active());
1✔
739
         result.test_is_true("server became active", server.is_active());
1✔
740

741
         return result;
1✔
742
      }
2✔
743

744
      static Test::Result test_duplicate_hello_verify_request_is_tolerated() {
1✔
745
         Test::Result result("DTLS duplicate HelloVerifyRequest is tolerated");
1✔
746

747
         auto rng = Test::new_shared_rng("dtls-core-duplicate-hvr");
1✔
748
         auto assoc = make_association(result, rng);
1✔
749
         auto& client = *assoc->client;
1✔
750
         auto& server = *assoc->server;
1✔
751
         auto& c2s = assoc->c2s;
1✔
752
         auto& s2c = assoc->s2c;
1✔
753

754
         deliver(result, "client hello 1", c2s, server);
1✔
755

756
         const std::vector<uint8_t> hello_verify = s2c;
1✔
757
         if(!result.test_is_true("hello verify request was produced", !hello_verify.empty())) {
1✔
758
            return result;
759
         }
760
         result.test_is_true(
1✔
761
            "server response is HelloVerifyRequest",
762
            contains_dtls_handshake_type(hello_verify, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
763
         s2c.clear();
1✔
764

765
         deliver_copy(result, "hello verify request", hello_verify, client);
1✔
766
         if(!result.test_is_true("client hello 2 was produced", !c2s.empty())) {
1✔
767
            return result;
768
         }
769
         std::vector<uint8_t> client_hello_2;
1✔
770
         std::swap(client_hello_2, c2s);
1✔
771

772
         deliver_copy(result, "duplicate hello verify request", hello_verify, client);
1✔
773
         result.test_is_true("duplicate hello verify request is ignored", c2s.empty());
1✔
774

775
         deliver_copy(result, "client hello 2 after duplicate hvr", client_hello_2, server);
1✔
776
         deliver(result, "server handshake flight", s2c, client);
1✔
777
         deliver(result, "client final flight", c2s, server);
1✔
778
         deliver(result, "server final flight", s2c, client);
1✔
779

780
         result.test_is_true("client became active", client.is_active());
1✔
781
         result.test_is_true("server became active", server.is_active());
1✔
782

783
         return result;
1✔
784
      }
3✔
785

786
      static Test::Result test_partial_server_flight_does_not_advance_client() {
1✔
787
         Test::Result result("DTLS partial server flight waits for ServerHelloDone");
1✔
788

789
         auto rng = Test::new_shared_rng("dtls-core-partial-server-flight");
1✔
790
         auto assoc = make_association(result, rng);
1✔
791
         auto& client = *assoc->client;
1✔
792
         auto& server = *assoc->server;
1✔
793
         auto& c2s = assoc->c2s;
1✔
794
         auto& s2c = assoc->s2c;
1✔
795

796
         deliver(result, "client hello 1", c2s, server);
1✔
797
         deliver(result, "hello verify request", s2c, client);
1✔
798
         deliver(result, "client hello 2", c2s, server);
1✔
799

800
         std::vector<uint8_t> first_record;
1✔
801
         std::vector<uint8_t> remaining_records;
1✔
802
         if(!split_first_dtls_record(result, s2c, first_record, remaining_records)) {
1✔
803
            return result;
804
         }
805
         s2c.clear();
1✔
806

807
         result.test_is_true("partial server flight starts with ServerHello",
1✔
808
                             contains_dtls_handshake_type(first_record, Botan::TLS::Handshake_Type::ServerHello));
1✔
809
         result.test_is_true(
1✔
810
            "partial server flight remainder has ServerHelloDone",
811
            contains_dtls_handshake_type(remaining_records, Botan::TLS::Handshake_Type::ServerHelloDone));
1✔
812

813
         deliver_copy(result, "partial server flight first record", first_record, client);
1✔
814
         result.test_is_false("client waits for the rest of the server flight", client.is_active());
1✔
815
         result.test_is_true("client does not send final flight early", c2s.empty());
1✔
816

817
         deliver_copy(result, "partial server flight remaining records", remaining_records, client);
1✔
818
         if(!result.test_is_true("client final flight was produced after ServerHelloDone", !c2s.empty())) {
1✔
819
            return result;
820
         }
821

822
         deliver(result, "client final flight", c2s, server);
1✔
823
         deliver(result, "server final flight", s2c, client);
1✔
824

825
         result.test_is_true("client became active", client.is_active());
1✔
826
         result.test_is_true("server became active", server.is_active());
1✔
827

828
         return result;
1✔
829
      }
3✔
830

831
      static Test::Result test_lost_server_flight_retransmits() {
1✔
832
         Test::Result result("DTLS lost server flight retransmits");
1✔
833

834
         auto rng = Test::new_shared_rng("dtls-core-lost-server-flight");
1✔
835
         auto assoc = make_association(result, rng);
1✔
836
         auto& client = *assoc->client;
1✔
837
         auto& server = *assoc->server;
1✔
838
         auto& c2s = assoc->c2s;
1✔
839
         auto& s2c = assoc->s2c;
1✔
840

841
         deliver(result, "client hello 1", c2s, server);
1✔
842
         deliver(result, "hello verify request", s2c, client);
1✔
843
         deliver(result, "client hello 2", c2s, server);
1✔
844

845
         if(!result.test_is_true("server handshake flight was produced", !s2c.empty())) {
1✔
846
            return result;
847
         }
848
         s2c.clear();  // simulate losing the server flight
1✔
849

850
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
851
         deliver(result, "client hello 2 retransmit", c2s, server);
1✔
852
         if(!result.test_is_true("server retransmitted handshake flight", !s2c.empty())) {
1✔
853
            return result;
854
         }
855

856
         deliver(result, "retransmitted server handshake flight", s2c, client);
1✔
857
         deliver(result, "client final flight", c2s, server);
1✔
858
         deliver(result, "server final flight", s2c, client);
1✔
859

860
         result.test_is_true("client became active", client.is_active());
1✔
861
         result.test_is_true("server became active", server.is_active());
1✔
862

863
         return result;
1✔
864
      }
2✔
865

866
      // RFC 5246 7.4.1.1 says a HelloRequest is ignored by a client that is
867
      // already negotiating. Answering with a fatal alert instead killed a
868
      // server whose own HelloRequest merely crossed with our renegotiation.
869
      static Test::Result test_hello_request_during_handshake_is_ignored() {
1✔
870
         Test::Result result("DTLS HelloRequest crossing a client renegotiation");
1✔
871

872
         auto rng = Test::new_shared_rng("dtls-core-crossed-hello-request");
1✔
873
         auto assoc = make_association(result, rng);
1✔
874
         auto& client = *assoc->client;
1✔
875
         auto& server = *assoc->server;
1✔
876
         auto& c2s = assoc->c2s;
1✔
877
         auto& s2c = assoc->s2c;
1✔
878

879
         if(!complete_dtls_handshake(result, *assoc)) {
1✔
880
            return result;
881
         }
882

883
         // Both sides ask to renegotiate at once. Hold the client's ClientHello
884
         // back so the two requests genuinely cross.
885
         result.test_no_throw("client starts renegotiation", [&] { client.renegotiate(true); });
2✔
886
         c2s.clear();
1✔
887

888
         result.test_no_throw("server sends its own HelloRequest", [&] { server.renegotiate(false); });
2✔
889
         result.test_is_true("server emitted a HelloRequest", !s2c.empty());
1✔
890

891
         result.test_no_throw("crossed HelloRequest is ignored", [&] {
1✔
892
            std::vector<uint8_t> in;
1✔
893
            std::swap(s2c, in);
1✔
894
            client.received_data(in.data(), in.size());
1✔
895
         });
1✔
896
         result.test_is_true("client produced no response to HelloRequest", c2s.empty());
1✔
897
         result.test_is_true("client remains active", client.is_active());
1✔
898
         result.test_is_false("client did not close", client.is_closed());
1✔
899

900
         return result;
1✔
901
      }
2✔
902

903
      // RFC 6347 4.2.1: "Note to implementors: This may result in clients
904
      // receiving multiple HelloVerifyRequest messages with different cookies.
905
      // Clients SHOULD handle this by sending a new ClientHello with a cookie
906
      // in response to the new HelloVerifyRequest."
907
      static Test::Result test_rotated_cookie_secret_produces_fresh_hello_verify_request() {
1✔
908
         Test::Result result("DTLS fresh HelloVerifyRequest after cookie secret rotation");
1✔
909

910
         auto rng = Test::new_shared_rng("dtls-core-rotated-cookie-secret");
1✔
911
         auto assoc = make_association(result, rng);
1✔
912
         auto& client = *assoc->client;
1✔
913
         auto& server = *assoc->server;
1✔
914
         auto& c2s = assoc->c2s;
1✔
915
         auto& s2c = assoc->s2c;
1✔
916

917
         deliver(result, "client hello 1", c2s, server);
1✔
918
         result.test_is_true("server sent HelloVerifyRequest",
1✔
919
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
920
         deliver(result, "hello verify request", s2c, client);
1✔
921

922
         // The cookie the client is about to present is now signed under a
923
         // secret the server no longer uses.
924
         assoc->creds->rotate_cookie_secret();
1✔
925

926
         deliver(result, "client hello 2 carrying the stale cookie", c2s, server);
1✔
927
         result.test_is_true("server re-challenges with a fresh HelloVerifyRequest",
1✔
928
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
929

930
         deliver(result, "fresh hello verify request", s2c, client);
1✔
931
         if(!result.test_is_true("client answers the fresh HelloVerifyRequest", !c2s.empty())) {
1✔
932
            return result;
933
         }
934

935
         deliver(result, "client hello 3 carrying the rotated cookie", c2s, server);
1✔
936
         deliver(result, "server handshake flight", s2c, client);
1✔
937
         deliver(result, "client final flight", c2s, server);
1✔
938
         deliver(result, "server final flight", s2c, client);
1✔
939

940
         result.test_is_true("client became active", client.is_active());
1✔
941
         result.test_is_true("server became active", server.is_active());
1✔
942

943
         return result;
1✔
944
      }
2✔
945

946
      // Poll the retransmission timer forward until the handshake gives up.
947
      static bool run_out_the_clock(DTLS_Test_Callbacks& cb, Botan::TLS::Channel& channel) {
2✔
948
         for(int i = 0; i < 200; ++i) {
6✔
949
            cb.advance_clock_ms(50);
6✔
950
            try {
6✔
951
               channel.timeout_check();
6✔
952
            } catch(const Botan::TLS::TLS_Exception&) {
2✔
953
               return true;
2✔
954
            }
2✔
955
         }
956
         return false;
957
      }
958

959
      // Reaching the retransmission limit left the expired timer and its IO
960
      // installed, so every later timeout check threw again from unchanged
961
      // state and renegotiate() could never start another attempt.
962
      static Test::Result test_timed_out_initial_handshake_closes_the_channel() {
1✔
963
         Test::Result result("DTLS unanswered handshake closes the channel");
1✔
964

965
         auto rng = Test::new_shared_rng("dtls-core-timeout-abandon");
1✔
966
         auto assoc = make_association(result, rng, dtls_policy_with_max_retransmissions(2));
2✔
967
         auto& client = *assoc->client;
1✔
968

969
         // Nothing is ever delivered to the server, so the client retransmits
970
         // until its budget is spent.
971
         result.test_is_true("initial handshake gave up", run_out_the_clock(*assoc->client_cb, client));
1✔
972
         result.test_is_true("channel is closed", client.is_closed());
1✔
973
         result.test_no_throw("later polls are quiet", [&] { client.timeout_check(); });
2✔
974

975
         return result;
2✔
976
      }
2✔
977

978
      // A renegotiation that dies before any ChangeCipherSpec leaves the
979
      // established association intact, and another attempt can be made.
980
      static Test::Result test_timed_out_renegotiation_keeps_the_association() {
1✔
981
         Test::Result result("DTLS timed out renegotiation keeps the association");
1✔
982

983
         auto rng = Test::new_shared_rng("dtls-core-timeout-abandon-reneg");
1✔
984
         auto assoc = make_association(result, rng, dtls_policy_with_max_retransmissions(2));
2✔
985
         auto& client = *assoc->client;
1✔
986
         auto& c2s = assoc->c2s;
1✔
987

988
         if(!complete_dtls_handshake(result, *assoc)) {
1✔
989
            return result;
990
         }
991

992
         client.renegotiate(true);
1✔
993
         c2s.clear();  // the renegotiation ClientHello never arrives
1✔
994

995
         result.test_is_true("renegotiation gave up", run_out_the_clock(*assoc->client_cb, client));
1✔
996
         result.test_is_true("association survived", client.is_active());
1✔
997
         result.test_is_false("channel not closed", client.is_closed());
1✔
998

999
         result.test_no_throw("another renegotiation can start", [&] { client.renegotiate(true); });
2✔
1000
         result.test_is_true("it emitted a ClientHello", !c2s.empty());
1✔
1001

1002
         return result;
1✔
1003
      }
2✔
1004

1005
      static Test::Result test_duplicate_server_flight_defers_to_timer() {
1✔
1006
         Test::Result result("DTLS duplicate server flight defers replay to timer");
1✔
1007

1008
         auto rng = Test::new_shared_rng("dtls-core-retransmitted-server-flight");
1✔
1009
         auto assoc = make_association(result, rng);
1✔
1010
         auto& client = *assoc->client;
1✔
1011
         auto& server = *assoc->server;
1✔
1012
         auto& c2s = assoc->c2s;
1✔
1013
         auto& s2c = assoc->s2c;
1✔
1014

1015
         deliver(result, "client hello 1", c2s, server);
1✔
1016
         deliver(result, "hello verify request", s2c, client);
1✔
1017
         deliver(result, "client hello 2", c2s, server);
1✔
1018
         deliver(result, "server handshake flight", s2c, client);
1✔
1019
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1020
            return result;
1021
         }
1022
         c2s.clear();  // simulate losing the client's response flight
1✔
1023

1024
         fire_retransmission_timer(result, *assoc->server_cb, server, s2c);
1✔
1025

1026
         std::vector<uint8_t> first_server_record;
1✔
1027
         std::vector<uint8_t> remaining_server_records;
1✔
1028
         if(!split_first_dtls_record(result, s2c, first_server_record, remaining_server_records)) {
1✔
1029
            return result;
1030
         }
1031
         s2c.clear();
1✔
1032

1033
         result.test_is_true(
1✔
1034
            "first retransmitted server record is ServerHello",
1035
            contains_dtls_handshake_type(first_server_record, Botan::TLS::Handshake_Type::ServerHello));
1✔
1036
         result.test_is_true(
1✔
1037
            "remaining retransmitted records include ServerHelloDone",
1038
            contains_dtls_handshake_type(remaining_server_records, Botan::TLS::Handshake_Type::ServerHelloDone));
1✔
1039

1040
         deliver_copy(result, "retransmitted ServerHello record", first_server_record, client);
1✔
1041
         result.test_is_true("non-terminal record does not replay client flight", c2s.empty());
1✔
1042

1043
         deliver_copy(result, "rest of retransmitted server flight", remaining_server_records, client);
1✔
1044
         result.test_is_true("duplicated server flight does not immediately replay client flight", c2s.empty());
1✔
1045

1046
         // A pending handshake deliberately does not take RFC 6347 4.2.4's
1047
         // "read retransmit" exit; see process_previous_handshake_fragment for
1048
         // why reordering makes that unsafe. The flight is still recoverable
1049
         // from the local timer.
1050
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
1051
         result.test_is_true("client timer replays the lost response flight", !c2s.empty());
1✔
1052

1053
         return result;
1✔
1054
      }
3✔
1055

1056
      static Test::Result test_lost_server_final_flight_retransmits() {
1✔
1057
         Test::Result result("DTLS lost server final flight retransmits");
1✔
1058

1059
         auto rng = Test::new_shared_rng("dtls-core-lost-server-final");
1✔
1060
         auto assoc = make_association(result, rng);
1✔
1061
         auto& client = *assoc->client;
1✔
1062
         auto& server = *assoc->server;
1✔
1063
         auto& c2s = assoc->c2s;
1✔
1064
         auto& s2c = assoc->s2c;
1✔
1065

1066
         deliver(result, "client hello 1", c2s, server);
1✔
1067
         deliver(result, "hello verify request", s2c, client);
1✔
1068
         deliver(result, "client hello 2", c2s, server);
1✔
1069
         deliver(result, "server handshake flight", s2c, client);
1✔
1070
         deliver(result, "client final flight", c2s, server);
1✔
1071

1072
         result.test_is_true("server became active", server.is_active());
1✔
1073
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1074
         if(!result.test_is_true("server final flight was produced", !s2c.empty())) {
1✔
1075
            return result;
1076
         }
1077

1078
         std::vector<uint8_t> delayed_ccs;
1✔
1079
         std::vector<uint8_t> delayed_finished;
1✔
1080
         if(!split_first_dtls_record(result, s2c, delayed_ccs, delayed_finished)) {
1✔
1081
            return result;
1082
         }
1083
         s2c.clear();  // simulate delaying the original server final flight
1✔
1084

1085
         result.test_is_false("finished server has no proactive retransmission timer",
3✔
1086
                              server.next_retransmission_timeout().has_value());
1✔
1087
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
1088
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
1089
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
1090

1091
         result.test_is_true("client became active", client.is_active());
1✔
1092
         result.test_is_false("active client has no retransmission timer",
3✔
1093
                              client.next_retransmission_timeout().has_value());
1✔
1094

1095
         // The original Finished may arrive after the retransmitted flight
1096
         // activated the client. It has an unseen DTLS record sequence number
1097
         // but an old handshake message_seq and must be discarded silently.
1098
         deliver(result, "delayed original server Finished", delayed_finished, client);
1✔
1099
         result.test_is_true("client remains active after delayed Finished", client.is_active());
1✔
1100
         result.test_is_true("client does not respond to delayed Finished", c2s.empty());
1✔
1101

1102
         return result;
1✔
1103
      }
3✔
1104

1105
      static Test::Result test_stale_client_hello_does_not_replace_active_handshake() {
1✔
1106
         Test::Result result("DTLS stale ClientHello after server activation");
1✔
1107

1108
         auto rng = Test::new_shared_rng("dtls-core-stale-client-hello");
1✔
1109
         auto assoc = make_association(result, rng);
1✔
1110
         auto& client = *assoc->client;
1✔
1111
         auto& server = *assoc->server;
1✔
1112
         auto& c2s = assoc->c2s;
1✔
1113
         auto& s2c = assoc->s2c;
1✔
1114

1115
         deliver(result, "client hello 1", c2s, server);
1✔
1116
         deliver(result, "hello verify request", s2c, client);
1✔
1117
         const auto cookie_client_hello = c2s;
1✔
1118
         deliver(result, "client hello 2", c2s, server);
1✔
1119
         deliver(result, "server handshake flight", s2c, client);
1✔
1120
         deliver(result, "client final flight", c2s, server);
1✔
1121

1122
         result.test_is_true("server became active", server.is_active());
1✔
1123
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1124
         s2c.clear();  // simulate losing the server final flight
1✔
1125

1126
         // A delayed retransmission of the cookie-bearing ClientHello has
1127
         // message_seq 1. It belongs to the completed handshake, whereas a
1128
         // genuinely new association starts at message_seq 0.
1129
         deliver_copy(result, "stale client hello 2", cookie_client_hello, server);
1✔
1130
         result.test_is_true("server remains active", server.is_active());
1✔
1131
         result.test_is_true("stale ClientHello replays the final server flight", !s2c.empty());
1✔
1132
         s2c.clear();
1✔
1133

1134
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
1135
         deliver(result, "retransmitted client final flight", c2s, server);
1✔
1136
         result.test_is_true("client final flight still receives a response", !s2c.empty());
1✔
1137

1138
         return result;
1✔
1139
      }
2✔
1140

1141
      static Test::Result test_epoch0_client_hello_retransmit_while_restart_pending() {
1✔
1142
         Test::Result result("DTLS epoch-zero ClientHello retransmit while restart pending");
1✔
1143

1144
         auto rng = Test::new_shared_rng("dtls-core-pending-epoch0-restart");
1✔
1145
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1146
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1147
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1148
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_Noop>();
1✔
1149

1150
         std::vector<uint8_t> s2c;
1✔
1151
         std::vector<uint8_t> server_recv;
1✔
1152
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1153
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1154

1155
         std::vector<uint8_t> c1_c2s;
1✔
1156
         std::vector<uint8_t> client1_recv;
1✔
1157
         auto client1_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c1_c2s, client1_recv);
1✔
1158
         Botan::TLS::Client client1(client1_callbacks,
1✔
1159
                                    client_sessions,
1160
                                    creds,
1161
                                    policy,
1162
                                    rng,
1163
                                    Botan::TLS::Server_Information("localhost"),
1✔
1164
                                    Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1165

1166
         deliver(result, "client 1 hello", c1_c2s, server);
1✔
1167
         deliver(result, "client 1 hello verify request", s2c, client1);
1✔
1168
         deliver(result, "client 1 cookie-bearing hello", c1_c2s, server);
1✔
1169
         deliver(result, "client 1 server handshake flight", s2c, client1);
1✔
1170
         deliver(result, "client 1 final flight", c1_c2s, server);
1✔
1171
         deliver(result, "client 1 server final flight", s2c, client1);
1✔
1172

1173
         result.test_is_true("first client became active", client1.is_active());
1✔
1174
         result.test_is_true("server became active for first client", server.is_active());
1✔
1175
         result.test_sz_eq("server established one session", server_callbacks->sessions_established(), 1);
1✔
1176

1177
         std::vector<uint8_t> c2_c2s;
1✔
1178
         std::vector<uint8_t> client2_recv;
1✔
1179
         auto client2_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2_c2s, client2_recv);
1✔
1180
         Botan::TLS::Client client2(client2_callbacks,
1✔
1181
                                    client_sessions,
1182
                                    creds,
1183
                                    policy,
1184
                                    rng,
1185
                                    Botan::TLS::Server_Information("localhost"),
1✔
1186
                                    Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1187

1188
         const auto epoch0_client_hello = c2_c2s;
1✔
1189
         deliver(result, "client 2 epoch-zero hello", c2_c2s, server);
1✔
1190
         const auto hello_verify_request = s2c;
1✔
1191
         const auto cookie_secret_requests = creds->dtls_cookie_secret_requests();
1✔
1192
         s2c.clear();
1✔
1193

1194
         // HelloVerifyRequest is not retained as retransmittable flight data.
1195
         // A repeated initial ClientHello recreates the cookie response.
1196
         deliver_copy(result, "retransmitted client 2 epoch-zero hello", epoch0_client_hello, server);
1✔
1197
         result.test_is_true("pending restart replays HelloVerifyRequest",
1✔
1198
                             contains_dtls_handshake_type(s2c, Botan::TLS::Handshake_Type::HelloVerifyRequest));
1✔
1199
         result.test_sz_eq("retransmitted ClientHello does not establish another session",
1✔
1200
                           server_callbacks->sessions_established(),
1201
                           1);
1202
         result.test_sz_eq("retransmitted ClientHello recreates the cookie response",
1✔
1203
                           creds->dtls_cookie_secret_requests(),
1204
                           cookie_secret_requests + 1);
1205
         result.test_is_true("previous association remains active during restart", server.is_active());
1✔
1206
         s2c.clear();
1✔
1207

1208
         deliver_copy(result, "client 2 hello verify request", hello_verify_request, client2);
1✔
1209
         deliver(result, "client 2 cookie-bearing hello", c2_c2s, server);
1✔
1210
         deliver(result, "client 2 server handshake flight", s2c, client2);
1✔
1211
         deliver(result, "client 2 final flight", c2_c2s, server);
1✔
1212
         deliver(result, "client 2 server final flight", s2c, client2);
1✔
1213

1214
         result.test_is_true("second client became active", client2.is_active());
1✔
1215
         result.test_is_true("server became active for second client", server.is_active());
1✔
1216
         result.test_sz_eq("server established the replacement session", server_callbacks->sessions_established(), 2);
1✔
1217

1218
         return result;
1✔
1219
      }
10✔
1220

1221
      static Test::Result test_retransmitted_final_flight_then_application_data(bool expect_resumption) {
2✔
1222
         Test::Result result(expect_resumption ? "DTLS resumed handshake accepts app data"
2✔
1223
                                               : "DTLS final flight retransmit before app data");
3✔
1224

1225
         auto rng = Test::new_shared_rng(expect_resumption ? "dtls-core-resumption" : "dtls-core-full");
3✔
1226
         auto policy = std::make_shared<DTLS_PSK_Policy>();
2✔
1227
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
2✔
1228
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
2✔
1229
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
2✔
1230

1231
         if(expect_resumption) {
2✔
1232
            run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
5✔
1233
         }
1234

1235
         return run_handshake(result, rng, policy, creds, client_sessions, server_sessions, expect_resumption);
12✔
1236
      }
10✔
1237

1238
      static Test::Result test_resumed_client_final_flight_retransmits_after_activation() {
1✔
1239
         Test::Result result("DTLS resumed client final flight retransmits after activation");
1✔
1240

1241
         auto rng = Test::new_shared_rng("dtls-core-resumed-client-active");
1✔
1242
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1243
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1244
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1245
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1246

1247
         run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
4✔
1248

1249
         std::vector<uint8_t> c2s;
1✔
1250
         std::vector<uint8_t> s2c;
1✔
1251
         std::vector<uint8_t> client_recv;
1✔
1252
         std::vector<uint8_t> server_recv;
1✔
1253

1254
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1255
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
1✔
1256

1257
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1258
         Botan::TLS::Client client(client_callbacks,
1✔
1259
                                   client_sessions,
1260
                                   creds,
1261
                                   policy,
1262
                                   rng,
1263
                                   Botan::TLS::Server_Information("localhost"),
1✔
1264
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1265

1266
         deliver(result, "resumed client hello 1", c2s, server);
1✔
1267
         deliver(result, "resumed hello verify request", s2c, client);
1✔
1268
         deliver(result, "resumed client hello 2", c2s, server);
1✔
1269
         deliver(result, "resumed server handshake flight", s2c, client);
1✔
1270

1271
         result.test_is_true("client is active after resumed server flight", client.is_active());
1✔
1272
         result.test_is_false("server is still waiting for client final flight", server.is_active());
1✔
1273
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1274
            return result;
1275
         }
1276

1277
         std::vector<uint8_t> discarded_ccs;
1✔
1278
         std::vector<uint8_t> delayed_finished;
1✔
1279
         if(!split_first_dtls_record(result, c2s, discarded_ccs, delayed_finished)) {
1✔
1280
            return result;
1281
         }
1282
         c2s.clear();  // simulate delaying the original client final flight
1✔
1283

1284
         result.test_is_false("finished client has no proactive retransmission timer",
3✔
1285
                              client.next_retransmission_timeout().has_value());
1✔
1286
         fire_retransmission_timer(result, *server_callbacks, server, s2c);
1✔
1287
         deliver(result, "retransmitted resumed server flight", s2c, client);
1✔
1288
         deliver(result, "retransmitted resumed client final flight", c2s, server);
1✔
1289

1290
         result.test_is_true("server became active from retransmitted client final flight", server.is_active());
1✔
1291

1292
         // As in the full handshake, a delayed copy of the original terminal
1293
         // Finished must not turn normal DTLS reordering into a fatal alert.
1294
         deliver(result, "delayed original client Finished", delayed_finished, server);
1✔
1295
         result.test_is_true("server remains active after delayed Finished", server.is_active());
1✔
1296
         result.test_is_true("server does not respond to delayed Finished", s2c.empty());
1✔
1297

1298
         const std::vector<uint8_t> app_data = {0xB0, 0x7A, 0x11};
1✔
1299
         result.test_no_throw("server sends application data after completing resumed handshake",
1✔
1300
                              [&] { server.send(app_data); });
2✔
1301
         deliver(result, "server application data", s2c, client);
1✔
1302
         result.test_bin_eq("client received server application data", client_recv, app_data);
1✔
1303

1304
         return result;
1✔
1305
      }
11✔
1306

1307
      static Test::Result test_reordered_retransmitted_final_flight() {
1✔
1308
         Test::Result result("DTLS reordered retransmitted final flight");
1✔
1309

1310
         auto rng = Test::new_shared_rng("dtls-core-reordered-retransmitted-final");
1✔
1311
         auto assoc = make_association(result, rng);
1✔
1312
         auto& client = *assoc->client;
1✔
1313
         auto& server = *assoc->server;
1✔
1314
         auto& c2s = assoc->c2s;
1✔
1315
         auto& s2c = assoc->s2c;
1✔
1316

1317
         deliver(result, "client hello 1", c2s, server);
1✔
1318
         deliver(result, "hello verify request", s2c, client);
1✔
1319
         deliver(result, "client hello 2", c2s, server);
1✔
1320
         deliver(result, "server handshake flight", s2c, client);
1✔
1321
         deliver(result, "client final flight", c2s, server);
1✔
1322

1323
         result.test_is_true("server became active", server.is_active());
1✔
1324
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1325
         s2c.clear();  // simulate losing the server's last flight
1✔
1326

1327
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
1328

1329
         std::vector<uint8_t> client_key_exchange;
1✔
1330
         std::vector<uint8_t> ccs_and_finished;
1✔
1331
         if(!split_first_dtls_record(result, c2s, client_key_exchange, ccs_and_finished)) {
1✔
1332
            return result;
1333
         }
1334

1335
         std::vector<uint8_t> ccs;
1✔
1336
         std::vector<uint8_t> finished;
1✔
1337
         if(!split_first_dtls_record(result, ccs_and_finished, ccs, finished)) {
1✔
1338
            return result;
1339
         }
1340
         c2s.clear();
1✔
1341

1342
         result.test_is_true(
1✔
1343
            "first retransmitted record is ClientKeyExchange",
1344
            contains_dtls_handshake_type(client_key_exchange, Botan::TLS::Handshake_Type::ClientKeyExchange));
1✔
1345
         result.test_is_true("second retransmitted record is CCS",
1✔
1346
                             contains_dtls_record_type(ccs, Botan::TLS::Record_Type::ChangeCipherSpec));
1✔
1347
         result.test_is_true("third retransmitted record is Finished",
1✔
1348
                             contains_dtls_record_type(finished, Botan::TLS::Record_Type::Handshake));
1✔
1349

1350
         deliver_copy(result, "retransmitted ClientKeyExchange", client_key_exchange, server);
1✔
1351
         result.test_is_true("non-terminal retransmitted record produces no response", s2c.empty());
1✔
1352

1353
         deliver_copy(result, "retransmitted Finished before CCS", finished, server);
1✔
1354
         result.test_is_true("partial retransmitted flight produces no response", s2c.empty());
1✔
1355

1356
         deliver_copy(result, "retransmitted CCS after Finished", ccs, server);
1✔
1357
         result.test_is_true("complete reordered flight retransmits server flight", !s2c.empty());
1✔
1358

1359
         return result;
1✔
1360
      }
5✔
1361

1362
      static Test::Result test_renegotiation(bool server_initiated) {
2✔
1363
         Test::Result result(server_initiated ? "DTLS server-initiated renegotiation"
2✔
1364
                                              : "DTLS client-initiated renegotiation");
3✔
1365

1366
         auto rng = Test::new_shared_rng(server_initiated ? "dtls-core-server-renegotiation"
2✔
1367
                                                          : "dtls-core-client-renegotiation");
3✔
1368
         auto assoc = make_association(result, rng);
2✔
1369
         auto& client = *assoc->client;
2✔
1370
         auto& server = *assoc->server;
2✔
1371
         auto& c2s = assoc->c2s;
2✔
1372
         auto& s2c = assoc->s2c;
2✔
1373

1374
         complete_dtls_handshake(result, *assoc);
2✔
1375

1376
         if(server_initiated) {
2✔
1377
            result.test_no_throw("server requests renegotiation", [&] { server.renegotiate(true); });
2✔
1378
            deliver(result, "hello request", s2c, client);
2✔
1379
         } else {
1380
            result.test_no_throw("client requests renegotiation", [&] { client.renegotiate(true); });
2✔
1381
         }
1382

1383
         // A renegotiation ClientHello is already protected under the active
1384
         // epoch. Replaying it must not infer a preceding CCS merely because
1385
         // the cached handshake message has a non-zero epoch.
1386
         c2s.clear();  // simulate losing the renegotiation ClientHello
2✔
1387
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
2✔
1388
         result.test_is_false("retransmitted renegotiation ClientHello does not include CCS",
2✔
1389
                              contains_dtls_record_type(c2s, Botan::TLS::Record_Type::ChangeCipherSpec));
2✔
1390
         deliver(result, "renegotiation client hello", c2s, server);
2✔
1391
         deliver(result, "renegotiation server handshake flight", s2c, client);
2✔
1392
         deliver(result, "renegotiation client final flight", c2s, server);
2✔
1393
         deliver(result, "renegotiation server final flight", s2c, client);
2✔
1394

1395
         result.test_is_true("client remains active after renegotiation", client.is_active());
2✔
1396
         result.test_is_true("server remains active after renegotiation", server.is_active());
2✔
1397
         result.test_is_false("finished client has no proactive retransmission timer",
6✔
1398
                              client.next_retransmission_timeout().has_value());
2✔
1399
         result.test_is_false("finished server has no proactive retransmission timer",
6✔
1400
                              server.next_retransmission_timeout().has_value());
2✔
1401

1402
         return result;
4✔
1403
      }
4✔
1404

1405
      static Test::Result test_empty_old_handshake_fragment_does_not_retransmit() {
1✔
1406
         Test::Result result("DTLS empty old handshake fragment does not retransmit");
1✔
1407

1408
         auto rng = Test::new_shared_rng("dtls-core-empty-old-fragment");
1✔
1409
         auto assoc = make_association(result, rng);
1✔
1410
         auto& client = *assoc->client;
1✔
1411
         auto& server = *assoc->server;
1✔
1412
         auto& s2c = assoc->s2c;
1✔
1413

1414
         complete_dtls_handshake(result, *assoc);
1✔
1415

1416
         result.test_is_true("client became active", client.is_active());
1✔
1417
         result.test_is_true("server became active", server.is_active());
1✔
1418

1419
         const auto empty_fragment = empty_dtls_handshake_fragment(Botan::TLS::Handshake_Type::Finished, 12, 0);
1✔
1420

1421
         result.test_no_throw("empty old fragment is ignored",
1✔
1422
                              [&] { server.received_data(empty_fragment.data(), empty_fragment.size()); });
2✔
1423
         result.test_is_true("server did not retransmit final flight", s2c.empty());
1✔
1424

1425
         return result;
1✔
1426
      }
2✔
1427

1428
      static Test::Result test_spoofed_epoch0_records_do_not_abort_or_poison_retransmission() {
1✔
1429
         Test::Result result("DTLS spoofed epoch 0 records do not abort or poison retransmission");
1✔
1430

1431
         auto rng = Test::new_shared_rng("dtls-core-spoofed-epoch0");
1✔
1432
         auto assoc = make_association(result, rng);
1✔
1433
         auto& client = *assoc->client;
1✔
1434
         auto& server = *assoc->server;
1✔
1435
         auto& c2s = assoc->c2s;
1✔
1436
         auto& s2c = assoc->s2c;
1✔
1437

1438
         deliver(result, "client hello 1", c2s, server);
1✔
1439
         deliver(result, "hello verify request", s2c, client);
1✔
1440
         deliver(result, "client hello 2", c2s, server);
1✔
1441
         deliver(result, "server handshake flight", s2c, client);
1✔
1442
         deliver(result, "client final flight", c2s, server);
1✔
1443

1444
         result.test_is_true("server became active", server.is_active());
1✔
1445
         result.test_is_false("client is waiting for server final flight", client.is_active());
1✔
1446
         s2c.clear();  // simulate losing the server's final flight
1✔
1447

1448
         const std::array<uint8_t, 2> fatal_alert = {2, 40};
1✔
1449
         const auto spoofed_alert =
1✔
1450
            unprotected_dtls_record(Botan::TLS::Record_Type::Alert, 0x0000FFFFFFFFFFFF, fatal_alert);
1✔
1451
         result.test_no_throw("spoofed epoch 0 fatal alert is ignored",
1✔
1452
                              [&] { server.received_data(spoofed_alert.data(), spoofed_alert.size()); });
2✔
1453
         result.test_is_true("server remains active after spoofed alert", server.is_active());
1✔
1454
         result.test_is_true("server does not respond to spoofed alert", s2c.empty());
1✔
1455

1456
         std::array<uint8_t, 12> invalid_handshake = {};
1✔
1457
         invalid_handshake[0] = 0xFF;
1✔
1458
         const auto spoofed_handshake =
1✔
1459
            unprotected_dtls_record(Botan::TLS::Record_Type::Handshake, 0x0000FFFFFFFFFFFE, invalid_handshake);
1✔
1460
         result.test_no_throw("invalid epoch 0 handshake is ignored",
1✔
1461
                              [&] { server.received_data(spoofed_handshake.data(), spoofed_handshake.size()); });
2✔
1462
         result.test_is_true("server remains active after invalid handshake", server.is_active());
1✔
1463
         result.test_is_true("server does not respond to invalid handshake", s2c.empty());
1✔
1464

1465
         fire_retransmission_timer(result, *assoc->client_cb, client, c2s);
1✔
1466
         deliver(result, "genuine client final flight retransmit", c2s, server);
1✔
1467
         result.test_is_true("server retransmits final flight", !s2c.empty());
1✔
1468
         deliver(result, "retransmitted server final flight", s2c, client);
1✔
1469

1470
         result.test_is_true("client became active", client.is_active());
1✔
1471
         result.test_is_true("server remains active", server.is_active());
1✔
1472

1473
         return result;
1✔
1474
      }
3✔
1475

1476
      static Test::Result test_resumed_final_flight_and_app_data_in_one_receive() {
1✔
1477
         Test::Result result("DTLS resumed final flight and app data in one receive");
1✔
1478

1479
         auto rng = Test::new_shared_rng("dtls-core-resumed-final-and-app-data");
1✔
1480
         auto policy = std::make_shared<DTLS_PSK_Policy>();
1✔
1481
         auto creds = std::make_shared<DTLS_PSK_Credentials>();
1✔
1482
         auto client_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1483
         auto server_sessions = std::make_shared<Botan::TLS::Session_Manager_In_Memory>(rng);
1✔
1484

1485
         run_handshake(result, rng, policy, creds, client_sessions, server_sessions, false);
4✔
1486

1487
         std::vector<uint8_t> c2s;
1✔
1488
         std::vector<uint8_t> s2c;
1✔
1489
         std::vector<uint8_t> client_recv;
1✔
1490
         std::vector<uint8_t> server_recv;
1✔
1491

1492
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
1✔
1493
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
1✔
1494

1495
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
4✔
1496
         Botan::TLS::Client client(client_callbacks,
1✔
1497
                                   client_sessions,
1498
                                   creds,
1499
                                   policy,
1500
                                   rng,
1501
                                   Botan::TLS::Server_Information("localhost"),
1✔
1502
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
7✔
1503

1504
         deliver(result, "resumed client hello 1", c2s, server);
1✔
1505
         deliver(result, "resumed hello verify request", s2c, client);
1✔
1506
         deliver(result, "resumed client hello 2", c2s, server);
1✔
1507
         deliver(result, "resumed server handshake flight", s2c, client);
1✔
1508

1509
         result.test_is_true("client is active after resumed server flight", client.is_active());
1✔
1510
         result.test_is_false("server is still waiting for client final flight", server.is_active());
1✔
1511
         if(!result.test_is_true("client final flight was produced", !c2s.empty())) {
1✔
1512
            return result;
1513
         }
1514

1515
         const std::vector<uint8_t> app_data = {0x47, 0x82, 0x01};
1✔
1516
         result.test_no_throw("client sends application data before server processes final flight",
1✔
1517
                              [&] { client.send(app_data); });
2✔
1518

1519
         deliver(result, "resumed client final flight and application data", c2s, server);
1✔
1520

1521
         result.test_is_true("server became active", server.is_active());
1✔
1522
         result.test_bin_eq("server received client application data", server_recv, app_data);
1✔
1523

1524
         return result;
1✔
1525
      }
9✔
1526

1527
      static Test::Result run_handshake(Test::Result& result,
5✔
1528
                                        const std::shared_ptr<Botan::RandomNumberGenerator>& rng,
1529
                                        const std::shared_ptr<Botan::TLS::Policy>& policy,
1530
                                        const std::shared_ptr<Botan::Credentials_Manager>& creds,
1531
                                        const std::shared_ptr<Botan::TLS::Session_Manager>& client_sessions,
1532
                                        const std::shared_ptr<Botan::TLS::Session_Manager>& server_sessions,
1533
                                        bool expect_resumption) {
1534
         std::vector<uint8_t> c2s;
5✔
1535
         std::vector<uint8_t> s2c;
5✔
1536
         std::vector<uint8_t> client_recv;
5✔
1537
         std::vector<uint8_t> server_recv;
5✔
1538

1539
         auto server_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, s2c, server_recv);
5✔
1540
         auto client_callbacks = std::make_shared<DTLS_Test_Callbacks>(result, c2s, client_recv);
5✔
1541

1542
         Botan::TLS::Server server(server_callbacks, server_sessions, creds, policy, rng, true);
10✔
1543
         Botan::TLS::Client client(client_callbacks,
5✔
1544
                                   client_sessions,
1545
                                   creds,
1546
                                   policy,
1547
                                   rng,
1548
                                   Botan::TLS::Server_Information("localhost"),
5✔
1549
                                   Botan::TLS::Protocol_Version::latest_dtls_version());
25✔
1550

1551
         deliver(result, "client hello 1", c2s, server);
5✔
1552
         deliver(result, "hello verify request", s2c, client);
5✔
1553
         deliver(result, "client hello 2", c2s, server);
5✔
1554
         deliver(result, "server handshake flight", s2c, client);
5✔
1555

1556
         // For a full handshake this is the client's final flight. The server's
1557
         // response is intentionally withheld to make the client retransmit it with
1558
         // fresh DTLS record sequence numbers, matching the scenario in #2498.
1559
         deliver(result, "client final flight", c2s, server);
5✔
1560
         result.test_is_true("server became active", server.is_active());
5✔
1561

1562
         if(expect_resumption) {
5✔
1563
            result.test_is_true("client became active during resumed handshake", client.is_active());
1✔
1564
         } else {
1565
            if(!result.test_is_true("server final flight is pending", !s2c.empty())) {
4✔
1566
               return result;
×
1567
            }
1568

1569
            fire_retransmission_timer(result, *client_callbacks, client, c2s);
4✔
1570
            deliver(result, "client final flight retransmit", c2s, server);
4✔
1571

1572
            deliver(result, "server final flight", s2c, client);
8✔
1573
         }
1574

1575
         result.test_is_true("client became active", client.is_active());
5✔
1576

1577
         if(client_callbacks->session_was_resumption().has_value()) {
5✔
1578
            result.test_bool_eq(
10✔
1579
               "client resumption state", client_callbacks->session_was_resumption().value(), expect_resumption);
5✔
1580
         }
1581
         if(server_callbacks->session_was_resumption().has_value()) {
5✔
1582
            result.test_bool_eq(
10✔
1583
               "server resumption state", server_callbacks->session_was_resumption().value(), expect_resumption);
5✔
1584
         }
1585

1586
         const std::vector<uint8_t> app_data = {0xD7, 0x15, 0xA9};
5✔
1587
         result.test_no_throw("client sends application data after retransmitted final flight",
5✔
1588
                              [&] { client.send(app_data); });
10✔
1589
         deliver(result, "application data", c2s, server);
5✔
1590
         result.test_bin_eq("server received application data", server_recv, app_data);
5✔
1591

1592
         return result;
5✔
1593
      }
20✔
1594

1595
   public:
1596
      std::vector<Test::Result> run() override {
1✔
1597
         return {test_timeout_check_paces_retransmissions(),
1✔
1598
                 test_retransmitted_epoch_transition_flight_includes_ccs(),
1599
                 test_lost_hello_verify_request_retransmits(),
1600
                 test_duplicate_hello_verify_request_is_tolerated(),
1601
                 test_rotated_cookie_secret_produces_fresh_hello_verify_request(),
1602
                 test_partial_server_flight_does_not_advance_client(),
1603
                 test_lost_server_flight_retransmits(),
1604
                 test_duplicate_server_flight_defers_to_timer(),
1605
                 test_hello_request_during_handshake_is_ignored(),
1606
                 test_timed_out_initial_handshake_closes_the_channel(),
1607
                 test_timed_out_renegotiation_keeps_the_association(),
1608
                 test_lost_server_final_flight_retransmits(),
1609
                 test_stale_client_hello_does_not_replace_active_handshake(),
1610
                 test_epoch0_client_hello_retransmit_while_restart_pending(),
1611
                 test_reordered_retransmitted_final_flight(),
1612
                 test_empty_old_handshake_fragment_does_not_retransmit(),
1613
                 test_spoofed_epoch0_records_do_not_abort_or_poison_retransmission(),
1614
                 test_retransmitted_final_flight_then_application_data(false),
1615
                 test_retransmitted_final_flight_then_application_data(true),
1616
                 test_resumed_client_final_flight_retransmits_after_activation(),
1617
                 test_resumed_final_flight_and_app_data_in_one_receive(),
1618
                 test_renegotiation(false),
1619
                 test_renegotiation(true)};
24✔
1620
      }
1✔
1621
};
1622

1623
BOTAN_REGISTER_TEST("tls", "tls_dtls_core_regressions", DTLS_Core_Regression_Tests);
1624

1625
// RFC 6347 4.2.8: a client that goes silent may be replaced by a new one
1626
// arriving on the same 5-tuple, which the server has to take up as a fresh
1627
// association rather than as traffic on the old one.
1628
class DTLS_Reconnection_Test : public Test {
1✔
1629
   public:
1630
      std::vector<Test::Result> run() override {
1✔
1631
         Test::Result result("DTLS reconnection");
1✔
1632

1633
         auto rng = Test::new_shared_rng(this->test_name());
1✔
1634

1635
         // Neither client caches sessions, so the second one starts a genuinely
1636
         // new handshake rather than trying to resume.
1637
         DTLS_Association_Options opts;
1✔
1638
         opts.client_sessions = std::make_shared<Botan::TLS::Session_Manager_Noop>();
1✔
1639
         auto assoc = make_association(result, rng, opts);
1✔
1640

1641
         auto& server = *assoc->server;
1✔
1642

1643
         // Run one client to completion against the shared server and exchange a
1644
         // datagram in each direction. Bounded so a regression cannot hang.
1645
         const auto exchange = [&](Botan::TLS::Client& client,
3✔
1646
                                   std::vector<uint8_t>& c2s,
1647
                                   std::vector<uint8_t>& client_recv,
1648
                                   uint8_t to_server_byte,
1649
                                   uint8_t to_client_byte,
1650
                                   const std::string& label) {
1651
            const std::vector<uint8_t> to_server(16, to_server_byte);
2✔
1652
            const std::vector<uint8_t> to_client(16, to_client_byte);
2✔
1653
            bool client_sent = false;
2✔
1654
            bool server_sent = false;
2✔
1655

1656
            for(size_t round = 0; round != 64; ++round) {
22✔
1657
               if(!c2s.empty()) {
22✔
1658
                  std::vector<uint8_t> input;
8✔
1659
                  std::swap(c2s, input);
8✔
1660
                  server.received_data(input.data(), input.size());
8✔
1661
               } else if(!assoc->s2c.empty()) {
22✔
1662
                  std::vector<uint8_t> input;
8✔
1663
                  std::swap(assoc->s2c, input);
8✔
1664
                  client.received_data(input.data(), input.size());
8✔
1665
               } else if(!client_sent && client.is_active()) {
14✔
1666
                  client.send(to_server);
2✔
1667
                  client_sent = true;
2✔
1668
               } else if(!server_sent && server.is_active()) {
4✔
1669
                  server.send(to_client);
2✔
1670
                  server_sent = true;
2✔
1671
               } else if(!assoc->server_recv.empty() && !client_recv.empty()) {
2✔
1672
                  result.test_bin_eq("message from " + label, assoc->server_recv, to_server);
2✔
1673
                  result.test_bin_eq("message to " + label, client_recv, to_client);
2✔
1674
                  return true;
2✔
1675
               } else {
1676
                  break;  // out of input with nothing left to send
1677
               }
1678
            }
1679

1680
            result.test_failure("the " + label + " exchange did not complete");
×
1681
            return false;
×
1682
         };
4✔
1683

1684
         if(!exchange(*assoc->client, assoc->c2s, assoc->client_recv, 0xC1, 0x42, "client1")) {
1✔
1685
            return {result};
×
1686
         }
1687
         result.test_sz_eq("client1 established a session", assoc->client_cb->sessions_established(), size_t(1));
1✔
1688
         result.test_sz_eq("server established a session", assoc->server_cb->sessions_established(), size_t(1));
1✔
1689

1690
         // Now client1 goes silent and a new client connects to the same server
1691
         // context, as happens when a client source port is reused.
1692
         assoc->server_recv.clear();
1✔
1693
         assoc->s2c.clear();
1✔
1694

1695
         std::vector<uint8_t> c2_c2s;
1✔
1696
         std::vector<uint8_t> client2_recv;
1✔
1697
         auto client2_cb = std::make_shared<DTLS_Test_Callbacks>(result, c2_c2s, client2_recv);
1✔
1698
         auto client2 = make_dtls_client(client2_cb, assoc->client_sessions, assoc->creds, assoc->client_policy, rng);
2✔
1699

1700
         if(!exchange(*client2, c2_c2s, client2_recv, 0xC2, 0x66, "client2")) {
1✔
1701
            return {result};
×
1702
         }
1703
         result.test_sz_eq("client2 established a session", client2_cb->sessions_established(), size_t(1));
1✔
1704
         result.test_sz_eq("server established a second session", assoc->server_cb->sessions_established(), size_t(2));
1✔
1705

1706
         return {result};
2✔
1707
      }
5✔
1708
};
1709

1710
BOTAN_REGISTER_TEST("tls", "tls_dtls_reconnect", DTLS_Reconnection_Test);
1711

1712
#endif
1713

1714
}  // namespace
1715

1716
}  // namespace Botan_Tests
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc