Merge pull request #2346 from xmcclure/proxy-load-fail
[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
40 namespace System.Web.Util {
41
42         static class MachineKeySectionUtils {
43                 static byte ToHexValue (char c, bool high)
44                 {
45                         byte v;
46                         if (c >= '0' && c <= '9')
47                                 v = (byte) (c - '0');
48                         else if (c >= 'a' && c <= 'f')
49                                 v = (byte) (c - 'a' + 10);
50                         else if (c >= 'A' && c <= 'F')
51                                 v = (byte) (c - 'A' + 10);
52                         else
53                                 throw new ArgumentException ("Invalid hex character");
54
55                         if (high)
56                                 v <<= 4;
57
58                         return v;
59                 }
60
61                 internal static byte [] GetBytes (string key, int len)
62                 {
63                         byte [] result = new byte [len / 2];
64                         for (int i = 0; i < len; i += 2)
65                                 result [i / 2] = (byte) (ToHexValue (key [i], true) + ToHexValue (key [i + 1], false));
66
67                         return result;
68                 }
69
70                 static public string GetHexString (byte [] bytes)
71                 {
72                         StringBuilder sb = new StringBuilder (bytes.Length * 2);
73                         int letterPart = 55;
74                         const int numberPart = 48;
75                         for (int i = 0; i < bytes.Length; i++) {
76                                 int tmp = (int) bytes [i];
77                                 int second = tmp & 15;
78                                 int first = (tmp >> 4) & 15;
79                                 sb.Append ((char) (first > 9 ? letterPart + first : numberPart + first));
80                                 sb.Append ((char) (second > 9 ? letterPart + second : numberPart + second));
81                         }
82                         return sb.ToString ();
83                 }
84
85
86                 // decryption="Auto" [Auto | DES | 3DES | AES | alg:algorithm_name]
87                 // http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
88                 public static SymmetricAlgorithm GetDecryptionAlgorithm (string name)
89                 {
90                         SymmetricAlgorithm sa = null;
91                         switch (name) {
92                         case "AES":
93                         case "Auto":
94                                 sa = Rijndael.Create ();
95                                 break;
96                         case "DES":
97                                 sa = DES.Create ();
98                                 break;
99                         case "3DES":
100                                 sa = TripleDES.Create ();
101                                 break;
102                         default:
103                                 if (name.StartsWith ("alg:")) {
104                                         sa = SymmetricAlgorithm.Create (name.Substring (4));
105                                         break;
106                                 }
107                                 throw new ConfigurationErrorsException ();
108                         }
109                         return sa;
110                 }
111
112                 // validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]
113                 // [1] http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeyvalidation.aspx
114                 // [2] http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
115                 public static KeyedHashAlgorithm GetValidationAlgorithm (MachineKeySection section)
116                 {
117                         KeyedHashAlgorithm kha = null;
118                         switch (section.Validation) {
119                         case MachineKeyValidation.MD5:
120                                 kha = new HMACMD5 ();
121                                 break;
122                         case MachineKeyValidation.AES:          // see link [1] or [2]
123                         case MachineKeyValidation.TripleDES:    // see link [2]
124                         case MachineKeyValidation.SHA1:
125                                 kha = new HMACSHA1 ();
126                                 break;
127                         case MachineKeyValidation.HMACSHA256:
128                                 kha = new HMACSHA256 ();
129                                 break;
130                         case MachineKeyValidation.HMACSHA384:
131                                 kha = new HMACSHA384 ();
132                                 break;
133                         case MachineKeyValidation.HMACSHA512:
134                                 kha = new HMACSHA512 ();
135                                 break;
136                         case MachineKeyValidation.Custom:
137                                 // remove the "alg:" from the start of the string
138                                 string algo = section.ValidationAlgorithm;
139                                 if (algo.StartsWith ("alg:"))
140                                         kha = KeyedHashAlgorithm.Create (algo.Substring (4));
141                                 break;
142                         }
143                         return kha;
144                 }
145
146                 // helpers to ease unit testing of the cryptographic code
147 #if TEST
148                 static byte [] decryption_key;
149                 static byte [] validation_key;
150
151                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
152                 {
153                         return GetDecryptionAlgorithm (section.Decryption);
154                 }
155
156                 static byte [] GetDecryptionKey (MachineKeySection section)
157                 {
158                         if (decryption_key == null)
159                                 decryption_key = GetDecryptionAlgorithm (section).Key;
160                         return decryption_key;
161                 }
162
163                 static byte [] GetValidationKey (MachineKeySection section)
164                 {
165                         if (validation_key == null)
166                                 validation_key = GetValidationAlgorithm (section).Key;
167                         return validation_key;
168                 }
169 #else
170                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
171                 {
172                         return section.GetDecryptionAlgorithm ();
173                 }
174
175                 static byte[] GetDecryptionKey (MachineKeySection section)
176                 {
177                         return section.GetDecryptionKey ();
178                 }
179
180                 public static byte [] GetValidationKey (MachineKeySection section)
181                 {
182                         return section.GetValidationKey ();
183                 }
184 #endif
185
186                 static public byte [] Decrypt (MachineKeySection section, byte [] encodedData)
187                 {
188                         return Decrypt (section, encodedData, 0, encodedData.Length);
189                 }
190
191                 static byte [] Decrypt (MachineKeySection section, byte [] encodedData, int offset, int length)
192                 {
193                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
194                                 sa.Key = GetDecryptionKey (section);
195                                 return Decrypt (sa, encodedData, offset, length);
196                         }
197                 }
198
199                 static public byte [] Decrypt (SymmetricAlgorithm alg, byte [] encodedData, int offset, int length)
200                 {
201                         // alg.IV is randomly set (default behavior) and perfect for our needs
202                         // iv is the first part of the encodedPassword
203                         byte [] iv = new byte [alg.IV.Length];
204                         Array.Copy (encodedData, 0, iv, 0, iv.Length);
205                         using (ICryptoTransform decryptor = alg.CreateDecryptor (alg.Key, iv)) {
206                                 try {
207                                         return decryptor.TransformFinalBlock (encodedData, iv.Length + offset, length - iv.Length);
208                                 }
209                                 catch (CryptographicException) {
210                                         return null;
211                                 }
212                         }
213                 }
214
215                 static public byte [] Encrypt (MachineKeySection section, byte [] data)
216                 {
217                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
218                                 sa.Key = GetDecryptionKey (section);
219                                 return Encrypt (sa, data);
220                         }
221                 }
222
223                 static public byte [] Encrypt (SymmetricAlgorithm alg, byte [] data)
224                 {
225                         // alg.IV is randomly set (default behavior) and perfect for our needs
226                         byte [] iv = alg.IV;
227                         using (ICryptoTransform encryptor = alg.CreateEncryptor (alg.Key, iv)) {
228                                 byte [] encrypted = encryptor.TransformFinalBlock (data, 0, data.Length);
229                                 byte [] output = new byte [iv.Length + encrypted.Length];
230                                 // note: the IV can be public, however it should not be based on the password
231                                 Array.Copy (iv, 0, output, 0, iv.Length);
232                                 Array.Copy (encrypted, 0, output, iv.Length, encrypted.Length);
233                                 return output;
234                         }
235                 }
236
237                 // in           [data]
238                 // return       [data][signature]
239                 public static byte [] Sign (MachineKeySection section, byte [] data)
240                 {
241                         return Sign (section, data, 0, data.Length);
242                 }
243
244                 static byte [] Sign (MachineKeySection section, byte [] data, int offset, int length)
245                 {
246                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
247                                 kha.Key = GetValidationKey (section);
248                                 byte [] signature = kha.ComputeHash (data, offset, length);
249                                 byte [] block = new byte [length + signature.Length];
250                                 Array.Copy (data, block, length);
251                                 Array.Copy (signature, 0, block, length, signature.Length);
252                                 return block;
253                         }
254                 }
255
256                 public static byte [] Verify (MachineKeySection section, byte [] data)
257                 {
258                         byte [] unsigned_data = null;
259                         bool valid = true;
260                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
261                                 kha.Key = GetValidationKey (section);
262                                 int signlen = kha.HashSize >> 3; // bits to bytes
263                                 byte [] signature = Sign (section, data, 0, data.Length - signlen);
264                                 for (int i = 0; i < signature.Length; i++) {
265                                         if (signature [i] != data [data.Length - signature.Length + i])
266                                                 valid = false; // do not return (timing attack)
267                                 }
268                                 unsigned_data = new byte [data.Length - signlen];
269                                 Array.Copy (data, 0, unsigned_data, 0, unsigned_data.Length);
270                         }
271                         return valid ? unsigned_data : null;
272                 }
273
274                 // do NOT sign then encrypt
275
276                 public static byte [] EncryptSign (MachineKeySection section, byte [] data)
277                 {
278                         byte [] encdata = Encrypt (section, data);
279                         return Sign (section, encdata);
280                 }
281
282                 // note: take no shortcut (timing attack) while verifying or decrypting
283                 public static byte [] VerifyDecrypt (MachineKeySection section, byte [] block)
284                 {
285                         bool valid = true;
286                         int signlen;
287
288                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
289                                 kha.Key = GetValidationKey (section);
290                                 signlen = kha.HashSize >> 3; // bits to bytes
291                                 byte [] signature = Sign (section, block, 0, block.Length - signlen);
292                                 for (int i = 0; i < signature.Length; i++) {
293                                         if (signature [i] != block [block.Length - signature.Length + i])
294                                                 valid = false; // do not return (timing attack)
295                                 }
296                         }
297
298                         // whatever the signature continue with decryption
299                         try {
300                                 byte [] decdata = Decrypt (section, block, 0, block.Length - signlen);
301                                 return valid ? decdata : null;
302                         }
303                         catch {
304                                 return null;
305                         }
306                 }
307         }
308 }
309