/* * SQL TLS Session Manager * (C) 2012,2014 Jack Lloyd * * Botan is released under the Simplified BSD License (see license.txt) */ #include #include #include #include #include #include namespace Botan { namespace TLS { Session_Manager_SQL::Session_Manager_SQL(std::shared_ptr db, const std::string& passphrase, RandomNumberGenerator& rng, size_t max_sessions, std::chrono::seconds session_lifetime) : m_db(db), m_rng(rng), m_max_sessions(max_sessions), m_session_lifetime(session_lifetime) { m_db->create_table( "create table if not exists tls_sessions " "(" "session_id TEXT PRIMARY KEY, " "session_start INTEGER, " "hostname TEXT, " "hostport INTEGER, " "session BLOB" ")"); m_db->create_table( "create table if not exists tls_sessions_metadata " "(" "passphrase_salt BLOB, " "passphrase_iterations INTEGER, " "passphrase_check INTEGER " ")"); const size_t salts = m_db->row_count("tls_sessions_metadata"); std::unique_ptr pbkdf(get_pbkdf("PBKDF2(SHA-512)")); if(salts == 1) { // existing db auto stmt = m_db->new_statement("select * from tls_sessions_metadata"); if(stmt->step()) { std::pair salt = stmt->get_blob(0); const size_t iterations = stmt->get_size_t(1); const size_t check_val_db = stmt->get_size_t(2); secure_vector x = pbkdf->pbkdf_iterations(32 + 2, passphrase, salt.first, salt.second, iterations); const size_t check_val_created = make_uint16(x[0], x[1]); m_session_key.assign(x.begin() + 2, x.end()); if(check_val_created != check_val_db) throw Exception("Session database password not valid"); } } else { // maybe just zap the salts + sessions tables in this case? if(salts != 0) throw Exception("Seemingly corrupted database, multiple salts found"); // new database case std::vector salt = unlock(rng.random_vec(16)); size_t iterations = 0; secure_vector x = pbkdf->pbkdf_timed(32 + 2, passphrase, salt.data(), salt.size(), std::chrono::milliseconds(100), iterations); size_t check_val = make_uint16(x[0], x[1]); m_session_key.assign(x.begin() + 2, x.end()); auto stmt = m_db->new_statement("insert into tls_sessions_metadata values(?1, ?2, ?3)"); stmt->bind(1, salt); stmt->bind(2, iterations); stmt->bind(3, check_val); stmt->spin(); } } bool Session_Manager_SQL::load_from_session_id(const std::vector& session_id, Session& session) { auto stmt = m_db->new_statement("select session from tls_sessions where session_id = ?1"); stmt->bind(1, hex_encode(session_id)); while(stmt->step()) { std::pair blob = stmt->get_blob(0); try { session = Session::decrypt(blob.first, blob.second, m_session_key); return true; } catch(...) { } } return false; } bool Session_Manager_SQL::load_from_server_info(const Server_Information& server, Session& session) { auto stmt = m_db->new_statement("select session from tls_sessions" " where hostname = ?1 and hostport = ?2" " order by session_start desc"); stmt->bind(1, server.hostname()); stmt->bind(2, server.port()); while(stmt->step()) { std::pair blob = stmt->get_blob(0); try { session = Session::decrypt(blob.first, blob.second, m_session_key); return true; } catch(...) { } } return false; } void Session_Manager_SQL::remove_entry(const std::vector& session_id) { auto stmt = m_db->new_statement("delete from tls_sessions where session_id = ?1"); stmt->bind(1, hex_encode(session_id)); stmt->spin(); } size_t Session_Manager_SQL::remove_all() { auto stmt = m_db->new_statement("delete from tls_sessions"); return stmt->spin(); } void Session_Manager_SQL::save(const Session& session) { auto stmt = m_db->new_statement("insert or replace into tls_sessions" " values(?1, ?2, ?3, ?4, ?5)"); stmt->bind(1, hex_encode(session.session_id())); stmt->bind(2, session.start_time()); stmt->bind(3, session.server_info().hostname()); stmt->bind(4, session.server_info().port()); stmt->bind(5, session.encrypt(m_session_key, m_rng)); stmt->spin(); prune_session_cache(); } void Session_Manager_SQL::prune_session_cache() { // First expire old sessions auto remove_expired = m_db->new_statement("delete from tls_sessions where session_start <= ?1"); remove_expired->bind(1, std::chrono::system_clock::now() - m_session_lifetime); remove_expired->spin(); const size_t sessions = m_db->row_count("tls_sessions"); // Then if needed expire some more sessions at random if(sessions > m_max_sessions) { auto remove_some = m_db->new_statement("delete from tls_sessions where session_id in " "(select session_id from tls_sessions limit ?1)"); remove_some->bind(1, sessions - m_max_sessions); remove_some->spin(); } } } }