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 }