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

randombit / botan / 5230455705

10 Jun 2023 02:30PM UTC coverage: 91.715% (-0.03%) from 91.746%
5230455705

push

github

randombit
Merge GH #3584 Change clang-format AllowShortFunctionsOnASingleLine config from All to Inline

77182 of 84154 relevant lines covered (91.72%)

11975295.43 hits per line

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

98.93
/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

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

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

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

41
namespace Botan_Tests {
42

43
class Test_Credentials_Manager : public Botan::Credentials_Manager {
7✔
44
   public:
45
      Botan::SymmetricKey psk(const std::string&, const std::string&, const std::string&) override {
55✔
46
         return Botan::SymmetricKey("DEADBEEFCAFEC0DE");
55✔
47
      }
48
};
49

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

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

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

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

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

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

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

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

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

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

83
      bool reuse_session_tickets() const override { return allow_session_reuse; }
27✔
84

85
      size_t maximum_session_tickets_per_client_hello() const override { return session_limit; }
54✔
86

87
   public:
88
      size_t session_limit = 1000;  // basically 'no limit'
89
      bool allow_session_reuse = true;
90
};
91

92
namespace {
93

94
decltype(auto) random_id() {
60✔
95
   return Test::rng().random_vec<Botan::TLS::Session_ID>(32);
58✔
96
}
97

98
decltype(auto) random_ticket() {
29✔
99
   return Test::rng().random_vec<Botan::TLS::Session_Ticket>(32);
27✔
100
}
101

102
decltype(auto) random_opaque_handle() {
1✔
103
   return Test::rng().random_vec<Botan::TLS::Opaque_Session_Handle>(32);
1✔
104
}
105

106
const Botan::TLS::Server_Information server_info("botan.randombit.net");
107

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

132
using namespace std::literals;
133

134
std::vector<Test::Result> test_session_manager_in_memory() {
1✔
135
   const Botan::TLS::Session_ID default_id = random_id();
1✔
136

137
   std::optional<Botan::TLS::Session_Manager_In_Memory> mgr;
1✔
138

139
   Session_Manager_Callbacks cbs;
1✔
140
   Session_Manager_Policy plcy;
1✔
141

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

145
      Botan_Tests::CHECK("empty cache does not obtain anything",
146
                         [&](auto& result) {
1✔
147
                            result.confirm("no session found via server info",
1✔
148
                                           mgr->find(server_info, cbs, plcy).empty());
3✔
149

150
                            Botan::TLS::Session_ID mock_id = random_id();
151
                            auto mock_ticket = Test::rng().random_vec<Botan::TLS::Session_Ticket>(128);
1✔
152

153
                            result.confirm("no session found via ID", !mgr->retrieve(mock_id, cbs, plcy));
5✔
154
                            result.confirm("no session found via ID", !mgr->retrieve(mock_ticket, cbs, plcy));
5✔
155
                         }),
2✔
156

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

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

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

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

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

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

215
      Botan_Tests::CHECK("invalid ticket causes std::nullopt",
216
                         [&](auto& result) {
1✔
217
                            auto no_session = mgr->retrieve(random_ticket(), cbs, plcy);
3✔
218
                            result.confirm("std::nullopt on bogus ticket", !no_session.has_value());
3✔
219
                         }),
1✔
220

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

227
      Botan_Tests::CHECK("remove_all",
228
                         [&](auto& result) {
1✔
229
                            result.test_eq("removed one element", mgr->remove_all(), 1);
2✔
230
                            result.test_eq("should be empty now", mgr->remove_all(), 0);
1✔
231
                         }),
1✔
232

233
      Botan_Tests::CHECK("add session with ID",
234
                         [&](auto& result) {
1✔
235
                            Botan::TLS::Session_ID new_id = random_id();
236

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

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

251
                            mgr->remove_all();
1✔
252
                         }),
2✔
253

254
      Botan_Tests::CHECK("add session with ticket",
255
                         [&](auto& result) {
1✔
256
                            Botan::TLS::Session_Ticket new_ticket = random_ticket();
257

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

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

272
                            mgr->remove_all();
1✔
273
                         }),
2✔
274

275
      Botan_Tests::CHECK(
276
         "removing by ID or opaque handle",
277
         [&](auto& result) {
1✔
278
            Botan::TLS::Session_Manager_In_Memory local_mgr(Test::rng_as_shared());
1✔
279

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

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

292
            result.test_is_eq("one was deleted", local_mgr.remove(default_id), size_t(1));
4✔
293
            result.confirm("cannot obtain via default ID anymore",
2✔
294
                           !local_mgr.retrieve(default_id, cbs, plcy).has_value());
4✔
295
            result.test_is_eq(
1✔
296
               "can find less via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
297

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

306
      Botan_Tests::CHECK(
307
         "removing by ticket or opaque handle",
308
         [&](auto& result) {
1✔
309
            Botan::TLS::Session_Manager_In_Memory local_mgr(Test::rng_as_shared());
2✔
310

311
            Botan::TLS::Session_Ticket ticket1 = random_ticket();
312
            Botan::TLS::Session_Ticket ticket2 = random_ticket();
313
            Botan::TLS::Session_Ticket ticket3 = random_ticket();
314

315
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket1);
2✔
316
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket2);
2✔
317
            local_mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket3);
2✔
318
            result.test_is_eq(
1✔
319
               "can find them via server info ", local_mgr.find(server_info, cbs, plcy).size(), size_t(3));
2✔
320

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

324
            result.test_is_eq("remove one session by opaque handle",
1✔
325
                              local_mgr.remove(Botan::TLS::Opaque_Session_Handle(ticket3.get())),
3✔
326
                              size_t(1));
1✔
327
            result.test_is_eq(
1✔
328
               "can find only one via server info", local_mgr.find(server_info, cbs, plcy).size(), size_t(1));
2✔
329
         }),
3✔
330

331
      Botan_Tests::CHECK(
332
         "session purging",
333
         [&](auto& result) {
1✔
334
            result.require("max sessions is 5", mgr->capacity() == 5);
41✔
335

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

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

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

351
            result.confirm("oldest session gone", !mgr->retrieve(handles[0], cbs, plcy).has_value());
3✔
352
            for(size_t i = 1; i < handles.size(); ++i) {
6✔
353
               result.confirm("session still there", mgr->retrieve(handles[i], cbs, plcy).has_value());
20✔
354
            }
355

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

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

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

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

378
            // clear it all out
379
            result.test_eq("rest of the sessions removed", mgr->remove_all(), size_t(5));
1✔
380
         }),
1✔
381
   };
17✔
382
}
3✔
383

384
std::vector<Test::Result> test_session_manager_choose_ticket() {
1✔
385
   #if defined(BOTAN_HAS_TLS_13)
386
   Session_Manager_Callbacks cbs;
1✔
387
   Session_Manager_Policy plcy;
1✔
388

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

415
   auto ticket = [&](std::span<const uint8_t> identity) {
16✔
416
      return Botan::TLS::Ticket(Botan::TLS::Opaque_Session_Handle(identity), 0);
15✔
417
   };
418

419
   return {
1✔
420
      CHECK("empty manager has nothing to choose from",
421
            [&](auto& result) {
1✔
422
               Botan::TLS::Session_Manager_In_Memory mgr(Test::rng_as_shared());
2✔
423

424
               Botan::TLS::Session_Ticket random_session_ticket = random_ticket();
425

426
               result.confirm("empty ticket list, no session",
2✔
427
                              !mgr.choose_from_offered_tickets({}, "SHA-256", cbs, plcy).has_value());
2✔
428
               result.confirm(
2✔
429
                  "empty session manager, no session",
430
                  !mgr.choose_from_offered_tickets(std::vector{ticket(random_session_ticket)}, "SHA-256", cbs, plcy)
3✔
431
                      .has_value());
4✔
432
            }),
1✔
433

434
      CHECK("choose ticket by ID",
435
            [&](auto& result) {
1✔
436
               Botan::TLS::Session_Manager_In_Memory mgr(Test::rng_as_shared());
2✔
437
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
438

439
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
440
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
441

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

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

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

461
      CHECK("choose ticket by ticket",
462
            [&](auto& result) {
1✔
463
               auto creds = std::make_shared<Test_Credentials_Manager>();
464
               Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
4✔
465
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
466

467
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
468
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
469

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

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

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

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

495
               handles.push_back(mgr.establish(default_session("AES_128_GCM_SHA256", cbs)).value());
4✔
496
               handles.push_back(mgr.establish(default_session("AES_256_GCM_SHA384", cbs)).value());
4✔
497

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

508
      CHECK("choose ticket based on protocol version",
509
            [&](auto& result) {
1✔
510
               auto creds = std::make_shared<Test_Credentials_Manager>();
511
               Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
4✔
512
               std::vector<Botan::TLS::Session_Handle> handles;
1✔
513

514
               handles.push_back(
1✔
515
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V12)).value());
4✔
516
               handles.push_back(
1✔
517
                  mgr.establish(default_session("AES_128_GCM_SHA256", cbs, Botan::TLS::Version_Code::TLS_V13)).value());
4✔
518

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

534
std::vector<Test::Result> test_session_manager_stateless() {
1✔
535
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
536
   Botan::TLS::Session_Manager_Stateless mgr(creds, Test::rng_as_shared());
3✔
537

538
   Session_Manager_Callbacks cbs;
1✔
539
   Session_Manager_Policy plcy;
1✔
540

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

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

557
      Botan_Tests::CHECK("establish without ticket key in credentials manager",
558
                         [&](auto& result) {
1✔
559
                            Botan::TLS::Session_Manager_Stateless local_mgr(
3✔
560
                               std::make_shared<Empty_Credentials_Manager>(), Test::rng_as_shared());
561

562
                            result.confirm("won't emit tickets", !local_mgr.emits_session_tickets());
3✔
563
                            auto ticket =
2✔
564
                               local_mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
1✔
565
                            result.confirm("returned std::nullopt", !ticket.has_value());
3✔
566
                         }),
1✔
567

568
      Botan_Tests::CHECK("retrieve via ticket",
569
                         [&](auto& result) {
1✔
570
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
571
                            auto ticket2 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
572
                            result.require("tickets created successfully", ticket1.has_value() && ticket2.has_value());
1✔
573

574
                            Botan::TLS::Session_Manager_Stateless local_mgr(creds, Test::rng_as_shared());
2✔
575
                            result.confirm("can retrieve ticket 1",
2✔
576
                                           mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
577
                            result.confirm("can retrieve ticket 2 from different manager but sam credentials",
2✔
578
                                           local_mgr.retrieve(ticket2.value(), cbs, plcy).has_value());
1✔
579
                         }),
3✔
580

581
      Botan_Tests::CHECK("retrieve via ID does not work",
582
                         [&](auto& result) {
1✔
583
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
584
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
585

586
                            result.confirm("retrieval by ID does not work",
2✔
587
                                           !mgr.retrieve(random_id(), cbs, plcy).has_value());
4✔
588
                         }),
1✔
589

590
      Botan_Tests::CHECK("retrieve via opaque handle does work",
591
                         [&](auto& result) {
1✔
592
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
593
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
594

595
                            result.confirm("retrieval by opaque handle",
2✔
596
                                           mgr.retrieve(ticket1->opaque_handle(), cbs, plcy).has_value());
3✔
597
                         }),
1✔
598

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

604
                            Botan::TLS::Session_Manager_Stateless local_mgr1(
3✔
605
                               std::make_shared<Empty_Credentials_Manager>(), Test::rng_as_shared());
606

607
                            Botan::TLS::Session_Manager_Stateless local_mgr2(
3✔
608
                               std::make_shared<Other_Test_Credentials_Manager>(), Test::rng_as_shared());
609

610
                            result.confirm("no successful retrieval (without key)",
2✔
611
                                           !local_mgr1.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
612
                            result.confirm("no successful retrieval (with wrong key)",
2✔
613
                                           !local_mgr2.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
614
                            result.confirm("successful retrieval",
2✔
615
                                           mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
1✔
616
                         }),
2✔
617

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

631
                            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
632
                            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
633
                            result.confirm("finding tickets does not work", mgr.find(server_info, cbs, plcy).empty());
3✔
634
                         }),
1✔
635

636
      Botan_Tests::CHECK(
637
         "remove is a NOOP",
638
         [&](auto& result) {
1✔
639
            auto ticket1 = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
640
            result.require("tickets created successfully", ticket1.has_value() && ticket1.has_value());
1✔
641

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

645
            result.test_is_eq("remove the ticket", mgr.remove_all(), size_t(0));
1✔
646
            result.confirm("successful retrieval 1", mgr.retrieve(ticket1.value(), cbs, plcy).has_value());
4✔
647
         }),
1✔
648

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

658
            result.test_is_eq(
1✔
659
               "timestamps match",
660
               std::chrono::duration_cast<std::chrono::seconds>(session_before.start_time().time_since_epoch()).count(),
×
661
               std::chrono::duration_cast<std::chrono::seconds>(session_after->start_time().time_since_epoch())
1✔
662
                  .count());
2✔
663
         }),
2✔
664
   };
11✔
665
}
2✔
666

667
std::vector<Test::Result> test_session_manager_hybrid() {
1✔
668
   auto creds = std::make_shared<Test_Credentials_Manager>();
1✔
669
   Session_Manager_Callbacks cbs;
1✔
670
   Session_Manager_Policy plcy;
1✔
671

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

692
      std::vector<Test::Result> results;
3✔
693
      using namespace std::placeholders;
694
      for(auto& factory_and_name : stateful_manager_factories) {
9✔
695
         auto& stateful_manager_name = factory_and_name.first;
6✔
696
         auto& stateful_manager_factory = factory_and_name.second;
6✔
697
         auto make_manager = [stateful_manager_factory, &creds](bool prefer_tickets) {
48✔
698
            return Botan::TLS::Session_Manager_Hybrid(
699
               stateful_manager_factory(), creds, Test::rng_as_shared(), prefer_tickets);
72✔
700
         };
701

702
         results.push_back(Botan_Tests::CHECK(std::string(name + " (" + stateful_manager_name + ")").c_str(),
24✔
703
                                              std::bind(lambda, make_manager, _1)));
704
      }
705
      return results;
3✔
706
   };
4✔
707

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

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

720
                   auto ticket3 = mgr_prefers_ids.establish(default_session(Botan::TLS::Connection_Side::Server, cbs),
4✔
721
                                                            std::nullopt,
722
                                                            true /* TLS 1.2 no ticket support */);
723
                   result.confirm("emits an ID", ticket3.has_value() && ticket3->is_id());
4✔
724

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

731
      CHECK_all("ticket vs ID preference in retrieval",
732
                [&](auto make_manager, auto& result) {
2✔
733
                   auto mgr_prefers_tickets = make_manager(true);
2✔
734
                   auto mgr_prefers_ids = make_manager(false);
2✔
735

736
                   auto id1 = mgr_prefers_tickets.underlying_stateful_manager()->establish(
4✔
737
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
4✔
738
                   auto id2 = mgr_prefers_ids.underlying_stateful_manager()->establish(
4✔
739
                      default_session(Botan::TLS::Connection_Side::Server, cbs));
740
                   auto ticket =
4✔
741
                      mgr_prefers_tickets.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
742

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

745
                   result.confirm("mgr1 + ID1", mgr_prefers_tickets.retrieve(id1.value(), cbs, plcy).has_value());
4✔
746
                   result.confirm("mgr1 + ID2", !mgr_prefers_tickets.retrieve(id2.value(), cbs, plcy).has_value());
4✔
747
                   result.confirm("mgr2 + ID1", !mgr_prefers_ids.retrieve(id1.value(), cbs, plcy).has_value());
4✔
748
                   result.confirm("mgr2 + ID2", mgr_prefers_ids.retrieve(id2.value(), cbs, plcy).has_value());
4✔
749
                   result.confirm("mgr1 + ticket", mgr_prefers_tickets.retrieve(ticket.value(), cbs, plcy).has_value());
4✔
750
                   result.confirm("mgr2 + ticket", mgr_prefers_ids.retrieve(ticket.value(), cbs, plcy).has_value());
6✔
751
                }),
6✔
752

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

762
                   result.confirm("does not emit tickets", !empty_mgr.emits_session_tickets());
4✔
763
                   result.confirm("does emit tickets 1", mgr_prefers_tickets.emits_session_tickets());
4✔
764
                   result.confirm("does emit tickets 2", mgr_prefers_ids.emits_session_tickets());
4✔
765
                }),
2✔
766
   });
7✔
767
}
2✔
768

769
namespace {
770

771
class Temporary_Database_File {
772
   private:
773
      std::string m_temp_file;
774

775
   public:
776
      Temporary_Database_File(const std::string& db_file) : m_temp_file(Test::data_file_as_temporary_copy(db_file)) {
1✔
777
         if(m_temp_file.empty()) {
1✔
778
            throw Test_Error("Failed to create temporary database file");
×
779
         }
780
      }
1✔
781

782
      ~Temporary_Database_File() {
1✔
783
   #if defined(BOTAN_TARGET_OS_HAS_FILESYSTEM) && defined(__cpp_lib_filesystem)
784
         if(!m_temp_file.empty()) {
1✔
785
            std::filesystem::remove(m_temp_file);
1✔
786
         }
787
   #endif
788
      }
1✔
789

790
      const std::string& get() const { return m_temp_file; }
791

792
      Temporary_Database_File(const Temporary_Database_File&) = delete;
793
      Temporary_Database_File& operator=(const Temporary_Database_File&) = delete;
794
      Temporary_Database_File(Temporary_Database_File&&) = delete;
795
      Temporary_Database_File& operator=(Temporary_Database_File&&) = delete;
796
};
797

798
}  // namespace
799

800
std::vector<Test::Result> test_session_manager_sqlite() {
1✔
801
   #if defined(BOTAN_HAS_TLS_SQLITE3_SESSION_MANAGER)
802
   Session_Manager_Callbacks cbs;
1✔
803
   Session_Manager_Policy plcy;
1✔
804

805
   return {
1✔
806
      Botan_Tests::CHECK(
807
         "migrate session database scheme (purges database)",
808
         [&](auto& result) {
1✔
809
            Temporary_Database_File dbfile("tls-sessions/botan-2.19.3.sqlite");
1✔
810

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

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

836
            result.test_is_eq("empty database won't get more empty", legacy_db.remove_all(), size_t(0));
2✔
837
         }),
1✔
838

839
      Botan_Tests::CHECK("clearing empty database",
840
                         [&](auto& result) {
1✔
841
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
842
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("empty.sqlite"));
1✔
843
                            result.test_eq("does not delete anything", mgr.remove_all(), 0);
2✔
844
                         }),
1✔
845

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

858
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
859
            result.require("establishment was successful", some_virtual_handle.has_value());
2✔
860
            result.require("session id was set", some_virtual_handle->id().has_value());
2✔
861
         }),
3✔
862

863
      Botan_Tests::CHECK(
864
         "retrieve session by ID",
865
         [&](auto& result) {
1✔
866
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
867
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("retrieve_by_id.sqlite"));
1✔
868
            auto some_random_id = random_id();
869
            auto some_random_handle =
2✔
870
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), some_random_id);
2✔
871
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
872

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

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

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

891
            auto session3 = mgr.retrieve(random_id(), cbs, plcy);
3✔
892
            result.confirm("random ID creates empty result", !session3.has_value());
3✔
893
         }),
6✔
894

895
      Botan_Tests::CHECK(
896
         "retrieval via ticket creates empty result",
897
         [&](auto& result) {
1✔
898
            Botan::TLS::Session_Manager_SQLite mgr(
4✔
899
               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("retrieve_by_ticket.sqlite"));
1✔
900
            auto some_random_handle =
2✔
901
               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id());
2✔
902
            auto some_virtual_handle = mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
2✔
903

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

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

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

926
      Botan_Tests::CHECK("removing sessions",
927
                         [&](auto& result) {
1✔
928
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
929
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("remove.sqlite"));
1✔
930
                            auto id = random_id();
931
                            auto ticket = random_ticket();
932
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), id);
2✔
933
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
2✔
934
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_id());
2✔
935
                            mgr.store(default_session(Botan::TLS::Connection_Side::Client, cbs), random_ticket());
2✔
936

937
                            result.test_is_eq("deletes one session by ID", mgr.remove(id), size_t(1));
4✔
938
                            result.test_is_eq("deletes one session by ticket", mgr.remove(ticket), size_t(1));
4✔
939

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

949
                            result.test_is_eq("removing the rest of the sessions", mgr.remove_all(), size_t(2));
1✔
950
                         }),
3✔
951

952
      Botan_Tests::CHECK("old sessions are purged when needed",
953
                         [&](auto& result) {
1✔
954
                            Botan::TLS::Session_Manager_SQLite mgr(
3✔
955
                               "thetruthisoutthere", Test::rng_as_shared(), Test::temp_file_name("purging.sqlite"), 1);
1✔
956

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

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

970
                            cbs.tick();
1✔
971
                            mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), ids[2]);
2✔
972
                            result.require("second ID is gone", !mgr.retrieve(ids[1], cbs, plcy).has_value());
4✔
973
                            result.require("new ID exists", mgr.retrieve(ids[2], cbs, plcy).has_value());
5✔
974

975
                            result.test_is_eq("only one entry exists", mgr.remove_all(), size_t(1));
1✔
976
                         }),
1✔
977

978
      Botan_Tests::CHECK("session purging can be disabled",
979
                         [&](auto& result) {
1✔
980
                            Botan::TLS::Session_Manager_SQLite mgr("thetruthisoutthere",
3✔
981
                                                                   Test::rng_as_shared(),
982
                                                                   Test::temp_file_name("purging.sqlite"),
1✔
983
                                                                   0 /* no pruning! */);
984

985
                            for(size_t i = 0; i < 25; ++i) {
26✔
986
                               mgr.establish(default_session(Botan::TLS::Connection_Side::Server, cbs), random_id());
50✔
987
                            }
988

989
                            result.test_is_eq("no entries were purged along the way", mgr.remove_all(), size_t(25));
2✔
990
                         }),
1✔
991
   };
10✔
992
   #else
993
   return {};
994
   #endif
995
}
1✔
996

997
std::vector<Test::Result> tls_session_manager_expiry() {
1✔
998
   Session_Manager_Callbacks cbs;
1✔
999
   Session_Manager_Policy plcy;
1✔
1000

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

1022
      std::vector<Test::Result> results;
5✔
1023
      results.reserve(stateful_manager_factories.size());
5✔
1024
      using namespace std::placeholders;
1025
      for(auto& [sub_name, factory] : stateful_manager_factories) {
20✔
1026
         results.push_back(Botan_Tests::CHECK(std::string(name + " (" + sub_name + ")").c_str(),
45✔
1027
                                              std::bind(lambda, sub_name, factory, _1)));
1028
      }
1029
      return results;
5✔
1030
   };
5✔
1031

1032
   return Test::flatten_result_lists({
5✔
1033
      CHECK_all("sessions expire",
1034
                [&](auto, auto factory, auto& result) {
3✔
1035
                   auto mgr = factory();
3✔
1036

1037
                   auto handle = mgr->establish(default_session(Botan::TLS::Connection_Side::Server, cbs));
6✔
1038
                   result.require("saved successfully", handle.has_value());
6✔
1039
                   result.require("session was found", mgr->retrieve(handle.value(), cbs, plcy).has_value());
6✔
1040
                   cbs.tick();
3✔
1041
                   result.confirm("session has expired", !mgr->retrieve(handle.value(), cbs, plcy).has_value());
9✔
1042
                   result.test_is_eq("session was deleted when it expired", mgr->remove_all(), size_t(0));
6✔
1043
                }),
6✔
1044

1045
         CHECK_all("expired sessions are not found",
1046
                   [&](const std::string& type, auto factory, auto& result) {
3✔
1047
                      if(type == "Stateless") {
3✔
1048
                         return;  // this manager can neither store nor find anything
1✔
1049
                      }
1050

1051
                      auto mgr = factory();
2✔
1052

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

1057
                      cbs.tick();
2✔
1058
                      auto handle_new = random_id();
1059
                      mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), handle_new);
6✔
1060
                      result.require("session was found", mgr->retrieve(handle_new, cbs, plcy).has_value());
10✔
1061

1062
                      auto sessions_and_handles = mgr->find(server_info, cbs, plcy);
2✔
1063
                      result.require("sessions are found", !sessions_and_handles.empty());
2✔
1064
                      result.test_is_eq("exactly one session is found", sessions_and_handles.size(), size_t(1));
4✔
1065
                      result.test_is_eq(
4✔
1066
                         "the new session is found", sessions_and_handles.front().handle.id().value(), handle_new);
4✔
1067

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

1071
         CHECK_all(
1072
            "session tickets are not reused",
1073
            [&](const std::string& type, auto factory, auto& result) {
3✔
1074
               if(type == "Stateless") {
3✔
1075
                  return;  // this manager can neither store nor find anything
1✔
1076
               }
1077

1078
               auto mgr = factory();
2✔
1079

1080
               auto handle_1 = random_id();
1081
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1082
                          handle_1);
1083
               auto handle_2 = random_ticket();
1084
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V12),
6✔
1085
                          handle_2);
1086

1087
   #if defined(BOTAN_HAS_TLS_13)
1088
               auto handle_3 = random_id();
1089
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1090
                          handle_3);
1091
               auto handle_4 = random_ticket();
1092
               mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs, Botan::TLS::Version_Code::TLS_V13),
6✔
1093
                          handle_4);
1094
   #endif
1095

1096
               plcy.allow_session_reuse = false;
2✔
1097

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

1101
               auto sessions_and_handles2 = mgr->find(server_info, cbs, plcy);
2✔
1102
               result.test_is_eq("only one session is found", sessions_and_handles2.size(), size_t(1));
4✔
1103
               result.confirm("found session is the Session_ID", sessions_and_handles2.front().handle.is_id());
6✔
1104
               result.test_is_eq(
4✔
1105
                  "found session is the Session_ID", sessions_and_handles2.front().handle.id().value(), handle_1);
6✔
1106
               result.confirm("found session is TLS 1.2",
4✔
1107
                              sessions_and_handles2.front().session.version().is_pre_tls_13());
2✔
1108
            }),
12✔
1109

1110
         CHECK_all(
1111
            "number of found tickets is capped",
1112
            [&](const std::string& type, auto factory, auto& result) {
3✔
1113
               if(type == "Stateless") {
3✔
1114
                  return;  // this manager can neither store nor find anything
1✔
1115
               }
1116

1117
               auto mgr = factory();
12✔
1118

1119
               std::array<Botan::TLS::Session_Ticket, 5> tickets;
1120
               for(auto& ticket : tickets) {
12✔
1121
                  ticket = random_ticket();
10✔
1122
                  mgr->store(default_session(Botan::TLS::Connection_Side::Client, cbs), ticket);
36✔
1123
               }
1124

1125
               plcy.allow_session_reuse = true;
6✔
1126

1127
               plcy.session_limit = 1;
2✔
1128
               result.test_is_eq("find one", mgr->find(server_info, cbs, plcy).size(), size_t(plcy.session_limit));
4✔
1129

1130
               plcy.session_limit = 3;
2✔
1131
               result.test_is_eq("find three", mgr->find(server_info, cbs, plcy).size(), size_t(plcy.session_limit));
4✔
1132

1133
               plcy.session_limit = 10;
2✔
1134
               result.test_is_eq("find all five", mgr->find(server_info, cbs, plcy).size(), size_t(5));
4✔
1135
            }),
4✔
1136

1137
   #if defined(BOTAN_HAS_TLS_13)
1138
         CHECK_all("expired tickets are not selected for PSK resumption", [&](auto, auto factory, auto& result) {
3✔
1139
            auto ticket = [&](const Botan::TLS::Session_Handle& handle) {
12✔
1140
               return Botan::TLS::Ticket(handle.opaque_handle(), 0);
12✔
1141
            };
1142

1143
            auto mgr = factory();
3✔
1144

1145
            auto old_handle = mgr->establish(
6✔
1146
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
3✔
1147
            cbs.tick();
3✔
1148
            auto new_handle = mgr->establish(
6✔
1149
               default_session(Botan::TLS::Connection_Side::Server, cbs, Botan::TLS::Version_Code::TLS_V13));
1150
            result.require("both sessions are stored", old_handle.has_value() && new_handle.has_value());
6✔
1151

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

1157
            cbs.tick();
3✔
1158

1159
            auto nothing = mgr->choose_from_offered_tickets(
18✔
1160
               std::vector{ticket(new_handle.value()), ticket(old_handle.value())}, "SHA-256", cbs, plcy);
6✔
1161
            result.require("all tickets are expired", !nothing.has_value());
6✔
1162
         }),
15✔
1163
   #endif
1164
   });
10✔
1165
}
1✔
1166

1167
}  // namespace
1168

1169
BOTAN_REGISTER_TEST_FN("tls",
1170
                       "tls_session_manager",
1171
                       test_session_manager_in_memory,
1172
                       test_session_manager_choose_ticket,
1173
                       test_session_manager_stateless,
1174
                       test_session_manager_hybrid,
1175
                       test_session_manager_sqlite,
1176
                       tls_session_manager_expiry);
1177

1178
}  // namespace Botan_Tests
1179

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