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

randombit / botan / 5356326050

23 Jun 2023 01:05PM UTC coverage: 91.728% (-0.008%) from 91.736%
5356326050

Pull #3595

github

web-flow
Merge a5b917599 into 92171c524
Pull Request #3595: Improve clang-tidy coverage

78163 of 85212 relevant lines covered (91.73%)

12690161.35 hits per line

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

98.95
/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::SymmetricKey psk(const std::string&, const std::string&, const std::string&) override {
55✔
47
         return Botan::SymmetricKey("DEADBEEFCAFEC0DE");
55✔
48
      }
49
};
50

51
class Other_Test_Credentials_Manager : public Botan::Credentials_Manager {
1✔
52
   public:
53
      Botan::SymmetricKey psk(const std::string&, const std::string&, const std::string&) override {
1✔
54
         return Botan::SymmetricKey("1337BEEFC0DECAFE");
1✔
55
      }
56
};
57

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

60
class Session_Manager_Callbacks : public Botan::TLS::Callbacks {
6✔
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 {
×
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() {
60✔
100
   return Test::rng().random_vec<Botan::TLS::Session_ID>(32);
58✔
101
}
102

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

107
decltype(auto) random_opaque_handle() {
1✔
108
   return Test::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
                                 server_info,
130
                                 cbs.tls_current_timestamp());
20✔
131
   #else
132
      throw Botan_Tests::Test_Error("TLS 1.3 is not available in this build");
133
   #endif
134
   }
135
}
136

137
using namespace std::literals;
138

139
std::vector<Test::Result> test_session_manager_in_memory() {
1✔
140
   const Botan::TLS::Session_ID default_id = random_id();
1✔
141

142
   std::optional<Botan::TLS::Session_Manager_In_Memory> mgr;
1✔
143

144
   Session_Manager_Callbacks cbs;
1✔
145
   Session_Manager_Policy plcy;
1✔
146

147
   return {
1✔
148
      Botan_Tests::CHECK("creation", [&](auto&) { mgr.emplace(Test::rng_as_shared(), 5); }),
2✔
149

150
      Botan_Tests::CHECK("empty cache does not obtain anything",
151
                         [&](auto& result) {
1✔
152
                            result.confirm("no session found via server info",
1✔
153
                                           mgr->find(server_info, cbs, plcy).empty());
3✔
154

155
                            Botan::TLS::Session_ID mock_id = random_id();
156
                            auto mock_ticket = Test::rng().random_vec<Botan::TLS::Session_Ticket>(128);
1✔
157

158
                            result.confirm("no session found via ID", !mgr->retrieve(mock_id, cbs, plcy));
5✔
159
                            result.confirm("no session found via ID", !mgr->retrieve(mock_ticket, cbs, plcy));
5✔
160
                         }),
2✔
161

162
      Botan_Tests::CHECK("clearing empty cache",
163
                         [&](auto& result) { result.test_eq("does not delete anything", mgr->remove_all(), 0); }),
2✔
164

165
      Botan_Tests::CHECK("establish new session",
166
                         [&](auto& result) {
1✔
167
                            auto handle =
2✔
168
                               mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
1✔
169
                            if(result.confirm("establishment was successful", handle.has_value())) {
3✔
170
                               result.require("session id was set", handle->id().has_value());
2✔
171
                               result.confirm("session ticket was empty", !handle->ticket().has_value());
3✔
172
                               result.test_is_eq("session id is correct", handle->id().value(), default_id);
3✔
173
                            }
174
                         }),
1✔
175

176
      Botan_Tests::CHECK("obtain session from server info",
177
                         [&](auto& result) {
1✔
178
                            auto sessions = mgr->find(server_info, cbs, plcy);
1✔
179
                            if(result.confirm("session was found successfully", sessions.size() == 1)) {
3✔
180
                               result.test_is_eq("protocol version was echoed",
1✔
181
                                                 sessions[0].session.version(),
1✔
182
                                                 Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
183
                               result.test_is_eq(
1✔
184
                                  "ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
1✔
185
                               result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), default_id);
3✔
186
                               result.confirm("not a ticket", !sessions[0].handle.ticket().has_value());
2✔
187
                            }
188
                         }),
1✔
189

190
      Botan_Tests::CHECK("obtain session from ID",
191
                         [&](auto& result) {
1✔
192
                            auto session = mgr->retrieve(default_id, cbs, plcy);
2✔
193
                            if(result.confirm("session was found successfully", session.has_value())) {
3✔
194
                               result.test_is_eq("protocol version was echoed",
1✔
195
                                                 session->version(),
1✔
196
                                                 Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
197
                               result.test_is_eq(
1✔
198
                                  "ciphersuite was echoed", session->ciphersuite_code(), uint16_t(0x009C));
2✔
199
                            }
200
                         }),
1✔
201

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

214
      Botan_Tests::CHECK("obtain session from ticket == id does not work",
215
                         [&](auto& result) {
1✔
216
                            auto session = mgr->retrieve(Botan::TLS::Session_Ticket(default_id), cbs, plcy);
2✔
217
                            result.confirm("session was not found", !session.has_value());
3✔
218
                         }),
1✔
219

220
      Botan_Tests::CHECK("invalid ticket causes std::nullopt",
221
                         [&](auto& result) {
1✔
222
                            auto no_session = mgr->retrieve(random_ticket(), cbs, plcy);
3✔
223
                            result.confirm("std::nullopt on bogus ticket", !no_session.has_value());
3✔
224
                         }),
1✔
225

226
      Botan_Tests::CHECK("invalid ID causes std::nullopt",
227
                         [&](auto& result) {
1✔
228
                            auto no_session = mgr->retrieve(random_id(), cbs, plcy);
3✔
229
                            result.confirm("std::nullopt on bogus ID", !no_session.has_value());
3✔
230
                         }),
1✔
231

232
      Botan_Tests::CHECK("remove_all",
233
                         [&](auto& result) {
1✔
234
                            result.test_eq("removed one element", mgr->remove_all(), 1);
2✔
235
                            result.test_eq("should be empty now", mgr->remove_all(), 0);
1✔
236
                         }),
1✔
237

238
      Botan_Tests::CHECK("add session with ID",
239
                         [&](auto& result) {
1✔
240
                            Botan::TLS::Session_ID new_id = random_id();
241

242
                            mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_id);
4✔
243
                            result.require("obtain via ID", mgr->retrieve(new_id, cbs, plcy).has_value());
5✔
244

245
                            auto sessions = mgr->find(server_info, cbs, plcy);
1✔
246
                            if(result.confirm("found via server info", sessions.size() == 1)) {
3✔
247
                               result.test_is_eq("protocol version was echoed",
1✔
248
                                                 sessions[0].session.version(),
1✔
249
                                                 Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
250
                               result.test_is_eq(
1✔
251
                                  "ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
252
                               result.test_is_eq("ID was echoed", sessions[0].handle.id().value(), new_id);
3✔
253
                               result.confirm("ticket was not stored", !sessions[0].handle.ticket().has_value());
3✔
254
                            }
255

256
                            mgr->remove_all();
1✔
257
                         }),
2✔
258

259
      Botan_Tests::CHECK("add session with ticket",
260
                         [&](auto& result) {
1✔
261
                            Botan::TLS::Session_Ticket new_ticket = random_ticket();
262

263
                            mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), new_ticket);
4✔
264
                            // cannot be obtained by (non-existent) ID or randomly generated ticket
265

266
                            auto sessions = mgr->find(server_info, cbs, plcy);
1✔
267
                            if(result.confirm("found via server info", sessions.size() == 1)) {
3✔
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(
1✔
272
                                  "ciphersuite was echoed", sessions[0].session.ciphersuite_code(), uint16_t(0x009C));
2✔
273
                               result.confirm("ID was not stored", !sessions[0].handle.id().has_value());
3✔
274
                               result.test_is_eq("ticket was echoed", sessions[0].handle.ticket().value(), new_ticket);
3✔
275
                            }
276

277
                            mgr->remove_all();
1✔
278
                         }),
2✔
279

280
      Botan_Tests::CHECK(
281
         "removing by ID or opaque handle",
282
         [&](auto& result) {
1✔
283
            Botan::TLS::Session_Manager_In_Memory local_mgr(Test::rng_as_shared());
1✔
284

285
            const auto new_session1 =
2✔
286
               local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), default_id);
2✔
287
            const auto new_session2 = local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
288
            result.require(
1✔
289
               "saving worked",
290
               new_session1.has_value() && new_session1->id().has_value() && !new_session1->ticket().has_value());
3✔
291
            result.require(
1✔
292
               "saving worked",
293
               new_session2.has_value() && new_session2->id().has_value() && !new_session2->ticket().has_value());
3✔
294

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

297
            result.test_is_eq("one was deleted", local_mgr.remove(default_id), size_t(1));
4✔
298
            result.confirm("cannot obtain via default ID anymore",
2✔
299
                           !local_mgr.retrieve(default_id, cbs, plcy).has_value());
4✔
300
            result.test_is_eq(
1✔
301
               "can find less via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
302

303
            result.test_is_eq("last one was deleted",
1✔
304
                              local_mgr.remove(Botan::TLS::Opaque_Session_Handle(new_session2->id().value())),
4✔
305
                              size_t(1));
1✔
306
            result.confirm("cannot obtain via ID anymore",
2✔
307
                           !local_mgr.retrieve(new_session2->id().value(), cbs, plcy).has_value());
4✔
308
            result.confirm("cannot find via server info", local_mgr.find(server_info, cbs, plcy).empty());
3✔
309
         }),
2✔
310

311
      Botan_Tests::CHECK(
312
         "removing by ticket or opaque handle",
313
         [&](auto& result) {
1✔
314
            Botan::TLS::Session_Manager_In_Memory local_mgr(Test::rng_as_shared());
2✔
315

316
            Botan::TLS::Session_Ticket ticket1 = random_ticket();
317
            Botan::TLS::Session_Ticket ticket2 = random_ticket();
318
            Botan::TLS::Session_Ticket ticket3 = random_ticket();
319

320
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket1);
2✔
321
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket2);
2✔
322
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket3);
2✔
323
            result.test_is_eq(
1✔
324
               "can find them via server info ", local_mgr.find(server_info, cbs, plcy).size(), size_t(3));
2✔
325

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

329
            result.test_is_eq("remove one session by opaque handle",
1✔
330
                              local_mgr.remove(Botan::TLS::Opaque_Session_Handle(ticket3.get())),
3✔
331
                              size_t(1));
1✔
332
            result.test_is_eq(
1✔
333
               "can find only one via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
334
         }),
3✔
335

336
      Botan_Tests::CHECK(
337
         "session purging",
338
         [&](auto& result) {
1✔
339
            result.require("max sessions is 5", mgr->capacity() == 5);
41✔
340

341
            // fill the Session_Manager up fully
342
            std::vector<Botan::TLS::Session_Handle> handles;
1✔
343
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
344
               handles.push_back(
5✔
345
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id()).value());
26✔
346
            }
347

348
            for(size_t i = 0; i < handles.size(); ++i) {
6✔
349
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
20✔
350
            }
351

352
            // add one more session (causing a first purge to happen)
353
            handles.push_back(
1✔
354
               mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id()).value());
4✔
355

356
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
3✔
357
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
358
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
20✔
359
            }
360

361
            // remove a session to cause a 'gap' in the FIFO
362
            mgr->remove(handles[4]);
1✔
363
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
3✔
364
            result.confirm("deleted session gone", !mgr->retrieve(handles[4], cbs, plcy).has_value());
3✔
365
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
366
               result.confirm("session still there", i == 4 || mgr->retrieve(handles[i], cbs, plcy).has_value());
23✔
367
            }
368

369
            // insert enough new sessions to fully purge the ones currently held
370
            for(size_t i = 0; i < mgr->capacity(); ++i) {
6✔
371
               handles.push_back(
5✔
372
                  mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id()).value());
20✔
373
            }
374

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

379
            for(size_t i = handles.size() - mgr->capacity(); i < handles.size(); ++i) {
6✔
380
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
20✔
381
            }
382

383
            // clear it all out
384
            result.test_eq("rest of the sessions removed", mgr->remove_all(), size_t(5));
1✔
385
         }),
1✔
386
   };
17✔
387
}
3✔
388

389
std::vector<Test::Result> test_session_manager_choose_ticket() {
1✔
390
   #if defined(BOTAN_HAS_TLS_13)
391
   Session_Manager_Callbacks cbs;
1✔
392
   Session_Manager_Policy plcy;
1✔
393

394
   auto default_session = [&](const std::string& suite,
9✔
395
                              Botan::TLS::Callbacks& mycbs,
396
                              Botan::TLS::Protocol_Version version = Botan::TLS::Protocol_Version::TLS_V13) {
397
      return (version.is_pre_tls_13())
8✔
398
                ? Botan::TLS::Session({},
399
                                      version,
400
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
1✔
401
                                      Botan::TLS::Connection_Side::Server,
402
                                      true,
403
                                      true,
404
                                      {},
405
                                      server_info,
406
                                      0,
407
                                      mycbs.tls_current_timestamp())
1✔
408
                : Botan::TLS::Session({},
409
                                      std::nullopt,
410
                                      0,
411
                                      std::chrono::seconds(1024),
7✔
412
                                      version,
413
                                      Botan::TLS::Ciphersuite::from_name(suite)->ciphersuite_code(),
7✔
414
                                      Botan::TLS::Connection_Side::Server,
415
                                      {},
416
                                      server_info,
417
                                      mycbs.tls_current_timestamp());
16✔
418
   };
419

420
   auto ticket = [&](std::span<const uint8_t> identity) {
16✔
421
      return Botan::TLS::Ticket(Botan::TLS::Opaque_Session_Handle(identity), 0);
15✔
422
   };
423

424
   return {
1✔
425
      CHECK("empty manager has nothing to choose from",
426
            [&](auto& result) {
1✔
427
               Botan::TLS::Session_Manager_In_Memory mgr(Test::rng_as_shared());
2✔
428

429
               Botan::TLS::Session_Ticket random_session_ticket = random_ticket();
430

431
               result.confirm("empty ticket list, no session",
2✔
432
                              !mgr.choose_from_offered_tickets({}, "SHA-256", cbs, plcy).has_value());
2✔
433
               result.confirm(
2✔
434
                  "empty session manager, no session",
435
                  !mgr.choose_from_offered_tickets(std::vector{ticket(random_session_ticket)}, "SHA-256", cbs, plcy)
3✔
436
                      .has_value());
4✔
437
            }),
1✔
438

439
      CHECK("choose ticket by ID",
440
            [&](auto& result) {
1✔
441
               Botan::TLS::Session_Manager_In_Memory mgr(Test::rng_as_shared());
2✔
442
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
443

444
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
445
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
446

447
               // choose from a list of tickets that contains only handles[0]
448
               auto session1 =
1✔
449
                  mgr.choose_from_offered_tickets(std::vector{ticket(handles[0].id().value())}, "SHA-256", cbs, plcy);
4✔
450
               result.require("ticket was chosen and produced a session (1)", session1.has_value());
1✔
451
               result.test_is_eq("chosen offset", session1->second, uint16_t(0));
1✔
452

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

459
               // choose from a list of tickets that contains a random ticket and handles[1]
460
               auto session3 = mgr.choose_from_offered_tickets(
2✔
461
                  std::vector{ticket(random_ticket()), ticket(handles[1].id().value())}, "SHA-256", cbs, plcy);
6✔
462
               result.require("ticket was chosen and produced a session (3)", session3.has_value());
1✔
463
               result.test_is_eq("chosen second offset", session3->second, uint16_t(1));
2✔
464
            }),
3✔
465

466
      CHECK("choose ticket by ticket",
467
            [&](auto& result) {
1✔
468
               auto creds = std::make_shared<Test_Credentials_Manager>();
469
               Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
4✔
470
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
471

472
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
473
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
474

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

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

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

494
      CHECK("choose ticket based on requested hash function",
495
            [&](auto& result) {
1✔
496
               auto creds = std::make_shared<Test_Credentials_Manager>();
497
               Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
4✔
498
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
499

500
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
501
               handles.push_back(mgr.establish(default_session("AES_256_GCM_SHA384", cbs)).value());
4✔
502

503
               auto session = mgr.choose_from_offered_tickets(
1✔
504
                  std::vector{
8✔
505
                     ticket(random_ticket()), ticket(handles[0].ticket().value()), ticket(handles[1].ticket().value())},
3✔
506
                  "SHA-384",
507
                  cbs,
508
                  plcy);
1✔
509
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
510
               result.test_is_eq("chosen second offset", session->second, uint16_t(2));
2✔
511
            }),
2✔
512

513
      CHECK("choose ticket based on protocol version",
514
            [&](auto& result) {
1✔
515
               auto creds = std::make_shared<Test_Credentials_Manager>();
516
               Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
4✔
517
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
518

519
               handles.push_back(
1✔
520
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V12)).value());
4✔
521
               handles.push_back(
1✔
522
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V13)).value());
4✔
523

524
               auto session = mgr.choose_from_offered_tickets(
1✔
525
                  std::vector{
8✔
526
                     ticket(random_ticket()), ticket(handles[0].ticket().value()), ticket(handles[1].ticket().value())},
3✔
527
                  "SHA-256",
528
                  cbs,
529
                  plcy);
1✔
530
               result.require("ticket was chosen and produced a session", session.has_value());
1✔
531
               result.test_is_eq("chosen second offset (TLS 1.3 ticket)", session->second, uint16_t(2));
2✔
532
            }),
2✔
533
   };
6✔
534
   #else
535
   return {};
536
   #endif
537
}
1✔
538

539
std::vector<Test::Result> test_session_manager_stateless() {
1✔
540
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
541
   Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
3✔
542

543
   Session_Manager_Callbacks cbs;
1✔
544
   Session_Manager_Policy plcy;
1✔
545

546
   return {
1✔
547
      Botan_Tests::CHECK("establish with default parameters",
548
                         [&](auto& result) {
1✔
549
                            result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
550
                            auto ticket = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
551
                            result.confirm("returned ticket", ticket.has_value() && ticket->is_ticket());
2✔
552
                         }),
1✔
553

554
      Botan_Tests::CHECK("establish with disabled tickets",
555
                         [&](auto& result) {
1✔
556
                            result.confirm("will emit tickets", mgr.emits_session_tickets());
2✔
557
                            auto ticket = mgr.establish(
2✔
558
                               default_session(Botan::TLS::Connection_Side::Server, cbs), std::nullopt, true);
1✔
559
                            result.confirm("returned std::nullopt", !ticket.has_value());
3✔
560
                         }),
1✔
561

562
      Botan_Tests::CHECK("establish without ticket key in credentials manager",
563
                         [&](auto& result) {
1✔
564
                            Botan::TLS::Session_Manager_Stateless local_mgr(
3✔
565
                               std::make_shared<Empty_Credentials_Manager>(), Test::rng_as_shared());
566

567
                            result.confirm("won't emit tickets", !local_mgr.emits_session_tickets());
3✔
568
                            auto ticket =
2✔
569
                               local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
570
                            result.confirm("returned std::nullopt", !ticket.has_value());
3✔
571
                         }),
1✔
572

573
      Botan_Tests::CHECK("retrieve via ticket",
574
                         [&](auto& result) {
1✔
575
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
576
                            auto ticket2 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
577
                            result.require("tickets created successfully", ticket1.has_value() && ticket2.has_value());
1✔
578

579
                            Botan::TLS::Session_Manager_Stateless local_mgr(creds, Test::rng_as_shared());
2✔
580
                            result.confirm("can retrieve ticket 1",
2✔
581
                                           mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
582
                            result.confirm("can retrieve ticket 2 from different manager but sam credentials",
2✔
583
                                           local_mgr.retrieve(ticket2.value(), cbs, plcy).has_value());
1✔
584
                         }),
3✔
585

586
      Botan_Tests::CHECK("retrieve via ID does not work",
587
                         [&](auto& result) {
1✔
588
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
589
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
590

591
                            result.confirm("retrieval by ID does not work",
2✔
592
                                           !mgr.retrieve(random_id(), cbs, plcy).has_value());
4✔
593
                         }),
1✔
594

595
      Botan_Tests::CHECK("retrieve via opaque handle does work",
596
                         [&](auto& result) {
1✔
597
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
598
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
599

600
                            result.confirm("retrieval by opaque handle",
2✔
601
                                           mgr.retrieve(ticket1->opaque_handle(), cbs, plcy).has_value());
3✔
602
                         }),
1✔
603

604
      Botan_Tests::CHECK("no retrieve without or with wrong ticket key",
605
                         [&](auto& result) {
1✔
606
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
607
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
608

609
                            Botan::TLS::Session_Manager_Stateless local_mgr1(
3✔
610
                               std::make_shared<Empty_Credentials_Manager>(), Test::rng_as_shared());
611

612
                            Botan::TLS::Session_Manager_Stateless local_mgr2(
3✔
613
                               std::make_shared<Other_Test_Credentials_Manager>(), Test::rng_as_shared());
614

615
                            result.confirm("no successful retrieval (without key)",
2✔
616
                                           !local_mgr1.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
617
                            result.confirm("no successful retrieval (with wrong key)",
2✔
618
                                           !local_mgr2.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
619
                            result.confirm("successful retrieval",
2✔
620
                                           mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
621
                         }),
2✔
622

623
      Botan_Tests::CHECK("Clients cannot be stateless",
624
                         [&](auto& result) {
1✔
625
                            result.test_throws("::store() does not work with ID", [&] {
2✔
626
                               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id());
5✔
627
                            });
628
                            result.test_throws("::store() does not work with ticket", [&] {
2✔
629
                               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket());
4✔
630
                            });
631
                            result.test_throws("::store() does not work with opaque handle", [&] {
2✔
632
                               mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs),
4✔
633
                                         random_opaque_handle());
634
                            });
635

636
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
637
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
638
                            result.confirm("finding tickets does not work", mgr.find(server_info, cbs, plcy).empty());
3✔
639
                         }),
1✔
640

641
      Botan_Tests::CHECK(
642
         "remove is a NOOP",
643
         [&](auto& result) {
1✔
644
            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
645
            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
646

647
            result.test_is_eq("remove the ticket", mgr.remove(ticket1.value()), size_t(0));
1✔
648
            result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
3✔
649

650
            result.test_is_eq("remove the ticket", mgr.remove_all(), size_t(0));
1✔
651
            result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
4✔
652
         }),
1✔
653

654
      Botan_Tests::CHECK(
655
         "retrieval via ticket reconstructs the start_time stamp",
656
         [&](auto& result) {
1✔
657
            auto session_before = default_session(Botan::TLS::Connection_Side::Server, cbs);
1✔
658
            auto ticket = mgr.establish(session_before);
2✔
659
            result.require("got a ticket", ticket.has_value() && ticket->is_ticket());
1✔
660
            auto session_after = mgr.retrieve(ticket.value(), cbs, plcy);
1✔
661
            result.require("got the session back", session_after.has_value());
2✔
662

663
            result.test_is_eq(
1✔
664
               "timestamps match",
665
               std::chrono::duration_cast<std::chrono::seconds>(session_before.start_time().time_since_epoch()).count(),
×
666
               std::chrono::duration_cast<std::chrono::seconds>(session_after->start_time().time_since_epoch())
1✔
667
                  .count());
2✔
668
         }),
2✔
669
   };
11✔
670
}
2✔
671

672
std::vector<Test::Result> test_session_manager_hybrid() {
1✔
673
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
674
   Session_Manager_Callbacks cbs;
1✔
675
   Session_Manager_Policy plcy;
1✔
676

677
   // Runs the passed-in hybrid manager test lambdas for all available stateful
678
   // managers. The `make_manager()` helper is passed into the test code and
679
   // transparently constructs a hybrid manager with the respective internal
680
   // stateful manager.
681
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
4✔
682
      std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>>
683
         stateful_manager_factories = {
12✔
684
            {"In Memory",
685
             []() -> std::unique_ptr<Botan::TLS::Session_Manager> {
6✔
686
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(Test::rng_as_shared(), 10);
12✔
687
             }},
688
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
689
            {"SQLite",
690
             []() -> std::unique_ptr<Botan::TLS::Session_Manager> {
6✔
691
                return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
692
                   "secure_pw", Test::rng_as_shared(), Test::temp_file_name("tls_session_manager_sqlite"), 10);
24✔
693
             }},
694
   #endif
695
         };
696

697
      std::vector<Test::Result> results;
3✔
698
      using namespace std::placeholders;
699
      for(auto& factory_and_name : stateful_manager_factories) {
15✔
700
         auto& stateful_manager_name = factory_and_name.first;
6✔
701
         auto& stateful_manager_factory = factory_and_name.second;
6✔
702
         auto make_manager = [stateful_manager_factory, &creds](bool prefer_tickets) {
60✔
703
            return Botan::TLS::Session_Manager_Hybrid(
704
               stateful_manager_factory(), creds, Test::rng_as_shared(), prefer_tickets);
72✔
705
         };
706

707
         auto nm = Botan::fmt("{} ({})", name, stateful_manager_name);
6✔
708
         auto fn = std::bind(lambda, make_manager, _1);
6✔
709
         results.push_back(Botan_Tests::CHECK(nm.c_str(), fn));
18✔
710
      }
711
      return results;
3✔
712
   };
4✔
713

714
   return Test::flatten_result_lists({
3✔
715
      CHECK_all("ticket vs ID preference in establishment",
716
                [&](auto make_manager, auto& result) {
2✔
717
                   auto mgr_prefers_tickets = make_manager(true);
2✔
718
                   auto ticket1 =
4✔
719
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
8✔
720
                   result.confirm("emits a ticket", ticket1.has_value() && ticket1->is_ticket());
4✔
721

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

726
                   auto ticket3 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
4✔
727
                                                            std::nullopt,
728
                                                            true /* TLS 1.2 no ticket support */);
729
                   result.confirm("emits an ID", ticket3.has_value() && ticket3->is_id());
4✔
730

731
                   auto ticket4 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
4✔
732
                                                            std::nullopt,
733
                                                            true /* TLS 1.2 no ticket support */);
734
                   result.confirm("emits an ID", ticket4.has_value() && ticket4->is_id());
4✔
735
                }),
8✔
736

737
      CHECK_all("ticket vs ID preference in retrieval",
738
                [&](auto make_manager, auto& result) {
2✔
739
                   auto mgr_prefers_tickets = make_manager(true);
2✔
740
                   auto mgr_prefers_ids = make_manager(false);
2✔
741

742
                   auto id1 = mgr_prefers_tickets.underlying_stateful_manager()->establish(
4✔
743
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
4✔
744
                   auto id2 = mgr_prefers_ids.underlying_stateful_manager()->establish(
4✔
745
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
746
                   auto ticket =
4✔
747
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
748

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

751
                   result.confirm("mgr1 + ID1", mgr_prefers_tickets.retrieve(id1.value(), cbs, plcy).has_value());
4✔
752
                   result.confirm("mgr1 + ID2", !mgr_prefers_tickets.retrieve(id2.value(), cbs, plcy).has_value());
4✔
753
                   result.confirm("mgr2 + ID1", !mgr_prefers_ids.retrieve(id1.value(), cbs, plcy).has_value());
4✔
754
                   result.confirm("mgr2 + ID2", mgr_prefers_ids.retrieve(id2.value(), cbs, plcy).has_value());
4✔
755
                   result.confirm("mgr1 + ticket", mgr_prefers_tickets.retrieve(ticket.value(), cbs, plcy).has_value());
4✔
756
                   result.confirm("mgr2 + ticket", mgr_prefers_ids.retrieve(ticket.value(), cbs, plcy).has_value());
6✔
757
                }),
6✔
758

759
      CHECK_all("no session tickets if hybrid manager cannot create them",
760
                [&](auto make_manager, auto& result) {
2✔
761
                   Botan::TLS::Session_Manager_Hybrid empty_mgr(
8✔
762
                      std::make_unique<Botan::TLS::Session_Manager_In_Memory>(Test::rng_as_shared(), 10),
4✔
763
                      std::make_shared<Empty_Credentials_Manager>(),
764
                      Test::rng_as_shared());
765
                   auto mgr_prefers_tickets = make_manager(true);
2✔
766
                   auto mgr_prefers_ids = make_manager(false);
2✔
767

768
                   result.confirm("does not emit tickets", !empty_mgr.emits_session_tickets());
4✔
769
                   result.confirm("does emit tickets 1", mgr_prefers_tickets.emits_session_tickets());
4✔
770
                   result.confirm("does emit tickets 2", mgr_prefers_ids.emits_session_tickets());
4✔
771
                }),
2✔
772
   });
7✔
773
}
2✔
774

775
namespace {
776

777
class Temporary_Database_File {
778
   private:
779
      std::string m_temp_file;
780

781
   public:
782
      Temporary_Database_File(const std::string& db_file) : m_temp_file(Test::data_file_as_temporary_copy(db_file)) {
1✔
783
         if(m_temp_file.empty()) {
1✔
784
            throw Test_Error("Failed to create temporary database file");
×
785
         }
786
      }
1✔
787

788
      ~Temporary_Database_File() {
1✔
789
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
790
         if(!m_temp_file.empty()) {
1✔
791
            std::filesystem::remove(m_temp_file);
1✔
792
         }
793
   #endif
794
      }
1✔
795

796
      const std::string& get() const { return m_temp_file; }
797

798
      Temporary_Database_File(const Temporary_Database_File&) = delete;
799
      Temporary_Database_File& operator=(const Temporary_Database_File&) = delete;
800
      Temporary_Database_File(Temporary_Database_File&&) = delete;
801
      Temporary_Database_File& operator=(Temporary_Database_File&&) = delete;
802
};
803

804
}  // namespace
805

806
std::vector<Test::Result> test_session_manager_sqlite() {
1✔
807
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
808
   Session_Manager_Callbacks cbs;
1✔
809
   Session_Manager_Policy plcy;
1✔
810

811
   return {
1✔
812
      Botan_Tests::CHECK(
813
         "migrate session database scheme (purges database)",
814
         [&](auto& result) {
1✔
815
            Temporary_Database_File dbfile("tls-sessions/botan-2.19.3.sqlite");
1✔
816

817
            // legacy database (encrypted with 'thetruthisoutthere') containing:
818
            //    $ sqlite3 src/tests/data/tls-sessions/botan-2.19.3.sqlite  'SELECT * FROM tls_sessions;'
819
            //    63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714|1673606906|cloudflare.com|443|...
820
            //    63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB|1673606915|randombit.net|443|...
821
            Botan::TLS::Session_Manager_SQLite legacy_db("thetruthisoutthere", Test::rng_as_shared(), dbfile.get());
1✔
822

823
            result.confirm("Session_ID for randombit.net is gone",
2✔
824
                           !legacy_db
825
                               .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
826
                                            "63C137030387E4A6CDAD303CCB1F53884944FDE5B4EDD91E6FCF74DCB033DCEB")),
827
                                         cbs,
4✔
828
                                         plcy)
4✔
829
                               .has_value());
3✔
830
            result.confirm("Session_ID for cloudflare.com is gone",
2✔
831
                           !legacy_db
832
                               .retrieve(Botan::TLS::Session_ID(Botan::hex_decode(
2✔
833
                                            "63C136FAD49F05A184F910FD6568A3884164216C11E41CEBFDCD149AF66C1714")),
834
                                         cbs,
835
                                         plcy)
836
                               .has_value());
3✔
837
            result.confirm("no more session for randombit.net",
1✔
838
                           legacy_db.find(Botan::TLS::Server_Information("randombit.net", 443), cbs, plcy).empty());
2✔
839
            result.confirm("no more session for cloudflare.com",
1✔
840
                           legacy_db.find(Botan::TLS::Server_Information("cloudflare.com", 443), cbs, plcy).empty());
2✔
841

842
            result.test_is_eq("empty database won't get more empty", legacy_db.remove_all(), size_t(0));
2✔
843
         }),
1✔
844

845
      Botan_Tests::CHECK("clearing empty database",
846
                         [&](auto& result) {
1✔
847
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
848
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("empty.sqlite"));
1✔
849
                            result.test_eq("does not delete anything", mgr.remove_all(), 0);
2✔
850
                         }),
1✔
851

852
      Botan_Tests::CHECK(
853
         "establish new session",
854
         [&](auto& result) {
1✔
855
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
856
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("new_session.sqlite"));
1✔
857
            auto some_random_id = random_id();
858
            auto some_random_handle =
2✔
859
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
2✔
860
            result.require("establishment was successful", some_random_handle.has_value());
2✔
861
            result.require("session id was set", some_random_handle->id().has_value());
2✔
862
            result.test_is_eq("session id is correct", some_random_handle->id().value(), some_random_id);
3✔
863

864
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
865
            result.require("establishment was successful", some_virtual_handle.has_value());
2✔
866
            result.require("session id was set", some_virtual_handle->id().has_value());
2✔
867
         }),
3✔
868

869
      Botan_Tests::CHECK(
870
         "retrieve session by ID",
871
         [&](auto& result) {
1✔
872
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
873
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("retrieve_by_id.sqlite"));
1✔
874
            auto some_random_id = random_id();
875
            auto some_random_handle =
2✔
876
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
2✔
877
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
878

879
            result.require("establishment was successful", some_random_handle->is_id() && some_virtual_handle->is_id());
1✔
880

881
            auto session1 = mgr.retrieve(some_random_handle.value(), cbs, plcy);
1✔
882
            if(result.confirm("found session by user-provided ID", session1.has_value())) {
3✔
883
               result.test_is_eq("protocol version was echoed",
1✔
884
                                 session1->version(),
1✔
885
                                 Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
886
               result.test_is_eq("ciphersuite was echoed", session1->ciphersuite_code(), uint16_t(0x009C));
2✔
887
            }
888

889
            auto session2 = mgr.retrieve(some_virtual_handle.value(), cbs, plcy);
1✔
890
            if(result.confirm("found session by manager-generated ID", session2.has_value())) {
3✔
891
               result.test_is_eq("protocol version was echoed",
1✔
892
                                 session2->version(),
1✔
893
                                 Botan::TLS::Protocol_Version(Botan::TLS::Version_Code::TLS_V12));
1✔
894
               result.test_is_eq("ciphersuite was echoed", session2->ciphersuite_code(), uint16_t(0x009C));
2✔
895
            }
896

897
            auto session3 = mgr.retrieve(random_id(), cbs, plcy);
3✔
898
            result.confirm("random ID creates empty result", !session3.has_value());
3✔
899
         }),
6✔
900

901
      Botan_Tests::CHECK(
902
         "retrieval via ticket creates empty result",
903
         [&](auto& result) {
1✔
904
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
905
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("retrieve_by_ticket.sqlite"));
1✔
906
            auto some_random_handle =
2✔
907
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id());
2✔
908
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
909

910
            result.confirm("std::nullopt on random ticket", !mgr.retrieve(random_ticket(), cbs, plcy).has_value());
5✔
911
         }),
2✔
912

913
      Botan_Tests::CHECK(
914
         "storing sessions and finding them by server info",
915
         [&](auto& result) {
1✔
916
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
917
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("store_and_find.sqlite"));
1✔
918
            auto id = random_id();
919
            auto ticket = random_ticket();
920
            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
921
            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
922

923
            auto found_sessions = mgr.find(server_info, cbs, plcy);
1✔
924
            if(result.test_is_eq("found both sessions", found_sessions.size(), size_t(2))) {
2✔
925
               for(const auto& [session, handle] : found_sessions) {
3✔
926
                  result.confirm("ID matches", !handle.is_id() || handle.id().value() == id);
5✔
927
                  result.confirm("ticket matches", !handle.is_ticket() || handle.ticket().value() == ticket);
6✔
928
               }
929
            }
930
         }),
3✔
931

932
      Botan_Tests::CHECK("removing sessions",
933
                         [&](auto& result) {
1✔
934
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
935
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("remove.sqlite"));
1✔
936
                            auto id = random_id();
937
                            auto ticket = random_ticket();
938
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
939
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
940
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id());
2✔
941
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket());
2✔
942

943
                            result.test_is_eq("deletes one session by ID", mgr.remove(id), size_t(1));
4✔
944
                            result.test_is_eq("deletes one session by ticket", mgr.remove(ticket), size_t(1));
4✔
945

946
                            auto found_sessions = mgr.find(server_info, cbs, plcy);
1✔
947
                            if(result.test_is_eq("found some other sessions", found_sessions.size(), size_t(2))) {
2✔
948
                               for(const auto& [session, handle] : found_sessions) {
3✔
949
                                  result.confirm("ID does not match", !handle.is_id() || handle.id().value() != id);
7✔
950
                                  result.confirm("ticket does not match",
4✔
951
                                                 !handle.is_ticket() || handle.ticket().value() != ticket);
5✔
952
                               }
953
                            }
954

955
                            result.test_is_eq("removing the rest of the sessions", mgr.remove_all(), size_t(2));
1✔
956
                         }),
3✔
957

958
      Botan_Tests::CHECK("old sessions are purged when needed",
959
                         [&](auto& result) {
1✔
960
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
961
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("purging.sqlite"), 1);
1✔
962

963
                            std::vector<Botan::TLS::Session_ID> ids = {random_id(), random_id(), random_id()};
4✔
964
                            mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[0]);
4✔
965
                            result.require("new ID exists", mgr.retrieve(ids[0], cbs, plcy).has_value());
5✔
966

967
                            // Session timestamps are saved with second-resolution. If more than
968
                            // one session has the same (coarse) timestamp it is undefined which
969
                            // will be purged first. The clock tick ensures that session's
970
                            // timestamps are unique.
971
                            cbs.tick();
1✔
972
                            mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[1]);
2✔
973
                            result.require("first ID is gone", !mgr.retrieve(ids[0], cbs, plcy).has_value());
4✔
974
                            result.require("new ID exists", mgr.retrieve(ids[1], cbs, plcy).has_value());
5✔
975

976
                            cbs.tick();
1✔
977
                            mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[2]);
2✔
978
                            result.require("second ID is gone", !mgr.retrieve(ids[1], cbs, plcy).has_value());
4✔
979
                            result.require("new ID exists", mgr.retrieve(ids[2], cbs, plcy).has_value());
5✔
980

981
                            result.test_is_eq("only one entry exists", mgr.remove_all(), size_t(1));
1✔
982
                         }),
1✔
983

984
      Botan_Tests::CHECK("session purging can be disabled",
985
                         [&](auto& result) {
1✔
986
                            Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere",
3✔
987
                                                                   Test::rng_as_shared(),
988
                                                                   Test::temp_file_name("purging.sqlite"),
1✔
989
                                                                   0 /* no pruning! */);
990

991
                            for(size_t i = 0; i < 25; ++i) {
26✔
992
                               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id());
50✔
993
                            }
994

995
                            result.test_is_eq("no entries were purged along the way", mgr.remove_all(), size_t(25));
2✔
996
                         }),
1✔
997
   };
10✔
998
   #else
999
   return {};
1000
   #endif
1001
}
1✔
1002

1003
std::vector<Test::Result> tls_session_manager_expiry() {
1✔
1004
   Session_Manager_Callbacks cbs;
1✔
1005
   Session_Manager_Policy plcy;
1✔
1006

1007
   auto CHECK_all = [&](const std::string& name, auto lambda) -> std::vector<Test::Result> {
6✔
1008
      std::vector<std::pair<std::string, std::function<std::unique_ptr<Botan::TLS::Session_Manager>()>>>
1009
         stateful_manager_factories = {
25✔
1010
            {"In Memory",
1011
             []() -> std::unique_ptr<Botan::TLS::Session_Manager> {
5✔
1012
                return std::make_unique<Botan::TLS::Session_Manager_In_Memory>(Test::rng_as_shared(), 10);
10✔
1013
             }},
1014
            {"Stateless",
1015
             [&]() -> std::unique_ptr<Botan::TLS::Session_Manager> {
2✔
1016
                return std::make_unique<Botan::TLS::Session_Manager_Stateless>(
1017
                   std::make_shared<Test_Credentials_Manager>(), Test::rng_as_shared());
6✔
1018
             }},
1019
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
1020
            {"SQLite",
1021
             []() -> std::unique_ptr<Botan::TLS::Session_Manager> {
5✔
1022
                return std::make_unique<Botan::TLS::Session_Manager_SQLite>(
1023
                   "secure_pw", Test::rng_as_shared(), Test::temp_file_name("tls_session_manager_sqlite"), 10);
20✔
1024
             }},
1025
   #endif
1026
         };
1027

1028
      std::vector<Test::Result> results;
5✔
1029
      results.reserve(stateful_manager_factories.size());
5✔
1030
      using namespace std::placeholders;
1031
      for(auto& [sub_name, factory] : stateful_manager_factories) {
35✔
1032
         auto nm = Botan::fmt("{} ({})", name, sub_name);
15✔
1033
         auto fn = std::bind(lambda, sub_name, factory, _1);
15✔
1034
         results.push_back(Botan_Tests::CHECK(nm.c_str(), fn));
30✔
1035
      }
1036
      return results;
5✔
1037
   };
5✔
1038

1039
   return Test::flatten_result_lists({
5✔
1040
      CHECK_all("sessions expire",
1041
                [&](auto, auto factory, auto& result) {
3✔
1042
                   auto mgr = factory();
3✔
1043

1044
                   auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
6✔
1045
                   result.require("saved successfully", handle.has_value());
6✔
1046
                   result.require("session was found", mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1047
                   cbs.tick();
3✔
1048
                   result.confirm("session has expired", !mgr->retrieve(handle.value(), cbs, plcy).has_value());
9✔
1049
                   result.test_is_eq("session was deleted when it expired", mgr->remove_all(), size_t(0));
6✔
1050
                }),
6✔
1051

1052
         CHECK_all("expired sessions are not found",
1053
                   [&](const std::string& type, auto factory, auto& result) {
3✔
1054
                      if(type == "Stateless") {
3✔
1055
                         return;  // this manager can neither store nor find anything
1✔
1056
                      }
1057

1058
                      auto mgr = factory();
2✔
1059

1060
                      auto handle_old = random_id();
1061
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_old);
6✔
1062
                      result.require("session was found", mgr->retrieve(handle_old, cbs, plcy).has_value());
10✔
1063

1064
                      cbs.tick();
2✔
1065
                      auto handle_new = random_id();
1066
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_new);
6✔
1067
                      result.require("session was found", mgr->retrieve(handle_new, cbs, plcy).has_value());
10✔
1068

1069
                      auto sessions_and_handles = mgr->find(server_info, cbs, plcy);
2✔
1070
                      result.require("sessions are found", !sessions_and_handles.empty());
2✔
1071
                      result.test_is_eq("exactly one session is found", sessions_and_handles.size(), size_t(1));
4✔
1072
                      result.test_is_eq(
4✔
1073
                         "the new session is found", sessions_and_handles.front().handle.id().value(), handle_new);
4✔
1074

1075
                      result.test_is_eq("old session was deleted when it expired", mgr->remove_all(), size_t(1));
2✔
1076
                   }),
8✔
1077

1078
         CHECK_all(
1079
            "session tickets are not reused",
1080
            [&](const std::string& type, auto factory, auto& result) {
3✔
1081
               if(type == "Stateless") {
3✔
1082
                  return;  // this manager can neither store nor find anything
1✔
1083
               }
1084

1085
               auto mgr = factory();
2✔
1086

1087
               auto handle_1 = random_id();
1088
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1089
                          handle_1);
1090
               auto handle_2 = random_ticket();
1091
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1092
                          handle_2);
1093

1094
   #if defined(BOTAN_HAS_TLS_13)
1095
               auto handle_3 = random_id();
1096
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1097
                          handle_3);
1098
               auto handle_4 = random_ticket();
1099
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1100
                          handle_4);
1101
   #endif
1102

1103
               plcy.set_allow_session_reuse(false);
2✔
1104

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

1108
               auto sessions_and_handles2 = mgr->find(server_info, cbs, plcy);
2✔
1109
               result.test_is_eq("only one session is found", sessions_and_handles2.size(), size_t(1));
4✔
1110
               result.confirm("found session is the Session_ID", sessions_and_handles2.front().handle.is_id());
6✔
1111
               result.test_is_eq(
4✔
1112
                  "found session is the Session_ID", sessions_and_handles2.front().handle.id().value(), handle_1);
6✔
1113
               result.confirm("found session is TLS 1.2",
4✔
1114
                              sessions_and_handles2.front().session.version().is_pre_tls_13());
2✔
1115
            }),
12✔
1116

1117
         CHECK_all("number of found tickets is capped",
1118
                   [&](const std::string& type, auto factory, auto& result) {
3✔
1119
                      if(type == "Stateless") {
3✔
1120
                         return;  // this manager can neither store nor find anything
1✔
1121
                      }
1122

1123
                      auto mgr = factory();
12✔
1124

1125
                      std::array<Botan::TLS::Session_Ticket, 5> tickets;
1126
                      for(auto& ticket : tickets) {
12✔
1127
                         ticket = random_ticket();
10✔
1128
                         mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
32✔
1129
                      }
1130

1131
                      plcy.set_allow_session_reuse(true);
10✔
1132

1133
                      plcy.set_session_limit(1);
2✔
1134
                      result.test_is_eq("find one",
2✔
1135
                                        mgr->find(server_info, cbs, plcy).size(),
4✔
1136
                                        size_t(plcy.maximum_session_tickets_per_client_hello()));
2✔
1137

1138
                      plcy.set_session_limit(3);
2✔
1139
                      result.test_is_eq("find three",
2✔
1140
                                        mgr->find(server_info, cbs, plcy).size(),
4✔
1141
                                        size_t(plcy.maximum_session_tickets_per_client_hello()));
2✔
1142

1143
                      plcy.set_session_limit(10);
2✔
1144
                      result.test_is_eq("find all five", mgr->find(server_info, cbs, plcy).size(), size_t(5));
4✔
1145
                   }),
4✔
1146

1147
   #if defined(BOTAN_HAS_TLS_13)
1148
         CHECK_all("expired tickets are not selected for PSK resumption", [&](auto, auto factory, auto& result) {
3✔
1149
            auto ticket = [&](const Botan::TLS::Session_Handle& handle) {
12✔
1150
               return Botan::TLS::Ticket(handle.opaque_handle(), 0);
12✔
1151
            };
1152

1153
            auto mgr = factory();
3✔
1154

1155
            auto old_handle = mgr->establish(
6✔
1156
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
3✔
1157
            cbs.tick();
3✔
1158
            auto new_handle = mgr->establish(
6✔
1159
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1160
            result.require("both sessions are stored", old_handle.has_value() && new_handle.has_value());
6✔
1161

1162
            auto session_and_index = mgr->choose_from_offered_tickets(
15✔
1163
               std::vector{ticket(old_handle.value()), ticket(new_handle.value())}, "SHA-256", cbs, plcy);
6✔
1164
            result.require("a ticket was chosen", session_and_index.has_value());
3✔
1165
            result.test_is_eq("the new ticket was chosen", session_and_index->second, uint16_t(1));
3✔
1166

1167
            cbs.tick();
3✔
1168

1169
            auto nothing = mgr->choose_from_offered_tickets(
18✔
1170
               std::vector{ticket(new_handle.value()), ticket(old_handle.value())}, "SHA-256", cbs, plcy);
6✔
1171
            result.require("all tickets are expired", !nothing.has_value());
6✔
1172
         }),
15✔
1173
   #endif
1174
   });
10✔
1175
}
1✔
1176

1177
}  // namespace
1178

1179
BOTAN_REGISTER_TEST_FN("tls",
1180
                       "tls_session_manager",
1181
                       test_session_manager_in_memory,
1182
                       test_session_manager_choose_ticket,
1183
                       test_session_manager_stateless,
1184
                       test_session_manager_hybrid,
1185
                       test_session_manager_sqlite,
1186
                       tls_session_manager_expiry);
1187

1188
}  // namespace Botan_Tests
1189

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