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

randombit / botan / 11844561993

14 Nov 2024 07:58PM UTC coverage: 91.178% (+0.1%) from 91.072%
11844561993

Pull #4435

github

web-flow
Merge 81dcb29da into e430f157a
Pull Request #4435: Test duration values ​​are now presented in seconds with six digits of precision. Tests without time measurements have been edited.

91856 of 100744 relevant lines covered (91.18%)

9311006.71 hits per line

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

99.35
/src/tests/test_tls_session_manager.cpp
1
/*
2
* (C) 2022 Jack Lloyd
3
* (C) 2022 René Meusel - Rohde & Schwarz Cybersecurity
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include "tests.h"
9

10
#include <array>
11
#include <chrono>
12
#include <thread>
13

14
#if defined(BOTAN_HAS_TLS)
15

16
   #include <botan/credentials_manager.h>
17
   #include <botan/tls_callbacks.h>
18
   #include <botan/tls_policy.h>
19
   #include <botan/tls_session_manager_hybrid.h>
20
   #include <botan/tls_session_manager_memory.h>
21
   #include <botan/tls_session_manager_stateless.h>
22
   #include <botan/internal/fmt.h>
23

24
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
25
      #include <botan/tls_session_manager_sqlite.h>
26
   #endif
27

28
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM)
29
      #include <version>
30
      #if defined(__cpp_lib_filesystem)
31
         #include <filesystem>
32
      #endif
33
   #endif
34

35
// This file contains a number of `Botan::TLS::Version_Code::TLS_V**` protocol
36
// version specifications. This is to work around a compiler bug in GCC 11.3
37
// where `Botan::TLS::Protocol_Version::TLS_V12` would lead to an "Internal
38
// Compiler Error" when used in the affected context.
39
//
40
// TODO: remove the workaround once GCC 11 is not supported anymore.
41

42
namespace Botan_Tests {
43

44
class Test_Credentials_Manager : public Botan::Credentials_Manager {
7✔
45
   public:
46
      Botan::secure_vector<uint8_t> session_ticket_key() override {
55✔
47
         return Botan::hex_decode_locked("DEADFACECAFEBAAD");
55✔
48
      }
49
};
50

51
class Other_Test_Credentials_Manager : public Botan::Credentials_Manager {
1✔
52
   public:
53
      Botan::secure_vector<uint8_t> session_ticket_key() override {
1✔
54
         return Botan::hex_decode_locked("CAFEBAADFACEBABE");
1✔
55
      }
56
};
57

58
class Empty_Credentials_Manager : public Botan::Credentials_Manager {};
4✔
59

60
class Session_Manager_Callbacks : public Botan::TLS::Callbacks {
3✔
61
   public:
62
      void tls_emit_data(std::span<const uint8_t>) override { BOTAN_ASSERT_NOMSG(false); }
×
63

64
      void tls_record_received(uint64_t, std::span<const uint8_t>) override { BOTAN_ASSERT_NOMSG(false); }
×
65

66
      void tls_alert(Botan::TLS::Alert) override { BOTAN_ASSERT_NOMSG(false); }
×
67

68
      void tls_session_established(const Botan::TLS::Session_Summary&) override { BOTAN_ASSERT_NOMSG(false); }
×
69

70
      std::chrono::system_clock::time_point tls_current_timestamp() override {
225✔
71
         return std::chrono::system_clock::now() + std::chrono::hours(m_ticks);
225✔
72
      }
73

74
      void tick() { ++m_ticks; }
13✔
75

76
   private:
77
      uint64_t m_ticks = 0;
78
};
79

80
class Session_Manager_Policy : public Botan::TLS::Policy {
6✔
81
   public:
82
      std::chrono::seconds session_ticket_lifetime() const override { return std::chrono::minutes(30); }
198✔
83

84
      bool reuse_session_tickets() const override { return m_allow_session_reuse; }
27✔
85

86
      size_t maximum_session_tickets_per_client_hello() const override { return m_session_limit; }
58✔
87

88
      void set_session_limit(size_t l) { m_session_limit = l; }
6✔
89

90
      void set_allow_session_reuse(bool b) { m_allow_session_reuse = b; }
4✔
91

92
   private:
93
      size_t m_session_limit = 1000;  // basically 'no limit'
94
      bool m_allow_session_reuse = true;
95
};
96

97
namespace {
98

99
decltype(auto) random_id(Botan::RandomNumberGenerator& rng) {
60✔
100
   return rng.random_vec<Botan::TLS::Session_ID>(32);
58✔
101
}
102

103
decltype(auto) random_ticket(Botan::RandomNumberGenerator& rng) {
29✔
104
   return rng.random_vec<Botan::TLS::Session_Ticket>(32);
27✔
105
}
106

107
decltype(auto) random_opaque_handle(Botan::RandomNumberGenerator& rng) {
1✔
108
   return rng.random_vec<Botan::TLS::Opaque_Session_Handle>(32);
1✔
109
}
110

111
const Botan::TLS::Server_Information server_info("botan.randombit.net");
112

113
decltype(auto) default_session(Botan::TLS::Connection_Side side,
118✔
114
                               Botan::TLS::Callbacks& cbs,
115
                               Botan::TLS::Protocol_Version version = Botan::TLS::Protocol_Version::TLS_V12) {
116
   if(version.is_pre_tls_13()) {
118✔
117
      return Botan::TLS::Session(
108✔
118
         {}, version, 0x009C, side, true, true, {}, server_info, 0, cbs.tls_current_timestamp());
216✔
119
   } else {
120
   #if defined(BOTAN_HAS_TLS_13)
121
      return Botan::TLS::Session({},
10✔
122
                                 std::nullopt,
123
                                 0,
124
                                 std::chrono::seconds(1024),
10✔
125
                                 Botan::TLS::Protocol_Version::TLS_V13,
126
                                 Botan::TLS::Ciphersuite::from_name("AES_128_GCM_SHA256")->ciphersuite_code(),
10✔
127
                                 side,
128
                                 {},
129
                                 nullptr,
130
                                 server_info,
131
                                 cbs.tls_current_timestamp());
20✔
132
   #else
133
      throw Test_Error("TLS 1.3 is not available in this build");
134
   #endif
135
   }
136
}
137

138
using namespace std::literals;
139

140
std::vector<Test::Result> test_session_manager_in_memory() {
1✔
141
   auto rng = Test::new_shared_rng(__func__);
1✔
142

143
   const Botan::TLS::Session_ID default_id = random_id(*rng);
1✔
144

145
   std::optional<Botan::TLS::Session_Manager_In_Memory> mgr;
1✔
146

147
   Session_Manager_Callbacks cbs;
1✔
148
   Session_Manager_Policy plcy;
1✔
149

150
   return {
1✔
151
      CHECK("creation", [&](auto&) { mgr.emplace(rng, 5); }),
1✔
152

153
      CHECK("empty cache does not obtain anything",
154
            [&](auto& result) {
1✔
155
               result.start_timer();
1✔
156
               result.confirm("no session found via server info", mgr->find(server_info, cbs, plcy).empty());
2✔
157

158
               Botan::TLS::Session_ID mock_id = random_id(*rng);
1✔
159
               auto mock_ticket = rng->random_vec<Botan::TLS::Session_Ticket>(128);
1✔
160

161
               result.confirm("no session found via ID", !mgr->retrieve(mock_id, cbs, plcy));
4✔
162
               result.confirm("no session found via ID", !mgr->retrieve(mock_ticket, cbs, plcy));
4✔
163
               result.end_timer();
1✔
164
            }),
2✔
165

166
      CHECK("clearing empty cache",
167
            [&](auto& result) { 
1✔
168
               result.start_timer();
1✔
169
               result.test_eq("does not delete anything", mgr->remove_all(), 0); 
1✔
170
               result.end_timer();
1✔
171
            }),
1✔
172

173
      CHECK("establish new session",
174
            [&](auto& result) {
1✔
175
               result.start_timer();
1✔
176
               auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
1✔
177
               if(result.confirm("establishment was successful", handle.has_value())) {
2✔
178
                  result.require("session id was set", handle->id().has_value());
2✔
179
                  result.confirm("session ticket was empty", !handle->ticket().has_value());
2✔
180
                  result.test_is_eq("session id is correct", handle->id().value(), default_id);
3✔
181
               }
182
               result.end_timer();
1✔
183
            }),
1✔
184

185
      CHECK("obtain session from server info",
186
            [&](auto& result) {
1✔
187
               result.start_timer();
1✔
188
               auto sessions = mgr->find(server_info, cbs, plcy);
1✔
189
               if(result.confirm("session was found successfully", sessions.size() == 1)) {
2✔
190
                  result.test_is_eq("protocol version was echoed",
1✔
191
                                    sessions[0].session.version(),
1✔
192
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
193
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
1✔
194
                  result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), default_id);
3✔
195
                  result.confirm("not a ticket", !sessions[0].handle.ticket().has_value());
2✔
196
               }
197
               result.end_timer();
1✔
198
            }),
1✔
199

200
      CHECK("obtain session from ID",
201
            [&](auto& result) {
1✔
202
               result.start_timer();
1✔
203
               auto session = mgr->retrieve(default_id, cbs, plcy);
2✔
204
               if(result.confirm("session was found successfully", session.has_value())) {
2✔
205
                  result.test_is_eq("protocol version was echoed",
1✔
206
                                    session->version(),
1✔
207
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
208
                  result.test_is_eq("ciphersuite was echoed", session->ciphersuite_code(), uint16_t(0x009C));
2✔
209
               }
210
               result.end_timer();
1✔
211
            }),
1✔
212

213
      CHECK("obtain session from ID disguised as opaque handle",
214
            [&](auto& result) {
1✔
215
               result.start_timer();
1✔
216
               auto session = mgr->retrieve(Botan::TLS::Opaque_Session_Handle(default_id), cbs, plcy);
2✔
217
               if(result.confirm("session was found successfully", session.has_value())) {
2✔
218
                  result.test_is_eq("protocol version was echoed",
1✔
219
                                    session->version(),
1✔
220
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
221
                  result.test_is_eq("ciphersuite was echoed", session->ciphersuite_code(), uint16_t(0x009C));
2✔
222
               }
223
               result.end_timer();
1✔
224
            }),
1✔
225

226
      CHECK("obtain session from ticket == id does not work",
227
            [&](auto& result) {
1✔
228
               result.start_timer();
1✔
229
               auto session = mgr->retrieve(Botan::TLS::Session_Ticket(default_id), cbs, plcy);
2✔
230
               result.confirm("session was not found", !session.has_value());
2✔
231
               result.end_timer();
1✔
232
            }),
1✔
233

234
      CHECK("invalid ticket causes std::nullopt",
235
            [&](auto& result) {
1✔
236
               result.start_timer();
1✔
237
               auto no_session = mgr->retrieve(random_ticket(*rng), cbs, plcy);
2✔
238
               result.confirm("std::nullopt on bogus ticket", !no_session.has_value());
2✔
239
               result.end_timer();
1✔
240
            }),
1✔
241

242
      CHECK("invalid ID causes std::nullopt",
243
            [&](auto& result) {
1✔
244
               result.start_timer();
1✔
245
               auto no_session = mgr->retrieve(random_id(*rng), cbs, plcy);
2✔
246
               result.confirm("std::nullopt on bogus ID", !no_session.has_value());
2✔
247
               result.end_timer();
1✔
248
            }),
1✔
249

250
      CHECK("remove_all",
251
            [&](auto& result) {
1✔
252
               result.start_timer();
1✔
253
               result.test_eq("removed one element", mgr->remove_all(), 1);
1✔
254
               result.test_eq("should be empty now", mgr->remove_all(), 0);
1✔
255
               result.end_timer();
1✔
256
            }),
1✔
257

258
      CHECK("add session with ID",
259
            [&](auto& result) {
1✔
260
               result.start_timer();
1✔
261
               Botan::TLS::Session_ID new_id = random_id(*rng);
1✔
262

263
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_id);
3✔
264
               result.require("obtain via ID", mgr->retrieve(new_id, cbs, plcy).has_value());
5✔
265

266
               auto sessions = mgr->find(server_info, cbs, plcy);
1✔
267
               if(result.confirm("found via server info", sessions.size() == 1)) {
2✔
268
                  result.test_is_eq("protocol version was echoed",
1✔
269
                                    sessions[0].session.version(),
1✔
270
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
271
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
272
                  result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), new_id);
3✔
273
                  result.confirm("ticket was not stored", !sessions[0].handle.ticket().has_value());
2✔
274
               }
275

276
               mgr->remove_all();
1✔
277
               result.end_timer();
1✔
278
            }),
2✔
279

280
      CHECK("add session with ticket",
281
            [&](auto& result) {
1✔
282
               result.start_timer();
1✔
283
               Botan::TLS::Session_Ticket new_ticket = random_ticket(*rng);
1✔
284

285
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_ticket);
3✔
286
               // cannot be obtained by (non-existent) ID or randomly generated ticket
287

288
               auto sessions = mgr->find(server_info, cbs, plcy);
1✔
289
               if(result.confirm("found via server info", sessions.size() == 1)) {
2✔
290
                  result.test_is_eq("protocol version was echoed",
1✔
291
                                    sessions[0].session.version(),
1✔
292
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
293
                  result.test_is_eq("ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
294
                  result.confirm("ID was not stored", !sessions[0].handle.id().has_value());
2✔
295
                  result.test_is_eq("ticket was echoed", sessions[0].handle.ticket().value(), new_ticket);
3✔
296
               }
297

298
               mgr->remove_all();
1✔
299
               result.end_timer();
1✔
300
            }),
2✔
301

302
      CHECK("removing by ID or opaque handle",
303
            [&](auto& result) {
1✔
304
               result.start_timer();
1✔
305
               Botan::TLS::Session_Manager_In_Memory local_mgr(rng);
1✔
306

307
               const auto new_session1 =
1✔
308
                  local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
309
               const auto new_session2 = local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
310
               result.require(
1✔
311
                  "saving worked",
312
                  new_session1.has_value() && new_session1->id().has_value() && !new_session1->ticket().has_value());
4✔
313
               result.require(
1✔
314
                  "saving worked",
315
                  new_session2.has_value() && new_session2->id().has_value() && !new_session2->ticket().has_value());
4✔
316

317
               result.test_is_eq("can find via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(2));
2✔
318

319
               result.test_is_eq("one was deleted", local_mgr.remove(default_id), size_t(1));
4✔
320
               result.confirm("cannot obtain via default ID anymore",
1✔
321
                              !local_mgr.retrieve(default_id, cbs, plcy).has_value());
4✔
322
               result.test_is_eq(
1✔
323
                  "can find less via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
324

325
               result.test_is_eq("last one was deleted",
2✔
326
                                 local_mgr.remove(Botan::TLS::Opaque_Session_Handle(new_session2->id().value())),
3✔
327
                                 size_t(1));
1✔
328
               result.confirm("cannot obtain via ID anymore",
1✔
329
                              !local_mgr.retrieve(new_session2->id().value(), cbs, plcy).has_value());
4✔
330
               result.confirm("cannot find via server info", local_mgr.find(server_info, cbs, plcy).empty());
2✔
331
               result.end_timer();
1✔
332
            }),
2✔
333

334
      CHECK("removing by ticket or opaque handle",
335
            [&](auto& result) {
1✔
336
               result.start_timer();
1✔
337
               Botan::TLS::Session_Manager_In_Memory local_mgr(rng);
1✔
338

339
               Botan::TLS::Session_Ticket ticket1 = random_ticket(*rng);
1✔
340
               Botan::TLS::Session_Ticket ticket2 = random_ticket(*rng);
1✔
341
               Botan::TLS::Session_Ticket ticket3 = random_ticket(*rng);
1✔
342

343
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket1);
2✔
344
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket2);
2✔
345
               local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket3);
2✔
346
               result.test_is_eq(
1✔
347
                  "can find them via server info ", local_mgr.find(server_info, cbs, plcy).size(), size_t(3));
2✔
348

349
               result.test_is_eq("remove one session by ticket", local_mgr.remove(ticket2), size_t(1));
4✔
350
               result.test_is_eq(
1✔
351
                  "can find two via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(2));
2✔
352

353
               result.test_is_eq("remove one session by opaque handle",
2✔
354
                                 local_mgr.remove(Botan::TLS::Opaque_Session_Handle(ticket3.get())),
2✔
355
                                 size_t(1));
1✔
356
               result.test_is_eq(
1✔
357
                  "can find only one via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
358
               result.end_timer();
1✔
359
            }),
3✔
360

361
      CHECK(
362
         "session purging",
363
         [&](auto& result) {
1✔
364
            result.start_timer();
1✔
365
            result.require("max sessions is 5", mgr->capacity() == 5);
1✔
366

367
            // fill the Session_Manager up fully
368
            std::vector<Botan::TLS::Session_Handle> handles;
1✔
369
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
370
               handles.push_back(
5✔
371
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
15✔
372
            }
373

374
            for(size_t i = 0; i < handles.size(); ++i) {
6✔
375
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
15✔
376
            }
377

378
            // add one more session (causing a first purge to happen)
379
            handles.push_back(
1✔
380
               mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
3✔
381

382
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
2✔
383
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
384
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
15✔
385
            }
386

387
            // remove a session to cause a 'gap' in the FIFO
388
            mgr->remove(handles[4]);
1✔
389
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
2✔
390
            result.confirm("deleted session gone", !mgr->retrieve(handles[4], cbs, plcy).has_value());
2✔
391
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
392
               result.confirm("session still there", i == 4 || mgr->retrieve(handles[i], cbs, plcy).has_value());
14✔
393
            }
394

395
            // insert enough new sessions to fully purge the ones currently held
396
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
397
               handles.push_back(
5✔
398
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng)).value());
15✔
399
            }
400

401
            for(size_t i = 0; i < handles.size() - mgr->capacity(); ++i) {
7✔
402
               result.confirm("session gone", !mgr->retrieve(handles[i], cbs, plcy).has_value());
12✔
403
            }
404

405
            for(size_t i = handles.size() - mgr->capacity(); i < handles.size(); ++i) {
6✔
406
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
15✔
407
            }
408

409
            // clear it all out
410
            result.test_eq("rest of the sessions removed", mgr->remove_all(), size_t(5));
1✔
411
            result.end_timer();
1✔
412
         }),
1✔
413
   };
17✔
414
}
5✔
415

416
std::vector<Test::Result> test_session_manager_choose_ticket() {
1✔
417
   #if defined(BOTAN_HAS_TLS_13)
418
   Session_Manager_Callbacks cbs;
1✔
419
   Session_Manager_Policy plcy;
1✔
420

421
   auto rng = Test::new_shared_rng(__func__);
1✔
422

423
   auto default_session = [&](const std::string& suite,
9✔
424
                              Botan::TLS::Callbacks& mycbs,
425
                              Botan::TLS::Protocol_Version version = Botan::TLS::Protocol_Version::TLS_V13) {
426
      return (version.is_pre_tls_13())
8✔
427
                ? Botan::TLS::Session({},
8✔
428
                                      version,
429
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
1✔
430
                                      Botan::TLS::Connection_Side::Server,
431
                                      true,
432
                                      true,
433
                                      {},
434
                                      server_info,
435
                                      0,
436
                                      mycbs.tls_current_timestamp())
1✔
437
                : Botan::TLS::Session({},
438
                                      std::nullopt,
439
                                      0,
440
                                      std::chrono::seconds(1024),
7✔
441
                                      version,
442
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
7✔
443
                                      Botan::TLS::Connection_Side::Server,
444
                                      {},
445
                                      nullptr,
446
                                      server_info,
447
                                      mycbs.tls_current_timestamp());
23✔
448
   };
449

450
   auto ticket = [&](std::span<const uint8_t> identity) {
16✔
451
      return Botan::TLS::PskIdentity(std::vector(identity.begin(), identity.end()), 0);
15✔
452
   };
453

454
   return {
1✔
455
      CHECK("empty manager has nothing to choose from",
456
            [&](auto& result) {
1✔
457
               result.start_timer();
1✔
458
               Botan::TLS::Session_Manager_In_Memory mgr(rng);
1✔
459

460
               Botan::TLS::Session_Ticket random_session_ticket = random_ticket(*rng);
1✔
461

462
               result.confirm("empty ticket list, no session",
1✔
463
                              !mgr.choose_from_offered_tickets({}, "SHA-256", cbs, plcy).has_value());
2✔
464
               result.confirm(
1✔
465
                  "empty session manager, no session",
466
                  !mgr.choose_from_offered_tickets(std::vector{ticket(random_session_ticket)}, "SHA-256", cbs, plcy)
3✔
467
                      .has_value());
3✔
468
               result.end_timer();
1✔
469
            }),
3✔
470

471
      CHECK("choose ticket by ID",
472
            [&](auto& result) {
1✔
473
               result.start_timer();
1✔
474
               Botan::TLS::Session_Manager_In_Memory mgr(rng);
1✔
475
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
476

477
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
478
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
479

480
               // choose from a list of tickets that contains only handles[0]
481
               auto session1 =
2✔
482
                  mgr.choose_from_offered_tickets(std::vector{ticket(handles[0].id().value())}, "SHA-256", cbs, plcy);
3✔
483
               result.require("ticket was chosen and produced a session (1)", session1.has_value());
1✔
484
               result.test_is_eq("chosen offset", session1->second, uint16_t(0));
1✔
485

486
               // choose from a list of tickets that contains only handles[1]
487
               auto session2 =
2✔
488
                  mgr.choose_from_offered_tickets(std::vector{ticket(handles[1].id().value())}, "SHA-256", cbs, plcy);
3✔
489
               result.require("ticket was chosen and produced a session (2)", session2.has_value());
1✔
490
               result.test_is_eq("chosen offset", session2->second, uint16_t(0));
1✔
491

492
               // choose from a list of tickets that contains a random ticket and handles[1]
493
               auto session3 = mgr.choose_from_offered_tickets(
2✔
494
                  std::vector{ticket(random_ticket(*rng)), ticket(handles[1].id().value())}, "SHA-256", cbs, plcy);
5✔
495
               result.require("ticket was chosen and produced a session (3)", session3.has_value());
1✔
496
               result.test_is_eq("chosen second offset", session3->second, uint16_t(1));
1✔
497
               result.end_timer();
1✔
498
            }),
13✔
499

500
      CHECK("choose ticket by ticket",
501
            [&](auto& result) {
1✔
502
               result.start_timer();
1✔
503
               auto creds = std::make_shared<Test_Credentials_Manager>();
504
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
505
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
506

507
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
508
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
509

510
               // choose from a list of tickets that contains only handles[0]
511
               auto session1 = mgr.choose_from_offered_tickets(
2✔
512
                  std::vector{ticket(handles[0].ticket().value())}, "SHA-256", cbs, plcy);
4✔
513
               result.require("ticket was chosen and produced a session (1)", session1.has_value());
1✔
514
               result.test_is_eq("chosen offset", session1->second, uint16_t(0));
1✔
515

516
               // choose from a list of tickets that contains only handles[1]
517
               auto session2 = mgr.choose_from_offered_tickets(
2✔
518
                  std::vector{ticket(handles[1].ticket().value())}, "SHA-256", cbs, plcy);
4✔
519
               result.require("ticket was chosen and produced a session (2)", session2.has_value());
1✔
520
               result.test_is_eq("chosen offset", session2->second, uint16_t(0));
1✔
521

522
               // choose from a list of tickets that contains a random ticket and handles[1]
523
               auto session3 = mgr.choose_from_offered_tickets(
2✔
524
                  std::vector{ticket(random_ticket(*rng)), ticket(handles[1].ticket().value())}, "SHA-256", cbs, plcy);
5✔
525
               result.require("ticket was chosen and produced a session (3)", session3.has_value());
1✔
526
               result.test_is_eq("chosen second offset", session3->second, uint16_t(1));
1✔
527
               result.end_timer();
1✔
528
            }),
14✔
529

530
      CHECK("choose ticket based on requested hash function",
531
            [&](auto& result) {
1✔
532
               result.start_timer();
1✔
533
               auto creds = std::make_shared<Test_Credentials_Manager>();
534
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
535
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
536

537
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
3✔
538
               handles.push_back(mgr.establish(default_session("AES_256_GCM_SHA384", cbs)).value());
3✔
539

540
               auto session = mgr.choose_from_offered_tickets(std::vector{ticket(random_ticket(*rng)),
5✔
541
                                                                          ticket(handles[0].ticket().value()),
1✔
542
                                                                          ticket(handles[1].ticket().value())},
1✔
543
                                                              "SHA-384",
544
                                                              cbs,
545
                                                              plcy);
546
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
547
               result.test_is_eq("chosen second offset", session->second, uint16_t(2));
1✔
548
               result.end_timer();
1✔
549
            }),
7✔
550

551
      CHECK("choose ticket based on protocol version",
552
            [&](auto& result) {
1✔
553
               result.start_timer();
1✔
554
               auto creds = std::make_shared<Test_Credentials_Manager>();
555
               Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
556
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
557

558
               handles.push_back(
1✔
559
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V12)).value());
3✔
560
               handles.push_back(
1✔
561
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V13)).value());
3✔
562

563
               auto session = mgr.choose_from_offered_tickets(std::vector{ticket(random_ticket(*rng)),
5✔
564
                                                                          ticket(handles[0].ticket().value()),
1✔
565
                                                                          ticket(handles[1].ticket().value())},
1✔
566
                                                              "SHA-256",
567
                                                              cbs,
568
                                                              plcy);
569
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
570
               result.test_is_eq("chosen second offset (TLS 1.3 ticket)", session->second, uint16_t(2));
1✔
571
               result.end_timer();
1✔
572
            }),
7✔
573
   };
7✔
574
   #else
575
   return {};
576
   #endif
577
}
2✔
578

579
std::vector<Test::Result> test_session_manager_stateless() {
1✔
580
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
581

582
   auto rng = Test::new_shared_rng(__func__);
1✔
583

584
   Botan::TLS::Session_Manager_Stateless mgr(creds, rng);
1✔
585

586
   Session_Manager_Callbacks cbs;
1✔
587
   Session_Manager_Policy plcy;
1✔
588

589
   return {
1✔
590
      CHECK("establish with default parameters",
591
            [&](auto& result) {
1✔
592
               result.start_timer();
1✔
593
               result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
594
               auto ticket = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
595
               result.confirm("returned ticket", ticket.has_value() && ticket->is_ticket());
2✔
596
               result.end_timer();
1✔
597
            }),
1✔
598

599
      CHECK("establish with disabled tickets",
600
            [&](auto& result) {
1✔
601
               result.start_timer();
1✔
602
               result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
603
               auto ticket =
1✔
604
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), std::nullopt, true);
1✔
605
               result.confirm("returned std::nullopt", !ticket.has_value());
2✔
606
               result.end_timer();
1✔
607
            }),
1✔
608

609
      CHECK("establish without ticket key in credentials manager",
610
            [&](auto& result) {
1✔
611
               result.start_timer();
1✔
612
               Botan::TLS::Session_Manager_Stateless local_mgr(std::make_shared<Empty_Credentials_Manager>(), rng);
2✔
613

614
               result.confirm("won't emit tickets", !local_mgr.emits_session_tickets());
2✔
615
               auto ticket = local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
616
               result.confirm("returned std::nullopt", !ticket.has_value());
2✔
617
               result.end_timer();
1✔
618
            }),
1✔
619

620
      CHECK("retrieve via ticket",
621
            [&](auto& result) {
1✔
622
               result.start_timer();
1✔
623
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
624
               auto ticket2 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
625
               result.require("tickets created successfully", ticket1.has_value() && ticket2.has_value());
1✔
626

627
               Botan::TLS::Session_Manager_Stateless local_mgr(creds, rng);
1✔
628
               result.confirm("can retrieve ticket 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
629
               result.confirm("can retrieve ticket 2 from different manager but sam credentials",
1✔
630
                              local_mgr.retrieve(ticket2.value(), cbs, plcy).has_value());
1✔
631
               result.end_timer();
1✔
632
            }),
3✔
633

634
      CHECK("retrieve via ID does not work",
635
            [&](auto& result) {
1✔
636
               result.start_timer();
1✔
637
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
638
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
639

640
               result.confirm("retrieval by ID does not work", !mgr.retrieve(random_id(*rng), cbs, plcy).has_value());
3✔
641
               result.end_timer();
1✔
642
            }),
1✔
643

644
      CHECK("retrieve via opaque handle does work",
645
            [&](auto& result) {
1✔
646
               result.start_timer();
1✔
647
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
648
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
649

650
               result.confirm("retrieval by opaque handle",
1✔
651
                              mgr.retrieve(ticket1->opaque_handle(), cbs, plcy).has_value());
3✔
652
               result.end_timer();
1✔
653
            }),
1✔
654

655
      CHECK("no retrieve without or with wrong ticket key",
656
            [&](auto& result) {
1✔
657
               result.start_timer();
1✔
658
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
659
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
660

661
               Botan::TLS::Session_Manager_Stateless local_mgr1(std::make_shared<Empty_Credentials_Manager>(), rng);
2✔
662

663
               Botan::TLS::Session_Manager_Stateless local_mgr2(std::make_shared<Other_Test_Credentials_Manager>(),
2✔
664
                                                                rng);
665

666
               result.confirm("no successful retrieval (without key)",
1✔
667
                              !local_mgr1.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
668
               result.confirm("no successful retrieval (with wrong key)",
1✔
669
                              !local_mgr2.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
670
               result.confirm("successful retrieval", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
671
               result.end_timer();
1✔
672
            }),
2✔
673

674
      CHECK("Clients cannot be stateless",
675
            [&](auto& result) {
1✔
676
               result.start_timer();
1✔
677
               result.test_throws("::store() does not work with ID", [&] {
2✔
678
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id(*rng));
3✔
679
               });
680
               result.test_throws("::store() does not work with ticket", [&] {
2✔
681
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket(*rng));
3✔
682
               });
683
               result.test_throws("::store() does not work with opaque handle", [&] {
2✔
684
                  mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_opaque_handle(*rng));
3✔
685
               });
686

687
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
688
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
689
               result.confirm("finding tickets does not work", mgr.find(server_info, cbs, plcy).empty());
2✔
690
               result.end_timer();
1✔
691
            }),
1✔
692

693
      CHECK("remove is a NOOP",
694
            [&](auto& result) {
1✔
695
               result.start_timer();
1✔
696
               auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
697
               result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
698

699
               result.test_is_eq("remove the ticket", mgr.remove(ticket1.value()), size_t(0));
1✔
700
               result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
701

702
               result.test_is_eq("remove the ticket", mgr.remove_all(), size_t(0));
1✔
703
               result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
2✔
704
               result.end_timer();
1✔
705
            }),
1✔
706

707
      CHECK(
708
         "retrieval via ticket reconstructs the start_time stamp",
709
         [&](auto& result) {
1✔
710
            result.start_timer();
1✔
711
            auto session_before = default_session(Botan::TLS::Connection_Side::Server, cbs);
1✔
712
            auto ticket = mgr.establish(session_before);
2✔
713
            result.require("got a ticket", ticket.has_value() && ticket->is_ticket());
1✔
714
            auto session_after = mgr.retrieve(ticket.value(), cbs, plcy);
1✔
715
            result.require("got the session back", session_after.has_value());
2✔
716

717
            result.test_is_eq(
1✔
718
               "timestamps match",
719
               std::chrono::duration_cast<std::chrono::seconds>(session_before.start_time().time_since_epoch()).count(),
2✔
720
               std::chrono::duration_cast<std::chrono::seconds>(session_after->start_time().time_since_epoch())
1✔
721
                  .count());
1✔
722
            result.end_timer();
1✔
723
         }),
2✔
724
   };
11✔
725
}
4✔
726

727
std::vector<Test::Result> test_session_manager_hybrid() {
1✔
728
   auto rng = Test::new_shared_rng(__func__);
1✔
729

730
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
731
   Session_Manager_Callbacks cbs;
1✔
732
   Session_Manager_Policy plcy;
1✔
733

734
   // Runs the passed-in hybrid manager test lambdas for all available stateful
735
   // managers. The `make_manager()` helper is passed into the test code and
736
   // transparently constructs a hybrid manager with the respective internal
737
   // stateful manager.
738
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
4✔
739
      std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>>
740
         stateful_manager_factories = {
9✔
741
            {"In Memory",
742
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
9✔
743
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10);
6✔
744
             }},
745
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
746
            {"SQLite",
747
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
9✔
748
                return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
749
                   "secure_pw", rng, Test::temp_file_name("tls_session_manager_sqlite"), 10);
6✔
750
             }},
751
   #endif
752
         };
753

754
      std::vector<Test::Result> results;
3✔
755
      using namespace std::placeholders;
756
      for(auto& factory_and_name : stateful_manager_factories) {
15✔
757
         auto& stateful_manager_name = factory_and_name.first;
6✔
758
         auto& stateful_manager_factory = factory_and_name.second;
6✔
759
         auto make_manager = [stateful_manager_factory, &creds, &rng](bool prefer_tickets) {
54✔
760
            return Botan::TLS::Session_Manager_Hybrid(stateful_manager_factory(), creds, rng, prefer_tickets);
48✔
761
         };
762

763
         auto nm = Botan::fmt("{} ({})", name, stateful_manager_name);
6✔
764
         auto fn = std::bind(lambda, make_manager, _1);
6✔
765
         results.push_back(CHECK(nm.c_str(), fn));
18✔
766
      }
767
      return results;
3✔
768
   };
7✔
769

770
   return Test::flatten_result_lists({
3✔
771
      CHECK_all("ticket vs ID preference in establishment",
1✔
772
                [&](auto make_manager, auto& result) {
2✔
773
                  result.start_timer();
2✔
774
                   auto mgr_prefers_tickets = make_manager(true);
2✔
775
                   auto ticket1 =
2✔
776
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
777
                   result.confirm("emits a ticket", ticket1.has_value() && ticket1->is_ticket());
4✔
778

779
                   auto mgr_prefers_ids = make_manager(false);
2✔
780
                   auto ticket2 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
781
                   result.confirm("emits an ID", ticket2.has_value() && ticket2->is_id());
4✔
782

783
                   auto ticket3 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
2✔
784
                                                            std::nullopt,
785
                                                            true /* TLS 1.2 no ticket support */);
786
                   result.confirm("emits an ID", ticket3.has_value() && ticket3->is_id());
4✔
787

788
                   auto ticket4 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
2✔
789
                                                            std::nullopt,
790
                                                            true /* TLS 1.2 no ticket support */);
791
                   result.confirm("emits an ID", ticket4.has_value() && ticket4->is_id());
4✔
792
                   result.end_timer();
2✔
793
                }),
8✔
794

795
      CHECK_all("ticket vs ID preference in retrieval",
1✔
796
                [&](auto make_manager, auto& result) {
2✔
797
                  result.start_timer();
2✔
798
                   auto mgr_prefers_tickets = make_manager(true);
2✔
799
                   auto mgr_prefers_ids = make_manager(false);
2✔
800

801
                   auto id1 = mgr_prefers_tickets.underlying_stateful_manager()->establish(
2✔
802
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
803
                   auto id2 = mgr_prefers_ids.underlying_stateful_manager()->establish(
2✔
804
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
805
                   auto ticket =
2✔
806
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
807

808
                   result.require("establishments worked", id1.has_value() && id2.has_value() && ticket.has_value());
2✔
809

810
                   result.confirm("mgr1 + ID1", mgr_prefers_tickets.retrieve(id1.value(), cbs, plcy).has_value());
4✔
811
                   result.confirm("mgr1 + ID2", !mgr_prefers_tickets.retrieve(id2.value(), cbs, plcy).has_value());
4✔
812
                   result.confirm("mgr2 + ID1", !mgr_prefers_ids.retrieve(id1.value(), cbs, plcy).has_value());
4✔
813
                   result.confirm("mgr2 + ID2", mgr_prefers_ids.retrieve(id2.value(), cbs, plcy).has_value());
4✔
814
                   result.confirm("mgr1 + ticket", mgr_prefers_tickets.retrieve(ticket.value(), cbs, plcy).has_value());
4✔
815
                   result.confirm("mgr2 + ticket", mgr_prefers_ids.retrieve(ticket.value(), cbs, plcy).has_value());
4✔
816
                  result.end_timer();
2✔
817
                }),
6✔
818

819
      CHECK_all("no session tickets if hybrid manager cannot create them",
1✔
820
                [&](auto make_manager, auto& result) {
2✔
821
                  result.start_timer();
2✔
822
                   Botan::TLS::Session_Manager_Hybrid empty_mgr(
8✔
823
                      std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10),
4✔
824
                      std::make_shared<Empty_Credentials_Manager>(),
825
                      rng);
826
                   auto mgr_prefers_tickets = make_manager(true);
2✔
827
                   auto mgr_prefers_ids = make_manager(false);
2✔
828

829
                   result.confirm("does not emit tickets", !empty_mgr.emits_session_tickets());
4✔
830
                   result.confirm("does emit tickets 1", mgr_prefers_tickets.emits_session_tickets());
4✔
831
                   result.confirm("does emit tickets 2", mgr_prefers_ids.emits_session_tickets());
4✔
832
                  result.end_timer();
2✔
833
                }),
2✔
834
   });
4✔
835
}
4✔
836

837
namespace {
838

839
class Temporary_Database_File {
840
   private:
841
      std::string m_temp_file;
842

843
   public:
844
      Temporary_Database_File(const std::string& db_file) : m_temp_file(Test::data_file_as_temporary_copy(db_file)) {
1✔
845
         if(m_temp_file.empty()) {
1✔
846
            throw Test_Error("Failed to create temporary database file");
×
847
         }
848
      }
1✔
849

850
      ~Temporary_Database_File() {
1✔
851
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
852
         if(!m_temp_file.empty()) {
1✔
853
            std::filesystem::remove(m_temp_file);
1✔
854
         }
855
   #endif
856
      }
1✔
857

858
      const std::string& get() const { return m_temp_file; }
859

860
      Temporary_Database_File(const Temporary_Database_File&) = delete;
861
      Temporary_Database_File& operator=(const Temporary_Database_File&) = delete;
862
      Temporary_Database_File(Temporary_Database_File&&) = delete;
863
      Temporary_Database_File& operator=(Temporary_Database_File&&) = delete;
864
};
865

866
}  // namespace
867

868
std::vector<Test::Result> test_session_manager_sqlite() {
1✔
869
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
870
   auto rng = Test::new_shared_rng(__func__);
1✔
871
   Session_Manager_Callbacks cbs;
1✔
872
   Session_Manager_Policy plcy;
1✔
873

874
   return {
1✔
875
      CHECK("migrate session database scheme (purges database)",
876
            [&](auto& result) {
1✔
877
               result.start_timer();
1✔
878
               Temporary_Database_File dbfile("tls-sessions/botan-2.19.3.sqlite");
1✔
879

880
               // legacy database (encrypted with 'thetruthisoutthere') containing:
881
               //    $ sqlite3 src/tests/data/tls-sessions/botan-2.19.3.sqlite  'SELECT * FROM tls_sessions;'
882
               //    63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714|1673606906|cloudflare.com|443|...
883
               //    63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB|1673606915|randombit.net|443|...
884
               Botan::TLS::Session_Manager_SQLite legacy_db("thetruthisoutthere", rng, dbfile.get());
1✔
885

886
               result.confirm("Session_ID for randombit.net is gone",
1✔
887
                              !legacy_db
888
                                  .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
889
                                               "63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB")),
890
                                            cbs,
891
                                            plcy)
892
                                  .has_value());
3✔
893
               result.confirm("Session_ID for cloudflare.com is gone",
1✔
894
                              !legacy_db
895
                                  .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
896
                                               "63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714")),
897
                                            cbs,
898
                                            plcy)
899
                                  .has_value());
3✔
900
               result.confirm("no more session for randombit.net",
1✔
901
                              legacy_db.find(Botan::TLS::Server_Information("randombit.net", 443), cbs, plcy).empty());
2✔
902
               result.confirm("no more session for cloudflare.com",
1✔
903
                              legacy_db.find(Botan::TLS::Server_Information("cloudflare.com", 443), cbs, plcy).empty());
2✔
904

905
               result.test_is_eq("empty database won't get more empty", legacy_db.remove_all(), size_t(0));
1✔
906
               result.end_timer();
1✔
907
            }),
1✔
908

909
      CHECK("clearing empty database",
910
            [&](auto& result) {
1✔
911
               result.start_timer();
1✔
912
               Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere", rng, Test::temp_file_name("empty.sqlite"));
1✔
913
               result.test_eq("does not delete anything", mgr.remove_all(), 0);
1✔
914
               result.end_timer();
1✔
915
            }),
1✔
916

917
      CHECK("establish new session",
918
            [&](auto& result) {
1✔
919
               result.start_timer();
1✔
920
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
921
                  "thetruthisoutthere", rng, Test::temp_file_name("new_session.sqlite"));
1✔
922
               auto some_random_id = random_id(*rng);
1✔
923
               auto some_random_handle =
1✔
924
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
925
               result.require("establishment was successful", some_random_handle.has_value());
2✔
926
               result.require("session id was set", some_random_handle->id().has_value());
2✔
927
               result.test_is_eq("session id is correct", some_random_handle->id().value(), some_random_id);
3✔
928

929
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
930
               result.require("establishment was successful", some_virtual_handle.has_value());
2✔
931
               result.require("session id was set", some_virtual_handle->id().has_value());
2✔
932
               result.end_timer();
1✔
933
            }),
3✔
934

935
      CHECK("retrieve session by ID",
936
            [&](auto& result) {
1✔
937
               result.start_timer();
1✔
938
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
939
                  "thetruthisoutthere", rng, Test::temp_file_name("retrieve_by_id.sqlite"));
1✔
940
               auto some_random_id = random_id(*rng);
1✔
941
               auto some_random_handle =
1✔
942
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
943
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
944

945
               result.require("establishment was successful",
1✔
946
                              some_random_handle->is_id() && some_virtual_handle->is_id());
1✔
947

948
               auto session1 = mgr.retrieve(some_random_handle.value(), cbs, plcy);
1✔
949
               if(result.confirm("found session by user-provided ID", session1.has_value())) {
2✔
950
                  result.test_is_eq("protocol version was echoed",
1✔
951
                                    session1->version(),
1✔
952
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
953
                  result.test_is_eq("ciphersuite was echoed", session1->ciphersuite_code(), uint16_t(0x009C));
2✔
954
               }
955

956
               auto session2 = mgr.retrieve(some_virtual_handle.value(), cbs, plcy);
1✔
957
               if(result.confirm("found session by manager-generated ID", session2.has_value())) {
2✔
958
                  result.test_is_eq("protocol version was echoed",
1✔
959
                                    session2->version(),
1✔
960
                                    Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
961
                  result.test_is_eq("ciphersuite was echoed", session2->ciphersuite_code(), uint16_t(0x009C));
2✔
962
               }
963

964
               auto session3 = mgr.retrieve(random_id(*rng), cbs, plcy);
2✔
965
               result.confirm("random ID creates empty result", !session3.has_value());
2✔
966
               result.end_timer();
1✔
967
            }),
6✔
968

969
      CHECK("retrieval via ticket creates empty result",
970
            [&](auto& result) {
1✔
971
               result.start_timer();
1✔
972
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
973
                  "thetruthisoutthere", rng, Test::temp_file_name("retrieve_by_ticket.sqlite"));
1✔
974
               auto some_random_handle =
1✔
975
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng));
1✔
976
               auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
977

978
               result.confirm("std::nullopt on random ticket",
1✔
979
                              !mgr.retrieve(random_ticket(*rng), cbs, plcy).has_value());
3✔
980
               result.end_timer();
1✔
981
            }),
2✔
982

983
      CHECK("storing sessions and finding them by server info",
984
            [&](auto& result) {
1✔
985
               result.start_timer();
1✔
986
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
987
                  "thetruthisoutthere", rng, Test::temp_file_name("store_and_find.sqlite"));
1✔
988
               auto id = random_id(*rng);
1✔
989
               auto ticket = random_ticket(*rng);
1✔
990
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
991
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
992

993
               auto found_sessions = mgr.find(server_info, cbs, plcy);
1✔
994
               if(result.test_is_eq("found both sessions", found_sessions.size(), size_t(2))) {
1✔
995
                  for(const auto& [session, handle] : found_sessions) {
3✔
996
                     result.confirm("ID matches", !handle.is_id() || handle.id().value() == id);
5✔
997
                     result.confirm("ticket matches", !handle.is_ticket() || handle.ticket().value() == ticket);
5✔
998
                  }
999
               }
1000
               result.end_timer();
1✔
1001
            }),
3✔
1002

1003
      CHECK("removing sessions",
1004
            [&](auto& result) {
1✔
1005
               result.start_timer();
1✔
1006
               Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere", rng, Test::temp_file_name("remove.sqlite"));
1✔
1007
               auto id = random_id(*rng);
1✔
1008
               auto ticket = random_ticket(*rng);
1✔
1009
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
1010
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
1011
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id(*rng));
2✔
1012
               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket(*rng));
2✔
1013

1014
               result.test_is_eq("deletes one session by ID", mgr.remove(id), size_t(1));
4✔
1015
               result.test_is_eq("deletes one session by ticket", mgr.remove(ticket), size_t(1));
4✔
1016

1017
               auto found_sessions = mgr.find(server_info, cbs, plcy);
1✔
1018
               if(result.test_is_eq("found some other sessions", found_sessions.size(), size_t(2))) {
1✔
1019
                  for(const auto& [session, handle] : found_sessions) {
3✔
1020
                     result.confirm("ID does not match", !handle.is_id() || handle.id().value() != id);
6✔
1021
                     result.confirm("ticket does not match", !handle.is_ticket() || handle.ticket().value() != ticket);
6✔
1022
                  }
1023
               }
1024

1025
               result.test_is_eq("removing the rest of the sessions", mgr.remove_all(), size_t(2));
1✔
1026
               result.end_timer();
1✔
1027
            }),
3✔
1028

1029
      CHECK("old sessions are purged when needed",
1030
            [&](auto& result) {
1✔
1031
               result.start_timer();
1✔
1032
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
1033
                  "thetruthisoutthere", rng, Test::temp_file_name("purging.sqlite"), 1);
1✔
1034

1035
               std::vector<Botan::TLS::Session_ID> ids = {random_id(*rng), random_id(*rng), random_id(*rng)};
5✔
1036
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[0]);
2✔
1037
               result.require("new ID exists", mgr.retrieve(ids[0], cbs, plcy).has_value());
5✔
1038

1039
               // Session timestamps are saved with second-resolution. If more than
1040
               // one session has the same (coarse) timestamp it is undefined which
1041
               // will be purged first. The clock tick ensures that session's
1042
               // timestamps are unique.
1043
               cbs.tick();
1✔
1044
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[1]);
2✔
1045
               result.require("first ID is gone", !mgr.retrieve(ids[0], cbs, plcy).has_value());
4✔
1046
               result.require("new ID exists", mgr.retrieve(ids[1], cbs, plcy).has_value());
5✔
1047

1048
               cbs.tick();
1✔
1049
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[2]);
2✔
1050
               result.require("second ID is gone", !mgr.retrieve(ids[1], cbs, plcy).has_value());
4✔
1051
               result.require("new ID exists", mgr.retrieve(ids[2], cbs, plcy).has_value());
5✔
1052

1053
               result.test_is_eq("only one entry exists", mgr.remove_all(), size_t(1));
1✔
1054
               result.end_timer();
1✔
1055
            }),
2✔
1056

1057
      CHECK("session purging can be disabled",
1058
            [&](auto& result) {
1✔
1059
               result.start_timer();
1✔
1060
               Botan::TLS::Session_Manager_SQLite mgr(
1✔
1061
                  "thetruthisoutthere", rng, Test::temp_file_name("purging.sqlite"), 0 /* no pruning! */);
1✔
1062

1063
               for(size_t i = 0; i < 25; ++i) {
26✔
1064
                  mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id(*rng));
50✔
1065
               }
1066

1067
               result.test_is_eq("no entries were purged along the way", mgr.remove_all(), size_t(25));
1✔
1068
               result.end_timer();
1✔
1069
            }),
1✔
1070
   };
10✔
1071
   #else
1072
   return {};
1073
   #endif
1074
}
3✔
1075

1076
std::vector<Test::Result> tls_session_manager_expiry() {
1✔
1077
   auto rng = Test::new_shared_rng(__func__);
1✔
1078
   Session_Manager_Callbacks cbs;
1✔
1079
   Session_Manager_Policy plcy;
1✔
1080

1081
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
6✔
1082
      std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>>
1083
         stateful_manager_factories = {
20✔
1084
            {"In Memory",
1085
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
10✔
1086
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(rng, 10);
5✔
1087
             }},
1088
            {"Stateless",
1089
             [&]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
7✔
1090
                return std::make_unique<Botan::TLS::Session_Manager_Stateless>(
1091
                   std::make_shared<Test_Credentials_Manager>(), rng);
2✔
1092
             }},
1093
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
1094
            {"SQLite",
1095
             [&rng]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
10✔
1096
                return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
1097
                   "secure_pw", rng, Test::temp_file_name("tls_session_manager_sqlite"), 10);
5✔
1098
             }},
1099
   #endif
1100
         };
1101

1102
      std::vector<Test::Result> results;
5✔
1103
      results.reserve(stateful_manager_factories.size());
5✔
1104
      using namespace std::placeholders;
1105
      for(auto& [sub_name, factory] : stateful_manager_factories) {
20✔
1106
         auto nm = Botan::fmt("{} ({})", name, sub_name);
15✔
1107
         auto fn = std::bind(lambda, sub_name, factory, _1);
15✔
1108
         results.push_back(CHECK(nm.c_str(), fn));
30✔
1109
      }
1110
      return results;
5✔
1111
   };
11✔
1112

1113
   return Test::flatten_result_lists({
5✔
1114
      CHECK_all("sessions expire",
1✔
1115
                [&](auto, auto factory, auto& result) {
3✔
1116
                   result.start_timer();
3✔
1117
                   auto mgr = factory();
3✔
1118

1119
                   auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
3✔
1120
                   result.require("saved successfully", handle.has_value());
6✔
1121
                   result.require("session was found", mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1122
                   cbs.tick();
3✔
1123
                   result.confirm("session has expired", !mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1124
                   result.test_is_eq("session was deleted when it expired", mgr->remove_all(), size_t(0));
3✔
1125
                   result.end_timer();
3✔
1126
                }),
6✔
1127

1128
         CHECK_all("expired sessions are not found",
1✔
1129
                   [&](const std::string& type, auto factory, auto& result) {
3✔
1130
                      result.start_timer();
3✔
1131
                      if(type == "Stateless") {
3✔
1132
                         return;  // this manager can neither store nor find anything
1✔
1133
                      }
1134

1135
                      auto mgr = factory();
2✔
1136

1137
                      auto handle_old = random_id(*rng);
2✔
1138
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_old);
6✔
1139
                      result.require("session was found", mgr->retrieve(handle_old, cbs, plcy).has_value());
10✔
1140

1141
                      cbs.tick();
2✔
1142
                      auto handle_new = random_id(*rng);
2✔
1143
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_new);
6✔
1144
                      result.require("session was found", mgr->retrieve(handle_new, cbs, plcy).has_value());
10✔
1145

1146
                      auto sessions_and_handles = mgr->find(server_info, cbs, plcy);
2✔
1147
                      result.require("sessions are found", !sessions_and_handles.empty());
2✔
1148
                      result.test_is_eq("exactly one session is found", sessions_and_handles.size(), size_t(1));
4✔
1149
                      result.test_is_eq(
4✔
1150
                         "the new session is found", sessions_and_handles.front().handle.id().value(), handle_new);
4✔
1151

1152
                      result.test_is_eq("old session was deleted when it expired", mgr->remove_all(), size_t(1));
2✔
1153
                     result.end_timer();
2✔
1154
                   }),
8✔
1155

1156
         CHECK_all(
1✔
1157
            "session tickets are not reused",
1158
            [&](const std::string& type, auto factory, auto& result) {
3✔
1159
               result.start_timer();
3✔
1160
               if(type == "Stateless") {
3✔
1161
                  return;  // this manager can neither store nor find anything
1✔
1162
               }
1163

1164
               auto mgr = factory();
2✔
1165

1166
               auto handle_1 = random_id(*rng);
2✔
1167
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1168
                          handle_1);
1169
               auto handle_2 = random_ticket(*rng);
2✔
1170
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1171
                          handle_2);
1172

1173
   #if defined(BOTAN_HAS_TLS_13)
1174
               auto handle_3 = random_id(*rng);
2✔
1175
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1176
                          handle_3);
1177
               auto handle_4 = random_ticket(*rng);
2✔
1178
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1179
                          handle_4);
1180
   #endif
1181

1182
               plcy.set_allow_session_reuse(false);
2✔
1183

1184
               auto sessions_and_handles1 = mgr->find(server_info, cbs, plcy);
2✔
1185
               result.require("all sessions are found", sessions_and_handles1.size() > 1);
4✔
1186

1187
               auto sessions_and_handles2 = mgr->find(server_info, cbs, plcy);
2✔
1188
               result.test_is_eq("only one session is found", sessions_and_handles2.size(), size_t(1));
4✔
1189
               result.confirm("found session is the Session_ID", sessions_and_handles2.front().handle.is_id());
4✔
1190
               result.test_is_eq(
4✔
1191
                  "found session is the Session_ID", sessions_and_handles2.front().handle.id().value(), handle_1);
6✔
1192
               result.confirm("found session is TLS 1.2",
4✔
1193
                              sessions_and_handles2.front().session.version().is_pre_tls_13());
2✔
1194
               result.end_timer();
2✔
1195
            }),
12✔
1196

1197
         CHECK_all("number of found tickets is capped",
1✔
1198
                   [&](const std::string& type, auto factory, auto& result) {
3✔
1199
                     result.start_timer();
3✔
1200
                      if(type == "Stateless") {
3✔
1201
                         return;  // this manager can neither store nor find anything
1✔
1202
                      }
1203

1204
                      auto mgr = factory();
2✔
1205

1206
                      std::array<Botan::TLS::Session_Ticket, 5> tickets;
2✔
1207
                      for(auto& ticket : tickets) {
12✔
1208
                         ticket = random_ticket(*rng);
10✔
1209
                         mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
30✔
1210
                      }
1211

1212
                      plcy.set_allow_session_reuse(true);
2✔
1213

1214
                      plcy.set_session_limit(1);
2✔
1215
                      result.test_is_eq("find one",
2✔
1216
                                        mgr->find(server_info, cbs, plcy).size(),
4✔
1217
                                        plcy.maximum_session_tickets_per_client_hello());
2✔
1218

1219
                      plcy.set_session_limit(3);
2✔
1220
                      result.test_is_eq("find three",
2✔
1221
                                        mgr->find(server_info, cbs, plcy).size(),
4✔
1222
                                        plcy.maximum_session_tickets_per_client_hello());
2✔
1223

1224
                      plcy.set_session_limit(10);
2✔
1225
                      result.test_is_eq("find all five", mgr->find(server_info, cbs, plcy).size(), size_t(5));
4✔
1226
                     result.end_timer();
2✔
1227
                   }),
4✔
1228

1229
   #if defined(BOTAN_HAS_TLS_13)
1230
         CHECK_all("expired tickets are not selected for PSK resumption", [&](auto, auto factory, auto& result) {
4✔
1231
            result.start_timer();
3✔
1232
            auto ticket = [&](const Botan::TLS::Session_Handle& handle) {
12✔
1233
               return Botan::TLS::PskIdentity(handle.opaque_handle().get(), 0);
12✔
1234
            };
1235

1236
            auto mgr = factory();
3✔
1237

1238
            auto old_handle = mgr->establish(
3✔
1239
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1240
            cbs.tick();
3✔
1241
            auto new_handle = mgr->establish(
3✔
1242
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1243
            result.require("both sessions are stored", old_handle.has_value() && new_handle.has_value());
6✔
1244

1245
            auto session_and_index = mgr->choose_from_offered_tickets(
12✔
1246
               std::vector{ticket(old_handle.value()), ticket(new_handle.value())}, "SHA-256", cbs, plcy);
6✔
1247
            result.require("a ticket was chosen", session_and_index.has_value());
3✔
1248
            result.test_is_eq("the new ticket was chosen", session_and_index->second, uint16_t(1));
3✔
1249

1250
            cbs.tick();
3✔
1251

1252
            auto nothing = mgr->choose_from_offered_tickets(
12✔
1253
               std::vector{ticket(new_handle.value()), ticket(old_handle.value())}, "SHA-256", cbs, plcy);
6✔
1254
            result.require("all tickets are expired", !nothing.has_value());
3✔
1255
            result.end_timer();
3✔
1256
         }),
33✔
1257
   #endif
1258
   });
6✔
1259
}
3✔
1260

1261
}  // namespace
1262

1263
BOTAN_REGISTER_TEST_FN("tls",
1264
                       "tls_session_manager",
1265
                       test_session_manager_in_memory,
1266
                       test_session_manager_choose_ticket,
1267
                       test_session_manager_stateless,
1268
                       test_session_manager_hybrid,
1269
                       test_session_manager_sqlite,
1270
                       tls_session_manager_expiry);
1271

1272
}  // namespace Botan_Tests
1273

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

© 2025 Coveralls, Inc