87f65dab7d98ca162db0af80d40625db0858bcd2
[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 NET_4_0
104                                 if (name.StartsWith ("alg:")) {
105                                         sa = SymmetricAlgorithm.Create (name.Substring (4));
106                                         break;
107                                 }
108 #endif
109                                 throw new ConfigurationErrorsException ();
110                         }
111                         return sa;
112                 }
113
114                 // validation="HMACSHA256" [SHA1 | MD5 | 3DES | AES | HMACSHA256 | HMACSHA384 | HMACSHA512 | alg:algorithm_name]
115                 // [1] http://msdn.microsoft.com/en-us/library/system.web.configuration.machinekeyvalidation.aspx
116                 // [2] http://msdn.microsoft.com/en-us/library/w8h3skw9.aspx
117                 public static KeyedHashAlgorithm GetValidationAlgorithm (MachineKeySection section)
118                 {
119                         KeyedHashAlgorithm kha = null;
120                         switch (section.Validation) {
121                         case MachineKeyValidation.MD5:
122                                 kha = new HMACMD5 ();
123                                 break;
124                         case MachineKeyValidation.AES:          // see link [1] or [2]
125                         case MachineKeyValidation.TripleDES:    // see link [2]
126                         case MachineKeyValidation.SHA1:
127                                 kha = new HMACSHA1 ();
128                                 break;
129 #if NET_4_0
130                         case MachineKeyValidation.HMACSHA256:
131                                 kha = new HMACSHA256 ();
132                                 break;
133                         case MachineKeyValidation.HMACSHA384:
134                                 kha = new HMACSHA384 ();
135                                 break;
136                         case MachineKeyValidation.HMACSHA512:
137                                 kha = new HMACSHA512 ();
138                                 break;
139                         case MachineKeyValidation.Custom:
140                                 // remove the "alg:" from the start of the string
141                                 string algo = section.ValidationAlgorithm;
142                                 if (algo.StartsWith ("alg:"))
143                                         kha = KeyedHashAlgorithm.Create (algo.Substring (4));
144                                 break;
145 #endif
146                         }
147                         return kha;
148                 }
149
150                 // helpers to ease unit testing of the cryptographic code
151 #if TEST
152                 static byte [] decryption_key;
153                 static byte [] validation_key;
154
155                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
156                 {
157                         return GetDecryptionAlgorithm (section.Decryption);
158                 }
159
160                 static byte [] GetDecryptionKey (MachineKeySection section)
161                 {
162                         if (decryption_key == null)
163                                 decryption_key = GetDecryptionAlgorithm (section).Key;
164                         return decryption_key;
165                 }
166
167                 static byte [] GetValidationKey (MachineKeySection section)
168                 {
169                         if (validation_key == null)
170                                 validation_key = GetValidationAlgorithm (section).Key;
171                         return validation_key;
172                 }
173 #else
174                 static SymmetricAlgorithm GetDecryptionAlgorithm (MachineKeySection section)
175                 {
176                         return section.GetDecryptionAlgorithm ();
177                 }
178
179                 static byte[] GetDecryptionKey (MachineKeySection section)
180                 {
181                         return section.GetDecryptionKey ();
182                 }
183
184                 public static byte [] GetValidationKey (MachineKeySection section)
185                 {
186                         return section.GetValidationKey ();
187                 }
188 #endif
189
190                 static public byte [] Decrypt (MachineKeySection section, byte [] encodedData)
191                 {
192                         return Decrypt (section, encodedData, 0, encodedData.Length);
193                 }
194
195                 static byte [] Decrypt (MachineKeySection section, byte [] encodedData, int offset, int length)
196                 {
197                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
198                                 sa.Key = GetDecryptionKey (section);
199                                 return Decrypt (sa, encodedData, offset, length);
200                         }
201                 }
202
203                 static public byte [] Decrypt (SymmetricAlgorithm alg, byte [] encodedData, int offset, int length)
204                 {
205                         // alg.IV is randomly set (default behavior) and perfect for our needs
206                         // iv is the first part of the encodedPassword
207                         byte [] iv = new byte [alg.IV.Length];
208                         Array.Copy (encodedData, 0, iv, 0, iv.Length);
209                         using (ICryptoTransform decryptor = alg.CreateDecryptor (alg.Key, iv)) {
210                                 try {
211                                         return decryptor.TransformFinalBlock (encodedData, iv.Length + offset, length - iv.Length);
212                                 }
213                                 catch (CryptographicException) {
214                                         return null;
215                                 }
216                         }
217                 }
218
219                 static public byte [] Encrypt (MachineKeySection section, byte [] data)
220                 {
221                         using (SymmetricAlgorithm sa = GetDecryptionAlgorithm (section)) {
222                                 sa.Key = GetDecryptionKey (section);
223                                 return Encrypt (sa, data);
224                         }
225                 }
226
227                 static public byte [] Encrypt (SymmetricAlgorithm alg, byte [] data)
228                 {
229                         // alg.IV is randomly set (default behavior) and perfect for our needs
230                         byte [] iv = alg.IV;
231                         using (ICryptoTransform encryptor = alg.CreateEncryptor (alg.Key, iv)) {
232                                 byte [] encrypted = encryptor.TransformFinalBlock (data, 0, data.Length);
233                                 byte [] output = new byte [iv.Length + encrypted.Length];
234                                 // note: the IV can be public, however it should not be based on the password
235                                 Array.Copy (iv, 0, output, 0, iv.Length);
236                                 Array.Copy (encrypted, 0, output, iv.Length, encrypted.Length);
237                                 return output;
238                         }
239                 }
240
241                 // in           [data]
242                 // return       [data][signature]
243                 public static byte [] Sign (MachineKeySection section, byte [] data)
244                 {
245                         return Sign (section, data, 0, data.Length);
246                 }
247
248                 static byte [] Sign (MachineKeySection section, byte [] data, int offset, int length)
249                 {
250                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
251                                 kha.Key = GetValidationKey (section);
252                                 byte [] signature = kha.ComputeHash (data, offset, length);
253                                 byte [] block = new byte [length + signature.Length];
254                                 Array.Copy (data, block, length);
255                                 Array.Copy (signature, 0, block, length, signature.Length);
256                                 return block;
257                         }
258                 }
259
260                 public static byte [] Verify (MachineKeySection section, byte [] data)
261                 {
262                         byte [] unsigned_data = null;
263                         bool valid = true;
264                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
265                                 kha.Key = GetValidationKey (section);
266                                 int signlen = kha.HashSize >> 3; // bits to bytes
267                                 byte [] signature = Sign (section, data, 0, data.Length - signlen);
268                                 for (int i = 0; i < signature.Length; i++) {
269                                         if (signature [i] != data [data.Length - signature.Length + i])
270                                                 valid = false; // do not return (timing attack)
271                                 }
272                                 unsigned_data = new byte [data.Length - signlen];
273                                 Array.Copy (data, 0, unsigned_data, 0, unsigned_data.Length);
274                         }
275                         return valid ? unsigned_data : null;
276                 }
277
278                 // do NOT sign then encrypt
279
280                 public static byte [] EncryptSign (MachineKeySection section, byte [] data)
281                 {
282                         byte [] encdata = Encrypt (section, data);
283                         return Sign (section, encdata);
284                 }
285
286                 // note: take no shortcut (timing attack) while verifying or decrypting
287                 public static byte [] VerifyDecrypt (MachineKeySection section, byte [] block)
288                 {
289                         bool valid = true;
290                         int signlen;
291
292                         using (KeyedHashAlgorithm kha = GetValidationAlgorithm (section)) {
293                                 kha.Key = GetValidationKey (section);
294                                 signlen = kha.HashSize >> 3; // bits to bytes
295                                 byte [] signature = Sign (section, block, 0, block.Length - signlen);
296                                 for (int i = 0; i < signature.Length; i++) {
297                                         if (signature [i] != block [block.Length - signature.Length + i])
298                                                 valid = false; // do not return (timing attack)
299                                 }
300                         }
301
302                         // whatever the signature continue with decryption
303                         try {
304                                 byte [] decdata = Decrypt (section, block, 0, block.Length - signlen);
305                                 return valid ? decdata : null;
306                         }
307                         catch {
308                                 return null;
309                         }
310                 }
311         }
312 }
313