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

randombit / botan / 21851334357

10 Feb 2026 01:47AM UTC coverage: 90.073% (+0.004%) from 90.069%
21851334357

push

github

web-flow
Merge pull request #5296 from randombit/jack/tls-header-patrol

Various changes to reduce header dependencies in TLS

102230 of 113497 relevant lines covered (90.07%)

11359047.13 hits per line

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

79.75
/src/lib/tls/sessions_sql/tls_session_manager_sql.cpp
1
/*
2
* SQL TLS Session Manager
3
* (C) 2012,2014 Jack Lloyd
4
*
5
* Botan is released under the Simplified BSD License (see license.txt)
6
*/
7

8
#include <botan/tls_session_manager_sql.h>
9

10
#include <botan/database.h>
11
#include <botan/hex.h>
12
#include <botan/pwdhash.h>
13
#include <botan/rng.h>
14
#include <botan/tls_session.h>
15
#include <botan/internal/loadstor.h>
16
#include <chrono>
17

18
namespace Botan::TLS {
19

20
Session_Manager_SQL::Session_Manager_SQL(std::shared_ptr<SQL_Database> db,
22✔
21
                                         std::string_view passphrase,
22
                                         const std::shared_ptr<RandomNumberGenerator>& rng,
23
                                         size_t max_sessions) :
22✔
24
      Session_Manager(rng), m_db(std::move(db)), m_max_sessions(max_sessions) {
22✔
25
   create_or_migrate_and_open(passphrase);
22✔
26
}
22✔
27

28
void Session_Manager_SQL::create_or_migrate_and_open(std::string_view passphrase) {
22✔
29
   switch(detect_schema_revision()) {
22✔
30
      case CORRUPTED:
22✔
31
      case PRE_BOTAN_3_0:
22✔
32
      case EMPTY:
22✔
33
         // Legacy sessions before Botan 3.0 are simply dropped, no actual
34
         // migration is implemented. Same for apparently corrupt databases.
35
         m_db->exec("DROP TABLE IF EXISTS tls_sessions");
22✔
36
         m_db->exec("DROP TABLE IF EXISTS tls_sessions_metadata");
22✔
37
         create_with_latest_schema(passphrase, BOTAN_3_0);
22✔
38
         break;
22✔
39
      case BOTAN_3_0:
×
40
         initialize_existing_database(passphrase);
×
41
         break;
×
42
      default:
×
43
         throw Internal_Error("TLS session db has unknown database schema");
×
44
   }
45
}
22✔
46

47
Session_Manager_SQL::Schema_Revision Session_Manager_SQL::detect_schema_revision() {
22✔
48
   try {
22✔
49
      const auto meta_data_rows = m_db->row_count("tls_sessions_metadata");
22✔
50
      if(meta_data_rows != 1) {
1✔
51
         return CORRUPTED;
52
      }
53
   } catch(const SQL_Database::SQL_DB_Error&) {
21✔
54
      return EMPTY;  // `tls_sessions_metadata` probably didn't exist at all
21✔
55
   }
21✔
56

57
   try {
1✔
58
      auto stmt = m_db->new_statement("SELECT database_revision FROM tls_sessions_metadata");
1✔
59
      if(!stmt->step()) {
×
60
         throw Internal_Error("Failed to read revision of TLS session database");
×
61
      }
62
      return Schema_Revision(stmt->get_size_t(0));
×
63
   } catch(const SQL_Database::SQL_DB_Error&) {
1✔
64
      return PRE_BOTAN_3_0;  // `database_revision` did not exist yet -> preparing the statement failed
1✔
65
   }
1✔
66
}
67

68
void Session_Manager_SQL::create_with_latest_schema(std::string_view passphrase, Schema_Revision rev) {
22✔
69
   m_db->create_table(
22✔
70
      "CREATE TABLE tls_sessions "
71
      "("
72
      "session_id TEXT PRIMARY KEY, "
73
      "session_ticket BLOB, "
74
      "session_start INTEGER, "
75
      "hostname TEXT, "
76
      "hostport INTEGER, "
77
      "session BLOB NOT NULL"
78
      ")");
79

80
   m_db->create_table(
22✔
81
      "CREATE TABLE tls_sessions_metadata "
82
      "("
83
      "passphrase_salt BLOB, "
84
      "passphrase_iterations INTEGER, "
85
      "passphrase_check INTEGER, "
86
      "password_hash_family TEXT, "
87
      "database_revision INTEGER"
88
      ")");
89

90
   // speeds up lookups on session_tickets when deleting
91
   m_db->create_table("CREATE INDEX tls_tickets ON tls_sessions (session_ticket)");
22✔
92

93
   auto salt = m_rng->random_vec<std::vector<uint8_t>>(16);
22✔
94

95
   secure_vector<uint8_t> derived_key(32 + 2);
22✔
96

97
   const std::string pbkdf_name = "PBKDF2(SHA-512)";
22✔
98
   auto pbkdf_fam = PasswordHashFamily::create_or_throw(pbkdf_name);
22✔
99

100
   auto desired_runtime = std::chrono::milliseconds(100);
22✔
101
   auto pbkdf = pbkdf_fam->tune(derived_key.size(), desired_runtime);
22✔
102

103
   pbkdf->derive_key(
22✔
104
      derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.data(), salt.size());
22✔
105

106
   const size_t iterations = pbkdf->iterations();
22✔
107
   const size_t check_val = make_uint16(derived_key[0], derived_key[1]);
22✔
108
   m_session_key = SymmetricKey(std::span(derived_key).subspan(2));
22✔
109

110
   auto stmt = m_db->new_statement("INSERT INTO tls_sessions_metadata VALUES (?1, ?2, ?3, ?4, ?5)");
22✔
111

112
   stmt->bind(1, salt);
22✔
113
   stmt->bind(2, iterations);
22✔
114
   stmt->bind(3, check_val);
22✔
115
   stmt->bind(4, pbkdf_name);
22✔
116
   stmt->bind(5, rev);
22✔
117

118
   stmt->spin();
22✔
119
}
110✔
120

121
void Session_Manager_SQL::initialize_existing_database(std::string_view passphrase) {
×
122
   auto stmt = m_db->new_statement("SELECT * FROM tls_sessions_metadata");
×
123
   if(!stmt->step()) {
×
124
      throw Internal_Error("Failed to initialize TLS session database");
×
125
   }
126

127
   const std::pair<const uint8_t*, size_t> salt = stmt->get_blob(0);
×
128
   const size_t iterations = stmt->get_size_t(1);
×
129
   const size_t check_val_db = stmt->get_size_t(2);
×
130
   const std::string pbkdf_name = stmt->get_str(3);
×
131

132
   secure_vector<uint8_t> derived_key(32 + 2);
×
133

134
   auto pbkdf_fam = PasswordHashFamily::create_or_throw(pbkdf_name);
×
135
   auto pbkdf = pbkdf_fam->from_params(iterations);
×
136

137
   pbkdf->derive_key(
×
138
      derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.first, salt.second);
×
139

140
   const size_t check_val_created = make_uint16(derived_key[0], derived_key[1]);
×
141

142
   if(check_val_created != check_val_db) {
×
143
      throw Invalid_Argument("Session database password not valid");
×
144
   }
145

146
   m_session_key = SymmetricKey(std::span(derived_key).subspan(2));
×
147
}
×
148

149
void Session_Manager_SQL::store(const Session& session, const Session_Handle& handle) {
236✔
150
   std::optional<lock_guard_type<recursive_mutex_type>> lk;
236✔
151
   if(!database_is_threadsafe()) {
236✔
152
      lk.emplace(mutex());
×
153
   }
154

155
   if(session.server_info().hostname().empty()) {
472✔
156
      return;
×
157
   }
158

159
   auto stmt = m_db->new_statement(
236✔
160
      "INSERT OR REPLACE INTO tls_sessions"
161
      " VALUES (?1, ?2, ?3, ?4, ?5, ?6)");
236✔
162

163
   // Generate a random session ID if the peer did not provide one. Note that
164
   // this ID will not be returned on ::find(), as the ticket is preferred.
165
   const auto id = handle.id().value_or(m_rng->random_vec<Session_ID>(32));
698✔
166
   const auto ticket = handle.ticket().value_or(Session_Ticket());
472✔
167

168
   stmt->bind(1, hex_encode(id.get()));
236✔
169
   stmt->bind(2, ticket.get());
236✔
170
   stmt->bind(3, session.start_time());
236✔
171
   stmt->bind(4, session.server_info().hostname());
472✔
172
   stmt->bind(5, session.server_info().port());
236✔
173
   stmt->bind(6, session.encrypt(m_session_key, *m_rng));
236✔
174

175
   stmt->spin();
236✔
176

177
   prune_session_cache();
236✔
178
}
708✔
179

180
std::optional<Session> Session_Manager_SQL::retrieve_one(const Session_Handle& handle) {
36✔
181
   std::optional<lock_guard_type<recursive_mutex_type>> lk;
36✔
182
   if(!database_is_threadsafe()) {
36✔
183
      lk.emplace(mutex());
×
184
   }
185

186
   if(auto session_id = handle.id()) {
36✔
187
      auto stmt = m_db->new_statement("SELECT session FROM tls_sessions WHERE session_id = ?1");
34✔
188

189
      stmt->bind(1, hex_encode(session_id->get()));
34✔
190

191
      while(stmt->step()) {
34✔
192
         const std::pair<const uint8_t*, size_t> blob = stmt->get_blob(0);
21✔
193

194
         try {
21✔
195
            return Session::decrypt(blob.first, blob.second, m_session_key);
21✔
196
         } catch(...) {}
×
197
      }
198
   }
34✔
199

200
   return std::nullopt;
15✔
201
}
36✔
202

203
std::vector<Session_with_Handle> Session_Manager_SQL::find_some(const Server_Information& info,
102✔
204
                                                                const size_t max_sessions_hint) {
205
   std::optional<lock_guard_type<recursive_mutex_type>> lk;
102✔
206
   if(!database_is_threadsafe()) {
102✔
207
      lk.emplace(mutex());
×
208
   }
209

210
   auto stmt = m_db->new_statement(
102✔
211
      "SELECT session_id, session_ticket, session FROM tls_sessions"
212
      " WHERE hostname = ?1 AND hostport = ?2"
213
      " ORDER BY session_start DESC"
214
      " LIMIT ?3");
102✔
215

216
   stmt->bind(1, info.hostname());
204✔
217
   stmt->bind(2, info.port());
102✔
218
   stmt->bind(3, max_sessions_hint);
102✔
219

220
   std::vector<Session_with_Handle> found_sessions;
102✔
221
   while(stmt->step()) {
210✔
222
      auto handle = [&]() -> Session_Handle {
324✔
223
         auto ticket_blob = stmt->get_blob(1);
108✔
224
         if(ticket_blob.second > 0) {
108✔
225
            return Session_Handle(Session_Ticket(std::span(ticket_blob.first, ticket_blob.second)));
26✔
226
         } else {
227
            return Session_Handle(Session_ID(Botan::hex_decode(stmt->get_str(0))));
190✔
228
         }
229
      }();
108✔
230

231
      const std::pair<const uint8_t*, size_t> blob = stmt->get_blob(2);
108✔
232

233
      try {
108✔
234
         found_sessions.emplace_back(
216✔
235
            Session_with_Handle{Session::decrypt(blob.first, blob.second, m_session_key), std::move(handle)});
108✔
236
      } catch(...) {}
×
237
   }
108✔
238

239
   return found_sessions;
102✔
240
}
102✔
241

242
size_t Session_Manager_SQL::remove(const Session_Handle& handle) {
9✔
243
   // The number of deleted rows is taken globally from the database connection,
244
   // therefore we need to serialize this implementation.
245
   const lock_guard_type<recursive_mutex_type> lk(mutex());
9✔
246

247
   if(const auto id = handle.id()) {
9✔
248
      auto stmt = m_db->new_statement("DELETE FROM tls_sessions WHERE session_id = ?1");
6✔
249
      stmt->bind(1, hex_encode(id->get()));
6✔
250
      stmt->spin();
6✔
251
   } else if(const auto ticket = handle.ticket()) {
9✔
252
      auto stmt = m_db->new_statement("DELETE FROM tls_sessions WHERE session_ticket = ?1");
3✔
253
      stmt->bind(1, ticket->get());
3✔
254
      stmt->spin();
3✔
255
   } else {
3✔
256
      // should not happen, as session handles are exclusively either an ID or a ticket
257
      throw Invalid_Argument("provided a session handle that is neither ID nor ticket");
×
258
   }
3✔
259

260
   return m_db->rows_changed_by_last_statement();
9✔
261
}
9✔
262

263
size_t Session_Manager_SQL::remove_all() {
13✔
264
   // The number of deleted rows is taken globally from the database connection,
265
   // therefore we need to serialize this implementation.
266
   const lock_guard_type<recursive_mutex_type> lk(mutex());
13✔
267

268
   m_db->exec("DELETE FROM tls_sessions");
13✔
269
   return m_db->rows_changed_by_last_statement();
13✔
270
}
13✔
271

272
void Session_Manager_SQL::prune_session_cache() {
236✔
273
   // internal API: assuming that the lock is held already if needed
274

275
   if(m_max_sessions == 0) {
236✔
276
      return;
25✔
277
   }
278

279
   auto remove_oldest = m_db->new_statement(
211✔
280
      "DELETE FROM tls_sessions WHERE session_id NOT IN "
281
      "(SELECT session_id FROM tls_sessions ORDER BY session_start DESC LIMIT ?1)");
211✔
282
   remove_oldest->bind(1, m_max_sessions);
211✔
283
   remove_oldest->spin();
211✔
284
}
211✔
285

286
}  // namespace Botan::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

© 2026 Coveralls, Inc