2 // ClientSessionCache.cs: Client-side cache for re-using sessions
5 // Sebastien Pouliot <sebastien@ximian.com>
7 // Copyright (C) 2006 Novell (http://www.novell.com)
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:
17 // The above copyright notice and this permission notice shall be
18 // included in all copies or substantial portions of the Software.
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.
30 using System
.Collections
;
32 namespace Mono
.Security
.Protocol
.Tls
{
34 internal class ClientSessionInfo
: IDisposable
{
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
;
40 private bool disposed
;
41 private DateTime validuntil
;
44 // see RFC2246 - Section 7
46 private byte[] masterSecret
;
48 static ClientSessionInfo ()
50 string user_cache_timeout
= Environment
.GetEnvironmentVariable ("MONO_TLS_SESSION_CACHE_TIMEOUT");
51 if (user_cache_timeout
== null) {
52 ValidityInterval
= DefaultValidityInterval
;
55 ValidityInterval
= Int32
.Parse (user_cache_timeout
);
58 ValidityInterval
= DefaultValidityInterval
;
63 public ClientSessionInfo (string hostname
, byte[] id
)
76 public string HostName
{
85 get { return ((masterSecret != null) && (validuntil > DateTime.UtcNow)); }
89 public void GetContext (Context context
)
92 if (context
.MasterSecret
!= null)
93 masterSecret
= (byte[]) context
.MasterSecret
.Clone ();
96 public void SetContext (Context context
)
99 if (masterSecret
!= null)
100 context
.MasterSecret
= (byte[]) masterSecret
.Clone ();
103 public void KeepAlive ()
106 validuntil
= DateTime
.UtcNow
.AddSeconds (ValidityInterval
);
109 public void Dispose ()
112 GC
.SuppressFinalize (this);
115 private void Dispose (bool disposing
)
118 validuntil
= DateTime
.MinValue
;
122 if (masterSecret
!= null) {
123 Array
.Clear (masterSecret
, 0, masterSecret
.Length
);
130 private void CheckDisposed ()
133 string msg
= Locale
.GetText ("Cache session information were disposed.");
134 throw new ObjectDisposedException (msg
);
139 // note: locking is aggressive but isn't used often (and we gain much more :)
140 internal class ClientSessionCache
{
142 static Hashtable cache
;
143 static object locker
;
145 static ClientSessionCache ()
147 cache
= new Hashtable ();
148 locker
= new object ();
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
154 static public void Add (string host
, byte[] id
)
157 string uid
= BitConverter
.ToString (id
);
158 ClientSessionInfo si
= (ClientSessionInfo
) cache
[uid
];
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
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
171 cache
.Add (uid
, new ClientSessionInfo (host
, id
));
176 // return the first session us
177 static public byte[] FromHost (string host
)
180 foreach (ClientSessionInfo si
in cache
.Values
) {
181 if (si
.HostName
== host
) {
183 // ensure it's still valid when we really need it
193 // only called inside the lock
194 static private ClientSessionInfo
FromContext (Context context
, bool checkValidity
)
199 byte[] id
= context
.SessionId
;
200 if ((id
== null) || (id
.Length
== 0))
203 // do we have a session cached for this host ?
204 string uid
= BitConverter
.ToString (id
);
206 ClientSessionInfo si
= (ClientSessionInfo
) cache
[uid
];
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
)
215 // yes, so what's its status ?
216 if (checkValidity
&& !si
.Valid
) {
226 static public bool SetContextInCache (Context context
)
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);
235 csi
.GetContext (context
);
241 static public bool SetContextFromCache (Context context
)
244 ClientSessionInfo csi
= FromContext (context
, true);
248 csi
.SetContext (context
);