1 //-----------------------------------------------------------------------------
2 // Copyright (c) Microsoft Corporation.  All rights reserved.
3 //-----------------------------------------------------------------------------
4 
5 namespace System.ServiceModel.Security
6 {
7     using System.Collections;
8     using System.Globalization;
9     using System.IO;
10     using System.Runtime;
11 
12     /// <summary>
13     /// This is the in-memory nonce-cache used for turnkey replay detection.
14     /// The nonce cache is based on a hashtable implementation for fast lookups.
15     /// The hashcode is computed based on the nonce byte array.
16     /// The nonce cache periodically purges stale nonce entries.
17     /// </summary>
18     sealed class InMemoryNonceCache : NonceCache
19     {
20         NonceCacheImpl cacheImpl;
21 
InMemoryNonceCache(TimeSpan cachingTime, int maxCachedNonces)22         public InMemoryNonceCache(TimeSpan cachingTime, int maxCachedNonces)
23         {
24             this.CacheSize = maxCachedNonces;
25             this.CachingTimeSpan = cachingTime;
26             this.cacheImpl = new NonceCacheImpl(cachingTime, maxCachedNonces);
27         }
28 
CheckNonce(byte[] nonce)29         public override bool CheckNonce(byte[] nonce)
30         {
31             return this.cacheImpl.CheckNonce(nonce);
32         }
33 
TryAddNonce(byte[] nonce)34         public override bool TryAddNonce(byte[] nonce)
35         {
36             return this.cacheImpl.TryAddNonce(nonce);
37         }
38 
ToString()39         public override string ToString()
40         {
41             StringWriter writer = new StringWriter(CultureInfo.InvariantCulture);
42             writer.WriteLine("NonceCache:");
43             writer.WriteLine("   Caching Timespan: {0}", this.CachingTimeSpan);
44             writer.WriteLine("   Capacity: {0}", this.CacheSize);
45             return writer.ToString();
46         }
47 
48         internal sealed class NonceCacheImpl : TimeBoundedCache
49         {
50             static NonceKeyComparer comparer = new NonceKeyComparer();
51             static object dummyItem = new Object();
52             // if there are less than lowWaterMark entries, no purging is done
53             static int lowWaterMark = 50;
54             // We created a key for the nonce using the first 4 bytes, and hence the minimum length of nonce
55             // that can be added to the cache.
56             static int minimumNonceLength = 4;
57             TimeSpan cachingTimeSpan;
58 
NonceCacheImpl(TimeSpan cachingTimeSpan, int maxCachedNonces)59             public NonceCacheImpl(TimeSpan cachingTimeSpan, int maxCachedNonces)
60                 : base(lowWaterMark, maxCachedNonces, comparer, PurgingMode.AccessBasedPurge, TimeSpan.FromTicks(cachingTimeSpan.Ticks >> 2), false)
61             {
62                 this.cachingTimeSpan = cachingTimeSpan;
63             }
64 
TryAddNonce(byte[] nonce)65             public bool TryAddNonce(byte[] nonce)
66             {
67                 if (nonce == null)
68                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("nonce");
69                 if (nonce.Length < minimumNonceLength)
70                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.NonceLengthTooShort));
71                 DateTime expirationTime = TimeoutHelper.Add(DateTime.UtcNow, this.cachingTimeSpan);
72                 return base.TryAddItem(nonce, dummyItem, expirationTime, false);
73             }
74 
CheckNonce(byte[] nonce)75             public bool CheckNonce(byte[] nonce)
76             {
77                 if (nonce == null)
78                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgumentNull("nonce");
79                 if (nonce.Length < minimumNonceLength)
80                     throw DiagnosticUtility.ExceptionUtility.ThrowHelperArgument(SR.GetString(SR.NonceLengthTooShort));
81                 if (base.GetItem(nonce) != null)
82                     return true;
83                 else
84                     return false;
85             }
86 
87             /// <summary>
88             /// This class provides the hash-code value for the key (nonce) of the nonce cache.
89             /// The hash code is obtained from the nonce byte array  by making an int of
90             /// the first 4 bytes
91             /// </summary>
92             internal sealed class NonceKeyComparer : IEqualityComparer, System.Collections.Generic.IEqualityComparer<byte[]>
93             {
GetHashCode(object o)94                 public int GetHashCode(object o)
95                 {
96                     return GetHashCode((byte[])o);
97                 }
GetHashCode(byte[] o)98                 public int GetHashCode(byte[] o)
99                 {
100                     byte[] nonce = (byte[])o;
101 
102                     return (((int)nonce[0]) | (((int)nonce[1]) << 8) | (((int)nonce[2]) << 16) | (((int)nonce[3]) << 24));
103                 }
104 
Compare(object x, object y)105                 public int Compare(object x, object y)
106                 {
107                     return Compare((byte[])x, (byte[])y);
108                 }
109 
Compare(byte[] x, byte[] y)110                 public int Compare(byte[] x, byte[] y)
111                 {
112                     if (Object.ReferenceEquals(x, y))
113                         return 0;
114 
115                     if (x == null)
116                         return -1;
117                     else if (y == null)
118                         return 1;
119 
120                     byte[] nonce1 = (byte[])x;
121                     int length1 = nonce1.Length;
122                     byte[] nonce2 = (byte[])y;
123                     int length2 = nonce2.Length;
124 
125                     if (length1 == length2)
126                     {
127                         for (int i = 0; i < length1; ++i)
128                         {
129                             int diff = ((int)nonce1[i] - (int)nonce2[i]);
130 
131                             if (diff != 0)
132                             {
133                                 return diff;
134                             }
135                         }
136 
137                         return 0;
138                     }
139                     else if (length1 > length2)
140                     {
141                         return 1;
142                     }
143                     else
144                     {
145                         return -1;
146                     }
147                 }
148 
Equals(object x, object y)149                 public new bool Equals(object x, object y)
150                 {
151                     return (Compare(x, y) == 0);
152                 }
153 
Equals(byte[] x, byte[] y)154                 public bool Equals(byte[] x, byte[] y)
155                 {
156                     return (Compare(x, y) == 0);
157                 }
158             }
159         }
160     }
161 }
162