1 /** 2 * TLS Session Manager 3 * 4 * Copyright: 5 * (C) 2011 Jack Lloyd 6 * (C) 2014-2015 Etienne Cimon 7 * 8 * License: 9 * Botan is released under the Simplified BSD License (see LICENSE.md) 10 */ 11 module botan.tls.session_manager; 12 13 import botan.constants; 14 static if (BOTAN_HAS_TLS): 15 16 public import botan.tls.session; 17 public import botan.tls.server_info; 18 public import botan.algo_base.sym_algo; 19 public import botan.rng.rng; 20 import botan.codec.hex; 21 import std.datetime; 22 import core.sync.mutex; 23 import std.datetime; 24 import memutils.hashmap; 25 import botan.tls.magic; 26 27 /** 28 * TLSSessionManager is an interface to systems which can save 29 * session parameters for supporting session resumption. 30 * 31 * Saving sessions is done on a best-effort basis; an implementation is 32 * allowed to drop sessions due to space constraints. 33 * 34 * Implementations should strive to be thread safe 35 */ 36 interface TLSSessionManager 37 { 38 public: 39 /** 40 * Try to load a saved session (using session ID) 41 * Params: 42 * session_id = the session identifier we are trying to resume 43 * session = will be set to the saved session data (if found), 44 or not modified if not found 45 * Returns: true if session was modified 46 */ 47 abstract bool loadFromSessionId(const ref Vector!ubyte session_id, ref TLSSession session); 48 49 /** 50 * Try to load a saved session (using info about server) 51 * Params: 52 * info = the information about the server 53 * session = will be set to the saved session data (if found), 54 or not modified if not found 55 * Returns: true if session was modified 56 */ 57 abstract bool loadFromServerInfo(in TLSServerInformation info, ref TLSSession session); 58 59 /** 60 * Remove this session id from the cache, if it exists 61 */ 62 abstract void removeEntry(const ref Vector!ubyte session_id); 63 64 65 /** 66 * Save a session on a best effort basis; the manager may not in 67 * fact be able to save the session for whatever reason; this is 68 * not an error. Caller cannot assume that calling save followed 69 * immediately by loadFrom* will result in a successful lookup. 70 * 71 * Params: 72 * session = to save 73 */ 74 abstract void save(in TLSSession session); 75 76 /** 77 * Return the allowed lifetime of a session; beyond this time, 78 * sessions are not resumed. Returns 0 if unknown/no explicit 79 * expiration policy. 80 */ 81 abstract Duration sessionLifetime() const; 82 83 } 84 85 /** 86 * An implementation of TLSSessionManager that does not save sessions at 87 * all, preventing session resumption. 88 */ 89 final class TLSSessionManagerNoop : TLSSessionManager 90 { 91 public: 92 override bool loadFromSessionId(const ref Vector!ubyte, ref TLSSession) 93 { return false; } 94 95 override bool loadFromServerInfo(in TLSServerInformation, ref TLSSession) 96 { return false; } 97 98 override void removeEntry(const ref Vector!ubyte) {} 99 100 override void save(in TLSSession) {} 101 102 override Duration sessionLifetime() const 103 { return Duration.init; } 104 } 105 106 /** 107 * An implementation of TLSSessionManager that saves values in memory. 108 */ 109 final class TLSSessionManagerInMemory : TLSSessionManager 110 { 111 public: 112 /** 113 * Params: 114 * rng = a random number generator 115 * max_sessions = a hint on the maximum number of sessions 116 * to keep in memory at any one time. (If zero, don't cap) 117 * session_lifetime = sessions are expired after this duration has elapsed from initial handshake. 118 */ 119 this(RandomNumberGenerator rng, size_t max_sessions = 1000, Duration session_lifetime = 7200.seconds) 120 { 121 m_max_sessions = max_sessions; 122 m_session_lifetime = session_lifetime; 123 m_rng = rng; 124 m_session_key = SymmetricKey(m_rng, 32); 125 126 } 127 128 override bool loadFromSessionId(const ref Vector!ubyte session_id, ref TLSSession session) 129 { 130 131 return loadFromSessionStr(hexEncode(session_id), session); 132 } 133 134 override bool loadFromServerInfo(in TLSServerInformation info, ref TLSSession session) 135 { 136 137 auto str = m_info_sessions.get(info); 138 139 if (!str) 140 return false; 141 142 if (loadFromSessionStr(str, session)) 143 return true; 144 145 /* 146 * It existed at one point but was removed from the sessions map, 147 * remove m_info_sessions entry as well 148 */ 149 m_info_sessions.remove(info); 150 151 return false; 152 } 153 154 override void removeEntry(const ref Vector!ubyte session_id) 155 { 156 auto key = hexEncode(session_id); 157 auto val = m_sessions.get(key); 158 159 if (val.length > 0) { 160 m_sessions.remove(key); 161 removeFromOrdered(key); 162 } 163 } 164 165 override void save(in TLSSession session) 166 { 167 168 // make some space if too many sessions are found 169 if (m_max_sessions != 0 && m_sessions.length >= m_max_sessions) 170 { 171 int to_remove = cast(int)(m_sessions.length - m_max_sessions); 172 173 foreach (sess_id; m_sessions_ordered[0 .. to_remove]) 174 if (sess_id in m_sessions) m_sessions.remove(sess_id); 175 176 m_sessions_ordered = Vector!string(m_sessions_ordered[to_remove .. $][]); 177 } 178 179 const string session_id_str = hexEncode(session.sessionId()); 180 181 m_sessions[session_id_str] = session.encrypt(m_session_key, m_rng).dupr; 182 m_sessions_ordered ~= session_id_str; 183 184 if (session.side() == CLIENT && !session.serverInfo().empty) 185 m_info_sessions[session.serverInfo()] = session_id_str; 186 } 187 188 override Duration sessionLifetime() const 189 { return m_session_lifetime; } 190 191 private: 192 bool loadFromSessionStr(in string session_str, ref TLSSession session) 193 { 194 // assert(lock is held) 195 196 auto val = m_sessions.get(session_str, Array!ubyte.init); 197 198 if (val == Array!ubyte.init) 199 return false; 200 201 try 202 { 203 session = TLSSession.decrypt(*val, m_session_key); 204 } 205 catch (Exception) 206 { 207 return false; 208 } 209 210 // if session has expired, remove it 211 const auto now = Clock.currTime(UTC()); 212 213 if (session.startTime() + sessionLifetime() < now) 214 { 215 m_sessions.remove(session_str); 216 removeFromOrdered(session_str); 217 return false; 218 } 219 220 return true; 221 } 222 223 void removeFromOrdered(string val) { 224 225 import std.algorithm : countUntil; 226 auto i = m_sessions_ordered[].countUntil(val); 227 228 if (i != (m_sessions_ordered.length - 1)) { 229 auto tmp = m_sessions_ordered.ptr[i+1 .. m_sessions_ordered.length]; 230 m_sessions_ordered[] = Vector!string(m_sessions_ordered[0 .. i]); 231 m_sessions_ordered ~= tmp; 232 } 233 else 234 m_sessions_ordered.length = m_sessions_ordered.length - 1; 235 236 } 237 238 size_t m_max_sessions; 239 240 Duration m_session_lifetime; 241 242 RandomNumberGenerator m_rng; 243 SymmetricKey m_session_key; 244 245 HashMap!(string, Array!ubyte) m_sessions; // hex(session_id) . session 246 Vector!string m_sessions_ordered; 247 HashMap!(TLSServerInformation, string) m_info_sessions; 248 }