ClientSessionCache.cs 6.4 KB

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