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 ClientSessionInfo()48 static ClientSessionInfo () 49 { 50 string user_cache_timeout = Environment.GetEnvironmentVariable ("MONO_TLS_SESSION_CACHE_TIMEOUT"); 51 if (user_cache_timeout == null) { 52 ValidityInterval = DefaultValidityInterval; 53 } else { 54 try { 55 ValidityInterval = Int32.Parse (user_cache_timeout); 56 } 57 catch { 58 ValidityInterval = DefaultValidityInterval; 59 } 60 } 61 } 62 ClientSessionInfo(string hostname, byte[] id)63 public ClientSessionInfo (string hostname, byte[] id) 64 { 65 host = hostname; 66 sid = id; 67 KeepAlive (); 68 } 69 ~ClientSessionInfo()70 ~ClientSessionInfo () 71 { 72 Dispose (false); 73 } 74 75 76 public string HostName { 77 get { return host; } 78 } 79 80 public byte[] Id { 81 get { return sid; } 82 } 83 84 public bool Valid { 85 get { return ((masterSecret != null) && (validuntil > DateTime.UtcNow)); } 86 } 87 88 GetContext(Context context)89 public void GetContext (Context context) 90 { 91 CheckDisposed (); 92 if (context.MasterSecret != null) 93 masterSecret = (byte[]) context.MasterSecret.Clone (); 94 } 95 SetContext(Context context)96 public void SetContext (Context context) 97 { 98 CheckDisposed (); 99 if (masterSecret != null) 100 context.MasterSecret = (byte[]) masterSecret.Clone (); 101 } 102 KeepAlive()103 public void KeepAlive () 104 { 105 CheckDisposed (); 106 validuntil = DateTime.UtcNow.AddSeconds (ValidityInterval); 107 } 108 Dispose()109 public void Dispose () 110 { 111 Dispose (true); 112 GC.SuppressFinalize (this); 113 } 114 Dispose(bool disposing)115 private void Dispose (bool disposing) 116 { 117 if (!disposed) { 118 validuntil = DateTime.MinValue; 119 host = null; 120 sid = null; 121 122 if (masterSecret != null) { 123 Array.Clear (masterSecret, 0, masterSecret.Length); 124 masterSecret = null; 125 } 126 } 127 disposed = true; 128 } 129 CheckDisposed()130 private void CheckDisposed () 131 { 132 if (disposed) { 133 string msg = Locale.GetText ("Cache session information were disposed."); 134 throw new ObjectDisposedException (msg); 135 } 136 } 137 } 138 139 // note: locking is aggressive but isn't used often (and we gain much more :) 140 internal class ClientSessionCache { 141 142 static Hashtable cache; 143 static object locker; 144 ClientSessionCache()145 static ClientSessionCache () 146 { 147 cache = new Hashtable (); 148 locker = new object (); 149 } 150 151 // note: we may have multiple connections with a host, so 152 // possibly multiple entries per host (each with a different 153 // id), so we do not use the host as the hashtable key Add(string host, byte[] id)154 static public void Add (string host, byte[] id) 155 { 156 lock (locker) { 157 string uid = BitConverter.ToString (id); 158 ClientSessionInfo si = (ClientSessionInfo) cache[uid]; 159 if (si == null) { 160 cache.Add (uid, new ClientSessionInfo (host, id)); 161 } else if (si.HostName == host) { 162 // we already have this and it's still valid 163 // on the server, so we'll keep it a little longer 164 si.KeepAlive (); 165 } else { 166 // it's very unlikely but the same session id 167 // could be used by more than one host. In this 168 // case we replace the older one with the new one 169 si.Dispose (); 170 cache.Remove (uid); 171 cache.Add (uid, new ClientSessionInfo (host, id)); 172 } 173 } 174 } 175 176 // return the first session us FromHost(string host)177 static public byte[] FromHost (string host) 178 { 179 lock (locker) { 180 foreach (ClientSessionInfo si in cache.Values) { 181 if (si.HostName == host) { 182 if (si.Valid) { 183 // ensure it's still valid when we really need it 184 si.KeepAlive (); 185 return si.Id; 186 } 187 } 188 } 189 return null; 190 } 191 } 192 193 // only called inside the lock FromContext(Context context, bool checkValidity)194 static private ClientSessionInfo FromContext (Context context, bool checkValidity) 195 { 196 if (context == null) 197 return null; 198 199 byte[] id = context.SessionId; 200 if ((id == null) || (id.Length == 0)) 201 return null; 202 203 // do we have a session cached for this host ? 204 string uid = BitConverter.ToString (id); 205 206 ClientSessionInfo si = (ClientSessionInfo) cache[uid]; 207 if (si == null) 208 return null; 209 210 // In the unlikely case of multiple hosts using the same 211 // session id, we just act like we do not know about it 212 if (context.ClientSettings.TargetHost != si.HostName) 213 return null; 214 215 // yes, so what's its status ? 216 if (checkValidity && !si.Valid) { 217 si.Dispose (); 218 cache.Remove (uid); 219 return null; 220 } 221 222 // ok, it make sense 223 return si; 224 } 225 SetContextInCache(Context context)226 static public bool SetContextInCache (Context context) 227 { 228 lock (locker) { 229 // Don't check the validity because the masterKey of the ClientSessionInfo 230 // can still be null when this is called the first time 231 ClientSessionInfo csi = FromContext (context, false); 232 if (csi == null) 233 return false; 234 235 csi.GetContext (context); 236 csi.KeepAlive (); 237 return true; 238 } 239 } 240 SetContextFromCache(Context context)241 static public bool SetContextFromCache (Context context) 242 { 243 lock (locker) { 244 ClientSessionInfo csi = FromContext (context, true); 245 if (csi == null) 246 return false; 247 248 csi.SetContext (context); 249 csi.KeepAlive (); 250 return true; 251 } 252 } 253 } 254 } 255