8#include <botan/tls_session_manager_sql.h>
10#include <botan/database.h>
12#include <botan/pwdhash.h>
14#include <botan/tls_session.h>
15#include <botan/internal/loadstor.h>
20 std::string_view passphrase,
21 const std::shared_ptr<RandomNumberGenerator>& rng,
22 size_t max_sessions) :
23 Session_Manager(rng), m_db(std::move(db)), m_max_sessions(max_sessions) {
24 create_or_migrate_and_open(passphrase);
27void Session_Manager_SQL::create_or_migrate_and_open(std::string_view passphrase) {
28 switch(detect_schema_revision()) {
34 m_db->exec(
"DROP TABLE IF EXISTS tls_sessions");
35 m_db->exec(
"DROP TABLE IF EXISTS tls_sessions_metadata");
36 create_with_latest_schema(passphrase, BOTAN_3_0);
39 initialize_existing_database(passphrase);
42 throw Internal_Error(
"TLS session db has unknown database schema");
46Session_Manager_SQL::Schema_Revision Session_Manager_SQL::detect_schema_revision() {
48 const auto meta_data_rows = m_db->row_count(
"tls_sessions_metadata");
49 if(meta_data_rows != 1) {
52 }
catch(
const SQL_Database::SQL_DB_Error&) {
57 auto stmt = m_db->select(
"database_revision",
"tls_sessions_metadata");
59 throw Internal_Error(
"Failed to read revision of TLS session database");
61 return Schema_Revision(stmt->get_size_t(0));
62 }
catch(
const SQL_Database::SQL_DB_Error&) {
67void Session_Manager_SQL::create_with_latest_schema(std::string_view passphrase, Schema_Revision rev) {
68 using DB = SQL_Database;
69 const auto blob = DB::Column_Type::Blob;
70 const auto str = DB::Column_Type::String;
71 const auto integer = DB::Column_Type::Integer;
73 m_db->create_table(DB::Table_Schema(
"tls_sessions",
75 DB::Column(
"session_id", str).primary_key(),
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(),
83 m_db->create_table(DB::Table_Schema(
"tls_sessions_metadata",
85 DB::Column(
"passphrase_salt", blob).not_null(),
86 DB::Column(
"passphrase_iterations", integer).not_null(),
87 DB::Column(
"passphrase_check", integer).not_null(),
88 DB::Column(
"password_hash_family", str).not_null(),
89 DB::Column(
"database_revision", integer).not_null(),
93 m_db->exec(
"CREATE INDEX tls_tickets ON tls_sessions (session_ticket)");
95 auto salt =
m_rng->random_vec<std::vector<uint8_t>>(16);
99 const std::string pbkdf_name =
"PBKDF2(SHA-512)";
102 constexpr uint32_t desired_runtime_msec = 100;
103 auto pbkdf = pbkdf_fam->tune_params(derived_key.size(), desired_runtime_msec);
106 derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.data(), salt.size());
108 const size_t iterations = pbkdf->iterations();
109 const size_t check_val =
make_uint16(derived_key[0], derived_key[1]);
110 m_session_key =
SymmetricKey(std::span(derived_key).subspan(2));
112 auto stmt = m_db->new_statement(
"INSERT INTO tls_sessions_metadata VALUES (?1, ?2, ?3, ?4, ?5)");
115 stmt->bind(2, iterations);
116 stmt->bind(3, check_val);
117 stmt->bind(4, pbkdf_name);
123void Session_Manager_SQL::initialize_existing_database(std::string_view passphrase) {
124 auto stmt = m_db->select(
"*",
"tls_sessions_metadata");
126 throw Internal_Error(
"Failed to initialize TLS session database");
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();
137 auto pbkdf = pbkdf_fam->from_params(iterations);
140 derived_key.data(), derived_key.size(), passphrase.data(), passphrase.size(), salt.data(), salt.size());
142 const size_t check_val_created =
make_uint16(derived_key[0], derived_key[1]);
144 if(check_val_created != check_val_db) {
145 throw Invalid_Argument(
"Session database password not valid");
148 m_session_key =
SymmetricKey(std::span(derived_key).subspan(2));
152 std::optional<lock_guard_type<recursive_mutex_type>> lk;
161 auto stmt = m_db->upsert(
"tls_sessions",
162 {
"session_id",
"session_ticket",
"session_start",
"hostname",
"hostport",
"session"});
170 stmt->bind(2, ticket.get());
178 prune_session_cache();
182 std::optional<lock_guard_type<recursive_mutex_type>> lk;
187 if(
auto session_id = handle.
id()) {
188 auto stmt = m_db->select(
"session",
"tls_sessions",
"session_id = ?1");
192 while(stmt->step()) {
203 const size_t max_sessions_hint) {
204 std::optional<lock_guard_type<recursive_mutex_type>> lk;
209 auto stmt = m_db->new_statement(
210 "SELECT session_id, session_ticket, session FROM tls_sessions"
211 " WHERE hostname = ?1 AND hostport = ?2"
212 " ORDER BY session_start DESC"
216 stmt->bind(2, info.
port());
217 stmt->bind(3, max_sessions_hint);
219 std::vector<Session_with_Handle> found_sessions;
220 while(stmt->step()) {
222 auto ticket_blob = stmt->get_blob(1);
223 if(!ticket_blob.empty()) {
231 found_sessions.emplace_back(
236 return found_sessions;
244 if(
const auto id = handle.
id()) {
245 auto stmt = m_db->new_statement(
"DELETE FROM tls_sessions WHERE session_id = ?1");
248 }
else if(
const auto ticket = handle.
ticket()) {
249 auto stmt = m_db->new_statement(
"DELETE FROM tls_sessions WHERE session_ticket = ?1");
250 stmt->bind(1, ticket->get());
254 throw Invalid_Argument(
"provided a session handle that is neither ID nor ticket");
257 return m_db->rows_changed_by_last_statement();
265 m_db->exec(
"DELETE FROM tls_sessions");
266 return m_db->rows_changed_by_last_statement();
269void Session_Manager_SQL::prune_session_cache() {
272 if(m_max_sessions == 0) {
276 auto remove_oldest = m_db->new_statement(
277 "DELETE FROM tls_sessions WHERE session_id NOT IN "
278 "(SELECT session_id FROM tls_sessions ORDER BY session_start DESC LIMIT ?1)");
279 remove_oldest->bind(1, m_max_sessions);
280 remove_oldest->spin();
static std::unique_ptr< PasswordHashFamily > create_or_throw(std::string_view algo_spec, std::string_view provider="")
std::chrono::system_clock::time_point start_time() const
const Server_Information & server_info() const
Helper class to embody a session handle in all protocol versions.
std::optional< Session_Ticket > ticket() const
std::optional< Session_ID > id() const
Session_Manager_SQL(std::shared_ptr< SQL_Database > db, std::string_view passphrase, const std::shared_ptr< RandomNumberGenerator > &rng, size_t max_sessions=1000)
void store(const Session &session, const Session_Handle &handle) override
Save a Session under a Session_Handle (TLS Client).
size_t remove(const Session_Handle &handle) override
std::vector< Session_with_Handle > find_some(const Server_Information &info, size_t max_sessions_hint) override
Internal retrieval function to find sessions to resume.
virtual bool database_is_threadsafe() const
std::optional< Session > retrieve_one(const Session_Handle &handle) override
Internal retrieval function for a single session.
size_t remove_all() override
recursive_mutex_type & mutex()
BOTAN_FUTURE_EXPLICIT Session_Manager(const std::shared_ptr< RandomNumberGenerator > &rng)
std::shared_ptr< RandomNumberGenerator > m_rng
std::vector< uint8_t > encrypt(const SymmetricKey &key, RandomNumberGenerator &rng) const
static Session decrypt(const uint8_t ctext[], size_t ctext_size, const SymmetricKey &key)
Strong< std::vector< uint8_t >, struct Session_ID_ > Session_ID
holds a TLS 1.2 session ID for stateful resumption
Strong< std::vector< uint8_t >, struct Session_Ticket_ > Session_Ticket
holds a TLS 1.2 session ticket for stateless resumption
void hex_encode(char output[], const uint8_t input[], size_t input_length, bool uppercase)
size_t hex_decode(uint8_t output[], const char input[], size_t input_length, size_t &input_consumed, bool ignore_ws)
std::vector< T, secure_allocator< T > > secure_vector
lock_guard< T > lock_guard_type
constexpr uint16_t make_uint16(uint8_t i0, uint8_t i1)