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

randombit / botan / 26141725099

19 May 2026 08:32PM UTC coverage: 89.343% (+0.009%) from 89.334%
26141725099

push

github

web-flow
Merge pull request #5609 from randombit/jack/improve-http

Improve the HTTP 1.0 client used for OCSP/CRL

109341 of 122383 relevant lines covered (89.34%)

11264402.07 hits per line

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

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

17
namespace Botan::TLS {
18

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

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

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

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

67
void Session_Manager_SQL::create_with_latest_schema(std::string_view passphrase, Schema_Revision rev) {
22✔
68
   using DB = SQL_Database;
22✔
69
   const auto blob = DB::Column_Type::Blob;
22✔
70
   const auto str = DB::Column_Type::String;
22✔
71
   const auto integer = DB::Column_Type::Integer;
22✔
72

73
   m_db->create_table(DB::Table_Schema("tls_sessions",
220✔
74
                                       {
75
                                          DB::Column("session_id", str).primary_key(),
66✔
76
                                          DB::Column("session_ticket", blob),
77
                                          DB::Column("session_start", integer),
78
                                          DB::Column("hostname", str),
79
                                          DB::Column("hostport", integer),
80
                                          DB::Column("session", blob).not_null(),
44✔
81
                                       }));
132✔
82

83
   m_db->create_table(DB::Table_Schema("tls_sessions_metadata",
198✔
84
                                       {
85
                                          DB::Column("passphrase_salt", blob).not_null(),
66✔
86
                                          DB::Column("passphrase_iterations", integer).not_null(),
66✔
87
                                          DB::Column("passphrase_check", integer).not_null(),
66✔
88
                                          DB::Column("password_hash_family", str).not_null(),
66✔
89
                                          DB::Column("database_revision", integer).not_null(),
66✔
90
                                       }));
110✔
91

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

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

97
   secure_vector<uint8_t> derived_key(32 + 2);
22✔
98

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

102
   constexpr uint32_t desired_runtime_msec = 100;
22✔
103
   auto pbkdf = pbkdf_fam->tune_params(derived_key.size(), desired_runtime_msec);
22✔
104

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

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

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

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

120
   stmt->spin();
22✔
121
}
418✔
122

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

129
   const auto salt = stmt->get_blob(0);
×
130
   const size_t iterations = stmt->get_size_t(1);
×
131
   const size_t check_val_db = stmt->get_size_t(2);
×
132
   const std::string pbkdf_name = stmt->get_str(3).value();
×
133

134
   secure_vector<uint8_t> derived_key(32 + 2);
×
135

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

139
   pbkdf->derive_key(
×
140
      derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.data(), salt.size());
141

142
   const size_t check_val_created = make_uint16(derived_key[0], derived_key[1]);
×
143

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

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

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

157
   if(session.server_info().hostname().empty()) {
478✔
158
      return;
×
159
   }
160

161
   auto stmt = m_db->upsert("tls_sessions",
239✔
162
                            {"session_id", "session_ticket", "session_start", "hostname", "hostport", "session"});
239✔
163

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

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

176
   stmt->spin();
239✔
177

178
   prune_session_cache();
239✔
179
}
717✔
180

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

187
   if(auto session_id = handle.id()) {
34✔
188
      auto stmt = m_db->select("session", "tls_sessions", "session_id = ?1");
32✔
189

190
      stmt->bind(1, hex_encode(session_id->get()));
32✔
191

192
      while(stmt->step()) {
32✔
193
         try {
18✔
194
            return Session::decrypt(stmt->get_blob(0), m_session_key);
36✔
195
         } catch(...) {}
×
196
      }
197
   }
32✔
198

199
   return std::nullopt;
16✔
200
}
34✔
201

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

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

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

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

230
      try {
108✔
231
         found_sessions.emplace_back(
216✔
232
            Session_with_Handle{Session::decrypt(stmt->get_blob(2), m_session_key), std::move(handle)});
108✔
233
      } catch(...) {}
×
234
   }
108✔
235

236
   return found_sessions;
102✔
237
}
102✔
238

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

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

257
   return m_db->rows_changed_by_last_statement();
9✔
258
}
9✔
259

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

265
   m_db->exec("DELETE FROM tls_sessions");
13✔
266
   return m_db->rows_changed_by_last_statement();
13✔
267
}
13✔
268

269
void Session_Manager_SQL::prune_session_cache() {
239✔
270
   // internal API: assuming that the lock is held already if needed
271

272
   if(m_max_sessions == 0) {
239✔
273
      return;
25✔
274
   }
275

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

283
}  // 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