Merge pull request #119 from konrad-kruczynski/SslStreamNoTimeout
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / ClientSessionCache.cs
1 //
2 // ClientSessionCache.cs: Client-side cache for re-using sessions
3 //
4 // Author:
5 //      Sebastien Pouliot  <sebastien@ximian.com>
6 //
7 // Copyright (C) 2006 Novell (http://www.novell.com)
8 //
9 // Permission is hereby granted, free of charge, to any person obtaining
10 // a copy of this software and associated documentation files (the
11 // "Software"), to deal in the Software without restriction, including
12 // without limitation the rights to use, copy, modify, merge, publish,
13 // distribute, sublicense, and/or sell copies of the Software, and to
14 // permit persons to whom the Software is furnished to do so, subject to
15 // the following conditions:
16 // 
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
19 // 
20 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
21 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
22 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
24 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
25 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
26 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
27 //
28
29 using System;
30 using System.Collections;
31
32 namespace Mono.Security.Protocol.Tls {
33
34         internal class ClientSessionInfo : IDisposable {
35
36                 // (by default) we keep this item valid for 3 minutes (if unused)
37                 private const int DefaultValidityInterval = 3 * 60;
38                 private static readonly int ValidityInterval;
39
40                 private bool disposed;
41                 private DateTime validuntil;
42                 private string host;
43
44                 // see RFC2246 - Section 7
45                 private byte[] sid;
46                 private byte[] masterSecret;
47
48                 static ClientSessionInfo ()
49                 {
50 #if MOONLIGHT
51                         ValidityInterval = DefaultValidityInterval;
52 #else
53                         string user_cache_timeout = Environment.GetEnvironmentVariable ("MONO_TLS_SESSION_CACHE_TIMEOUT");
54                         if (user_cache_timeout == null) {
55                                 ValidityInterval = DefaultValidityInterval;
56                         } else {
57                                 try {
58                                         ValidityInterval = Int32.Parse (user_cache_timeout);
59                                 }
60                                 catch {
61                                         ValidityInterval = DefaultValidityInterval;
62                                 }
63                         }
64 #endif
65                 }
66
67                 public ClientSessionInfo (string hostname, byte[] id)
68                 {
69                         host = hostname;
70                         sid = id;
71                         KeepAlive ();
72                 }
73
74                 ~ClientSessionInfo ()
75                 {
76                         Dispose (false);
77                 }
78
79
80                 public string HostName {
81                         get { return host; }
82                 }
83
84                 public byte[] Id {
85                         get { return sid; }
86                 }
87
88                 public bool Valid {
89                         get { return ((masterSecret != null) && (validuntil > DateTime.UtcNow)); }
90                 }
91
92
93                 public void GetContext (Context context)
94                 {
95                         CheckDisposed ();
96                         if (context.MasterSecret != null)
97                                 masterSecret = (byte[]) context.MasterSecret.Clone ();
98                 }
99
100                 public void SetContext (Context context)
101                 {
102                         CheckDisposed ();
103                         if (masterSecret != null)
104                                 context.MasterSecret = (byte[]) masterSecret.Clone ();
105                 }
106
107                 public void KeepAlive ()
108                 {
109                         CheckDisposed ();
110                         validuntil = DateTime.UtcNow.AddSeconds (ValidityInterval);
111                 }
112
113                 public void Dispose ()
114                 {
115                         Dispose (true);
116                         GC.SuppressFinalize (this);
117                 }
118
119                 private void Dispose (bool disposing)
120                 {
121                         if (!disposed) {
122                                 validuntil = DateTime.MinValue;
123                                 host = null;
124                                 sid = null;
125
126                                 if (masterSecret != null) {
127                                         Array.Clear (masterSecret, 0, masterSecret.Length);
128                                         masterSecret = null;
129                                 }
130                         }
131                         disposed = true;
132                 }
133
134                 private void CheckDisposed ()
135                 {
136                         if (disposed) {
137                                 string msg = Locale.GetText ("Cache session information were disposed.");
138                                 throw new ObjectDisposedException (msg);
139                         }
140                 }
141         }
142
143         // note: locking is aggressive but isn't used often (and we gain much more :)
144         internal class ClientSessionCache {
145
146                 static Hashtable cache;
147                 static object locker;
148
149                 static ClientSessionCache ()
150                 {
151                         cache = new Hashtable ();
152                         locker = new object ();
153                 }
154
155                 // note: we may have multiple connections with a host, so 
156                 // possibly multiple entries per host (each with a different 
157                 // id), so we do not use the host as the hashtable key
158                 static public void Add (string host, byte[] id)
159                 {
160                         lock (locker) {
161                                 string uid = BitConverter.ToString (id);
162                                 ClientSessionInfo si = (ClientSessionInfo) cache[uid];
163                                 if (si == null) {
164                                         cache.Add (uid, new ClientSessionInfo (host, id));
165                                 } else if (si.HostName == host) {
166                                         // we already have this and it's still valid
167                                         // on the server, so we'll keep it a little longer
168                                         si.KeepAlive ();
169                                 } else {
170                                         // it's very unlikely but the same session id 
171                                         // could be used by more than one host. In this
172                                         // case we replace the older one with the new one
173                                         si.Dispose ();
174                                         cache.Remove (uid);
175                                         cache.Add (uid, new ClientSessionInfo (host, id));
176                                 }
177                         }
178                 }
179
180                 // return the first session us
181                 static public byte[] FromHost (string host)
182                 {
183                         lock (locker) {
184                                 foreach (ClientSessionInfo si in cache.Values) {
185                                         if (si.HostName == host) {
186                                                 if (si.Valid) {
187                                                         // ensure it's still valid when we really need it
188                                                         si.KeepAlive ();
189                                                         return si.Id;
190                                                 }
191                                         }
192                                 }
193                                 return null;
194                         }
195                 }
196
197                 // only called inside the lock
198                 static private ClientSessionInfo FromContext (Context context, bool checkValidity)
199                 {
200                         if (context == null)
201                                 return null;
202
203                         byte[] id = context.SessionId;
204                         if ((id == null) || (id.Length == 0))
205                                 return null;
206
207                         // do we have a session cached for this host ?
208                         string uid = BitConverter.ToString (id);
209
210                         ClientSessionInfo si = (ClientSessionInfo) cache[uid];
211                         if (si == null)
212                                 return null;
213
214                         // In the unlikely case of multiple hosts using the same 
215                         // session id, we just act like we do not know about it
216                         if (context.ClientSettings.TargetHost != si.HostName)
217                                 return null;
218
219                         // yes, so what's its status ?
220                         if (checkValidity && !si.Valid) {
221                                 si.Dispose ();
222                                 cache.Remove (uid);
223                                 return null;
224                         }
225
226                         // ok, it make sense
227                         return si;
228                 }
229
230                 static public bool SetContextInCache (Context context)
231                 {
232                         lock (locker) {
233                                 // Don't check the validity because the masterKey of the ClientSessionInfo
234                                 // can still be null when this is called the first time
235                                 ClientSessionInfo csi = FromContext (context, false);
236                                 if (csi == null)
237                                         return false;
238
239                                 csi.GetContext (context);
240                                 csi.KeepAlive ();
241                                 return true;
242                         }
243                 }
244
245                 static public bool SetContextFromCache (Context context)
246                 {
247                         lock (locker) {
248                                 ClientSessionInfo csi = FromContext (context, true);
249                                 if (csi == null)
250                                         return false;
251
252                                 csi.SetContext (context);
253                                 csi.KeepAlive ();
254                                 return true;
255                         }
256                 }
257         }
258 }