Merge branch 'cecil-light'
[mono.git] / mcs / class / System.Web / System.Web.Util / MachineKeySectionUtils.cs
1 //
2 // System.Web.Util.MachineKeySectionUtils
3 //
4 // Authors:
5 //      Chris Toshok (toshok@ximian.com)
6 //      Sebastien Pouliot  <sebastien@ximian.com>
7 //
8 // (c) Copyright 2005, 2010 Novell, Inc (http://www.novell.com)
9 //
10
11 //
12 // Permission is hereby granted, free of charge, to any person obtaining
13 // a copy of this software and associated documentation files (the
14 // "Software"), to deal in the Software without restriction, including
15 // without limitation the rights to use, copy, modify, merge, publish,
16 // distribute, sublicense, and/or sell copies of the Software, and to
17 // permit persons to whom the Software is furnished to do so, subject to
18 // the following conditions:
19 // 
20 // The above copyright notice and this permission notice shall be
21 // included in all copies or substantial portions of the Software.
22 // 
23 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
24 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
25 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
26 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
27 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
28 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
29 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
30 //
31
32 using System.ComponentModel;
33 using System.Configuration;
34 using System.Configuration.Provider;
35 using System.Security.Cryptography;
36 using System.Text;
37 using System.Web.Configuration;
38
39 #if NET_2_0
40
41 namespace System.Web.Util {
42
43         static class MachineKeySectionUtils {
44                 static byte ToHexValue (char c, bool high)
45                 {
46                         byte v;
47                         if (c >= '0' && c <= '9')
48                                 v = (byte) (c - '0');
49                         else if (c >= 'a' && c <= 'f')
50                                 v = (byte) (c - 'a' + 10);
51                         else if (c >= 'A' && c <= 'F')
52                                 v = (byte) (c - 'A' + 10);
53                         else
54                                 throw new ArgumentException ("Invalid hex character");
55
56                         if (high)
57                                 v <<= 4;
58
59                         return v;
60                 }
61
62                 internal static byte [] GetBytes (string key, int len)
63                 {
64                         byte [] result = new byte [len / 2];
65                         for (int i = 0; i < len; i += 2)
66                                 result [i / 2] = (byte) (ToHexValue (key [i], true) + ToHexValue (key [i + 1], false));
67
68                         return result;
69                 }
70
71                 static public string GetHexString (byte [] bytes)
72                 {
73                         StringBuilder sb = new StringBuilder (bytes.Length * 2);
74                         int letterPart = 55;
75                         const int numberPart = 48;
76                         for (int i = 0; i < bytes.Length; i++) {
77                                 int tmp = (int) bytes [i];
78                                 int second = tmp & 15;
79                                 int first = (tmp >> 4) & 15;
80                                 sb.Append ((char) (first > 9 ? letterPart + first : numberPart + first));
81                                 sb.Append ((char) (second > 9 ? letterPart + second : numberPart + second));
82                         }
83                         return sb.ToString ();
84                 }
85
86
87                 // decryption="Auto" [Auto | DES | 3DES | AES | alg:algorithm_name]
88                 // http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
89                 public static SymmetricAlgorithm GetDecryptionAlgorithm (string name)
90                 {
91                         SymmetricAlgorithm sa = null;
92                         switch (name) {
93                         case "AES":
94                         case "Auto":
95                                 sa = Rijndael.Create ();
96                                 break;
97                         case "DES":
98                                 sa = DES.Create ();
99                                 break;
100                         case "3DES":
101                                 sa = TripleDES.Create ();
102                                 break;
103                         default:
104 #if NET_4_0
105                                 if (name.StartsWith ("alg:")) {
106                                         sa = SymmetricAlgorithm.Create (name.Substring (4));
107                                         break;
108                                 }
109 #endif
110                                 throw new ConfigurationErrorsException ();
111                         }
112                         return sa;
113                 }
114
115                 // validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]
116                 // [1] http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeyvalidation.aspx
117                 // [2] http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
118                 public static KeyedHashAlgorithm GetValidationAlgorithm (MachineKeySection section)
119                 {
120                         KeyedHashAlgorithm kha = null;
121                         switch (section.Validation) {
122                         case MachineKeyValidation.MD5:
123                                 kha = new HMACMD5 ();
124                                 break;
125                         case MachineKeyValidation.AES:          // see link [1] or [2]
126                         case MachineKeyValidation.TripleDES:    // see link [2]
127                         case MachineKeyValidation.SHA1:
128                                 kha = new HMACSHA1 ();
129                                 break;
130 #if NET_4_0
131                         case MachineKeyValidation.HMACSHA256:
132                                 kha = new HMACSHA256 ();
133                                 break;
134                         case MachineKeyValidation.HMACSHA384:
135                                 kha = new HMACSHA384 ();
136                                 break;
137                         case MachineKeyValidation.HMACSHA512:
138                                 kha = new HMACSHA512 ();
139                                 break;
140                         case MachineKeyValidation.Custom:
141                                 // remove the "alg:" from the start of the string
142                                 string algo = section.ValidationAlgorithm;
143                                 if (algo.StartsWith ("alg:"))
144                                         kha = KeyedHashAlgorithm.Create (algo.Substring (4));
145                                 break;
146 #endif
147                         }
148                         return kha;
149                 }
150
151                 // helpers to ease unit testing of the cryptographic code
152 #if TEST
153                 static byte [] decryption_key;
154                 static byte [] validation_key;
155
156                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
157                 {
158                         return GetDecryptionAlgorithm (section.Decryption);
159                 }
160
161                 static byte [] GetDecryptionKey (MachineKeySection section)
162                 {
163                         if (decryption_key == null)
164                                 decryption_key = GetDecryptionAlgorithm (section).Key;
165                         return decryption_key;
166                 }
167
168                 static byte [] GetValidationKey (MachineKeySection section)
169                 {
170                         if (validation_key == null)
171                                 validation_key = GetValidationAlgorithm (section).Key;
172                         return validation_key;
173                 }
174 #else
175                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
176                 {
177                         return section.GetDecryptionAlgorithm ();
178                 }
179
180                 static byte[] GetDecryptionKey (MachineKeySection section)
181                 {
182                         return section.GetDecryptionKey ();
183                 }
184
185                 public static byte [] GetValidationKey (MachineKeySection section)
186                 {
187                         return section.GetValidationKey ();
188                 }
189 #endif
190
191                 static public byte [] Decrypt (MachineKeySection section, byte [] encodedData)
192                 {
193                         return Decrypt (section, encodedData, 0, encodedData.Length);
194                 }
195
196                 static byte [] Decrypt (MachineKeySection section, byte [] encodedData, int offset, int length)
197                 {
198                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
199                                 sa.Key = GetDecryptionKey (section);
200                                 return Decrypt (sa, encodedData, offset, length);
201                         }
202                 }
203
204                 static public byte [] Decrypt (SymmetricAlgorithm alg, byte [] encodedData, int offset, int length)
205                 {
206                         // alg.IV is randomly set (default behavior) and perfect for our needs
207                         // iv is the first part of the encodedPassword
208                         byte [] iv = new byte [alg.IV.Length];
209                         Array.Copy (encodedData, 0, iv, 0, iv.Length);
210                         using (ICryptoTransform decryptor = alg.CreateDecryptor (alg.Key, iv)) {
211                                 try {
212                                         return decryptor.TransformFinalBlock (encodedData, iv.Length + offset, length - iv.Length);
213                                 }
214                                 catch (CryptographicException) {
215                                         return null;
216                                 }
217                         }
218                 }
219
220                 static public byte [] Encrypt (MachineKeySection section, byte [] data)
221                 {
222                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
223                                 sa.Key = GetDecryptionKey (section);
224                                 return Encrypt (sa, data);
225                         }
226                 }
227
228                 static public byte [] Encrypt (SymmetricAlgorithm alg, byte [] data)
229                 {
230                         // alg.IV is randomly set (default behavior) and perfect for our needs
231                         byte [] iv = alg.IV;
232                         using (ICryptoTransform encryptor = alg.CreateEncryptor (alg.Key, iv)) {
233                                 byte [] encrypted = encryptor.TransformFinalBlock (data, 0, data.Length);
234                                 byte [] output = new byte [iv.Length + encrypted.Length];
235                                 // note: the IV can be public, however it should not be based on the password
236                                 Array.Copy (iv, 0, output, 0, iv.Length);
237                                 Array.Copy (encrypted, 0, output, iv.Length, encrypted.Length);
238                                 return output;
239                         }
240                 }
241
242                 // in           [data]
243                 // return       [data][signature]
244                 public static byte [] Sign (MachineKeySection section, byte [] data)
245                 {
246                         return Sign (section, data, 0, data.Length);
247                 }
248
249                 static byte [] Sign (MachineKeySection section, byte [] data, int offset, int length)
250                 {
251                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
252                                 kha.Key = GetValidationKey (section);
253                                 byte [] signature = kha.ComputeHash (data, offset, length);
254                                 byte [] block = new byte [length + signature.Length];
255                                 Array.Copy (data, block, length);
256                                 Array.Copy (signature, 0, block, length, signature.Length);
257                                 return block;
258                         }
259                 }
260
261                 public static byte [] Verify (MachineKeySection section, byte [] data)
262                 {
263                         byte [] unsigned_data = null;
264                         bool valid = true;
265                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
266                                 kha.Key = GetValidationKey (section);
267                                 int signlen = kha.HashSize >> 3; // bits to bytes
268                                 byte [] signature = Sign (section, data, 0, data.Length - signlen);
269                                 for (int i = 0; i < signature.Length; i++) {
270                                         if (signature [i] != data [data.Length - signature.Length + i])
271                                                 valid = false; // do not return (timing attack)
272                                 }
273                                 unsigned_data = new byte [data.Length - signlen];
274                                 Array.Copy (data, 0, unsigned_data, 0, unsigned_data.Length);
275                         }
276                         return valid ? unsigned_data : null;
277                 }
278
279                 // do NOT sign then encrypt
280
281                 public static byte [] EncryptSign (MachineKeySection section, byte [] data)
282                 {
283                         byte [] encdata = Encrypt (section, data);
284                         return Sign (section, encdata);
285                 }
286
287                 // note: take no shortcut (timing attack) while verifying or decrypting
288                 public static byte [] VerifyDecrypt (MachineKeySection section, byte [] block)
289                 {
290                         bool valid = true;
291                         int signlen;
292
293                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
294                                 kha.Key = GetValidationKey (section);
295                                 signlen = kha.HashSize >> 3; // bits to bytes
296                                 byte [] signature = Sign (section, block, 0, block.Length - signlen);
297                                 for (int i = 0; i < signature.Length; i++) {
298                                         if (signature [i] != block [block.Length - signature.Length + i])
299                                                 valid = false; // do not return (timing attack)
300                                 }
301                         }
302
303                         // whatever the signature continue with decryption
304                         try {
305                                 byte [] decdata = Decrypt (section, block, 0, block.Length - signlen);
306                                 return valid ? decdata : null;
307                         }
308                         catch {
309                                 return null;
310                         }
311                 }
312         }
313 }
314
315 #endif