[Mono.Security]: Use the new 'NoReflectionHelper' instead of reflection in 'MonoTlsPr...
[mono.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / SymmetricTransform.cs
1 //
2 // Mono.Security.Cryptography.SymmetricTransform implementation
3 //
4 // Authors:
5 //      Thomas Neidhart (tome@sbox.tugraz.at)
6 //      Sebastien Pouliot <sebastien@ximian.com>
7 //
8 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // Copyright (C) 2004-2008 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System;
32 using System.Security.Cryptography;
33
34 namespace Mono.Security.Cryptography {
35
36         // This class implement most of the common code required for symmetric
37         // algorithm transforms, like:
38         // - CipherMode: Builds CBC and CFB on top of (descendant supplied) ECB
39         // - PaddingMode, transform properties, multiple blocks, reuse...
40         //
41         // Descendants MUST:
42         // - intialize themselves (like key expansion, ...)
43         // - override the ECB (Electronic Code Book) method which will only be
44         //   called using BlockSize byte[] array.
45         internal abstract class SymmetricTransform : ICryptoTransform {
46                 protected SymmetricAlgorithm algo;
47                 protected bool encrypt;
48                 protected int BlockSizeByte;
49                 protected byte[] temp;
50                 protected byte[] temp2;
51                 private byte[] workBuff;
52                 private byte[] workout;
53                 protected PaddingMode padmode;
54                 // Silverlight 2.0 does not support any feedback mode
55                 protected int FeedBackByte;
56                 private bool m_disposed = false;
57                 protected bool lastBlock;
58
59                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
60                 {
61                         algo = symmAlgo;
62                         encrypt = encryption;
63                         BlockSizeByte = (algo.BlockSize >> 3);
64
65                         if (rgbIV == null) {
66                                 rgbIV = KeyBuilder.IV (BlockSizeByte);
67                         } else {
68                                 rgbIV = (byte[]) rgbIV.Clone ();
69                         }
70                         // compare the IV length with the "currently selected" block size and *ignore* IV that are too big
71                         if (rgbIV.Length < BlockSizeByte) {
72                                 string msg = Locale.GetText ("IV is too small ({0} bytes), it should be {1} bytes long.",
73                                         rgbIV.Length, BlockSizeByte);
74                                 throw new CryptographicException (msg);
75                         }
76                         padmode = algo.Padding;
77                         // mode buffers
78                         temp = new byte [BlockSizeByte];
79                         Buffer.BlockCopy (rgbIV, 0, temp, 0, System.Math.Min (BlockSizeByte, rgbIV.Length));
80                         temp2 = new byte [BlockSizeByte];
81                         FeedBackByte = (algo.FeedbackSize >> 3);
82                         // transform buffers
83                         workBuff = new byte [BlockSizeByte];
84                         workout =  new byte [BlockSizeByte];
85                 }
86
87                 ~SymmetricTransform () 
88                 {
89                         Dispose (false);
90                 }
91
92                 void IDisposable.Dispose () 
93                 {
94                         Dispose (true);
95                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
96                 }
97
98                 // MUST be overriden by classes using unmanaged ressources
99                 // the override method must call the base class
100                 protected virtual void Dispose (bool disposing) 
101                 {
102                         if (!m_disposed) {
103                                 if (disposing) {
104                                         // dispose managed object: zeroize and free
105                                         Array.Clear (temp, 0, BlockSizeByte);
106                                         temp = null;
107                                         Array.Clear (temp2, 0, BlockSizeByte);
108                                         temp2 = null;
109                                 }
110                                 m_disposed = true;
111                         }
112                 }
113
114                 public virtual bool CanTransformMultipleBlocks {
115                         get { return true; }
116                 }
117
118                 public virtual bool CanReuseTransform {
119                         get { return false; }
120                 }
121
122                 public virtual int InputBlockSize {
123                         get { return BlockSizeByte; }
124                 }
125
126                 public virtual int OutputBlockSize {
127                         get { return BlockSizeByte; }
128                 }
129
130                 // note: Each block MUST be BlockSizeValue in size!!!
131                 // i.e. Any padding must be done before calling this method
132                 protected virtual void Transform (byte[] input, byte[] output) 
133                 {
134                         switch (algo.Mode) {
135                         case CipherMode.ECB:
136                                 ECB (input, output);
137                                 break;
138                         case CipherMode.CBC:
139                                 CBC (input, output);
140                                 break;
141                         case CipherMode.CFB:
142                                 CFB (input, output);
143                                 break;
144                         case CipherMode.OFB:
145                                 OFB (input, output);
146                                 break;
147                         case CipherMode.CTS:
148                                 CTS (input, output);
149                                 break;
150                         default:
151                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
152                         }
153                 }
154
155                 // Electronic Code Book (ECB)
156                 protected abstract void ECB (byte[] input, byte[] output); 
157
158                 // Cipher-Block-Chaining (CBC)
159                 protected virtual void CBC (byte[] input, byte[] output) 
160                 {
161                         if (encrypt) {
162                                 for (int i = 0; i < BlockSizeByte; i++)
163                                         temp[i] ^= input[i];
164                                 ECB (temp, output);
165                                 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
166                         }
167                         else {
168                                 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
169                                 ECB (input, output);
170                                 for (int i = 0; i < BlockSizeByte; i++)
171                                         output[i] ^= temp[i];
172                                 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
173                         }
174                 }
175
176                 // Cipher-FeedBack (CFB)
177                 // this is how *CryptoServiceProvider implements CFB
178                 // only AesCryptoServiceProvider support CFB > 8
179                 // RijndaelManaged is incompatible with this implementation (and overrides it in it's own transform)
180                 protected virtual void CFB (byte[] input, byte[] output) 
181                 {
182                         if (encrypt) {
183                                 for (int x = 0; x < BlockSizeByte; x++) {
184                                         // temp is first initialized with the IV
185                                         ECB (temp, temp2);
186                                         output [x] = (byte) (temp2 [0] ^ input [x]);
187                                         Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
188                                         Buffer.BlockCopy (output, x, temp, BlockSizeByte - 1, 1);
189                                 }
190                         }
191                         else {
192                                 for (int x = 0; x < BlockSizeByte; x++) {
193                                         // we do not really decrypt this data!
194                                         encrypt = true;
195                                         // temp is first initialized with the IV
196                                         ECB (temp, temp2);
197                                         encrypt = false;
198
199                                         Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
200                                         Buffer.BlockCopy (input, x, temp, BlockSizeByte - 1, 1);
201                                         output [x] = (byte) (temp2 [0] ^ input [x]);
202                                 }
203                         }
204                 }
205
206                 // Output-FeedBack (OFB)
207                 protected virtual void OFB (byte[] input, byte[] output) 
208                 {
209                         throw new CryptographicException ("OFB isn't supported by the framework");
210                 }
211
212                 // Cipher Text Stealing (CTS)
213                 protected virtual void CTS (byte[] input, byte[] output) 
214                 {
215                         throw new CryptographicException ("CTS isn't supported by the framework");
216                 }
217
218                 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
219                 {
220                         if (inputBuffer == null)
221                                 throw new ArgumentNullException ("inputBuffer");
222                         if (inputOffset < 0)
223                                 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
224                         if (inputCount < 0)
225                                 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
226                         // ordered to avoid possible integer overflow
227                         if (inputOffset > inputBuffer.Length - inputCount)
228                                 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
229                 }
230
231                 // this method may get called MANY times so this is the one to optimize
232                 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
233                 {
234                         if (m_disposed)
235                                 throw new ObjectDisposedException ("Object is disposed");
236                         CheckInput (inputBuffer, inputOffset, inputCount);
237                         // check output parameters
238                         if (outputBuffer == null)
239                                 throw new ArgumentNullException ("outputBuffer");
240                         if (outputOffset < 0)
241                                 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
242
243                         // ordered to avoid possible integer overflow
244                         int len = outputBuffer.Length - inputCount - outputOffset;
245                         if (!encrypt && (0 > len) && ((padmode == PaddingMode.None) || (padmode == PaddingMode.Zeros))) {
246                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
247                         } else if (KeepLastBlock) {
248                                 if (0 > len + BlockSizeByte) {
249                                         throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
250                                 }
251                         } else {
252                                 if (0 > len) {
253                                         // there's a special case if this is the end of the decryption process
254                                         if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
255                                                 inputCount = outputBuffer.Length - outputOffset;
256                                         else
257                                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
258                                 }
259                         }
260                         return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
261                 }
262
263                 private bool KeepLastBlock {
264                         get {
265                                 return ((!encrypt) && (padmode != PaddingMode.None) && (padmode != PaddingMode.Zeros));
266                         }
267                 }
268
269                 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
270                 {
271                         int offs = inputOffset;
272                         int full;
273
274                         // this way we don't do a modulo every time we're called
275                         // and we may save a division
276                         if (inputCount != BlockSizeByte) {
277                                 if ((inputCount % BlockSizeByte) != 0)
278                                         throw new CryptographicException ("Invalid input block size.");
279
280                                 full = inputCount / BlockSizeByte;
281                         }
282                         else
283                                 full = 1;
284
285                         if (KeepLastBlock)
286                                 full--;
287
288                         int total = 0;
289
290                         if (lastBlock) {
291                                 Transform (workBuff, workout);
292                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
293                                 outputOffset += BlockSizeByte;
294                                 total += BlockSizeByte;
295                                 lastBlock = false;
296                         }
297
298                         for (int i = 0; i < full; i++) {
299                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
300                                 Transform (workBuff, workout);
301                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
302                                 offs += BlockSizeByte;
303                                 outputOffset += BlockSizeByte;
304                                 total += BlockSizeByte;
305                         }
306
307                         if (KeepLastBlock) {
308                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
309                                 lastBlock = true;
310                         }
311
312                         return total;
313                 }
314
315                 RandomNumberGenerator _rng;
316
317                 private void Random (byte[] buffer, int start, int length)
318                 {
319                         if (_rng == null) {
320                                 _rng = RandomNumberGenerator.Create ();
321                         }
322                         byte[] random = new byte [length];
323                         _rng.GetBytes (random);
324                         Buffer.BlockCopy (random, 0, buffer, start, length);
325                 }
326
327                 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
328                 {
329                         string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
330                         if (length >= 0)
331                                 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
332                         if (position >= 0)
333                                 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
334                         throw new CryptographicException (msg);
335                 }
336
337                 protected virtual byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
338                 {
339                         // are there still full block to process ?
340                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
341                         int rem = inputCount - full;
342                         int total = full;
343
344                         switch (padmode) {
345                         case PaddingMode.ANSIX923:
346                         case PaddingMode.ISO10126:
347                         case PaddingMode.PKCS7:
348                                 // we need to add an extra block for padding
349                                 total += BlockSizeByte;
350                                 break;
351                         default:
352                                 if (inputCount == 0)
353                                         return new byte [0];
354                                 if (rem != 0) {
355                                         if (padmode == PaddingMode.None)
356                                                 throw new CryptographicException ("invalid block length");
357                                         // zero padding the input (by adding a block for the partial data)
358                                         byte[] paddedInput = new byte [full + BlockSizeByte];
359                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
360                                         inputBuffer = paddedInput;
361                                         inputOffset = 0;
362                                         inputCount = paddedInput.Length;
363                                         total = inputCount;
364                                 }
365                                 break;
366                         }
367
368                         byte[] res = new byte [total];
369                         int outputOffset = 0;
370
371                         // process all blocks except the last (final) block
372                         while (total > BlockSizeByte) {
373                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
374                                 inputOffset += BlockSizeByte;
375                                 outputOffset += BlockSizeByte;
376                                 total -= BlockSizeByte;
377                         }
378
379                         // now we only have a single last block to encrypt
380                         byte padding = (byte) (BlockSizeByte - rem);
381                         switch (padmode) {
382                         case PaddingMode.ANSIX923:
383                                 // XX 00 00 00 00 00 00 07 (zero + padding length)
384                                 res [res.Length - 1] = padding;
385                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
386                                 // the last padded block will be transformed in-place
387                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
388                                 break;
389                         case PaddingMode.ISO10126:
390                                 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
391                                 Random (res, res.Length - padding, padding - 1);
392                                 res [res.Length - 1] = padding;
393                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
394                                 // the last padded block will be transformed in-place
395                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
396                                 break;
397                         case PaddingMode.PKCS7:
398                                 // XX 07 07 07 07 07 07 07 (padding length)
399                                 for (int i = res.Length; --i >= (res.Length - padding);) 
400                                         res [i] = padding;
401                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
402                                 // the last padded block will be transformed in-place
403                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
404                                 break;
405                         default:
406                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
407                                 break;
408                         }
409                         return res;
410                 }
411
412                 protected virtual byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
413                 {
414                         int full = inputCount;
415                         int total = inputCount;
416                         if (lastBlock)
417                                 total += BlockSizeByte;
418
419                         byte[] res = new byte [total];
420                         int outputOffset = 0;
421
422                         while (full > 0) {
423                                 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
424                                 inputOffset += BlockSizeByte;
425                                 outputOffset += len;
426                                 full -= BlockSizeByte;
427                         }
428
429                         if (lastBlock) {
430                                 Transform (workBuff, workout);
431                                 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
432                                 outputOffset += BlockSizeByte;
433                                 lastBlock = false;
434                         }
435
436                         // total may be 0 (e.g. PaddingMode.None)
437                         byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
438                         switch (padmode) {
439                         case PaddingMode.ANSIX923:
440                                 if ((padding == 0) || (padding > BlockSizeByte))
441                                         ThrowBadPaddingException (padmode, padding, -1);
442                                 for (int i = padding - 1; i > 0; i--) {
443                                         if (res [total - 1 - i] != 0x00)
444                                                 ThrowBadPaddingException (padmode, -1, i);
445                                 }
446                                 total -= padding;
447                                 break;
448                         case PaddingMode.ISO10126:
449                                 if ((padding == 0) || (padding > BlockSizeByte))
450                                         ThrowBadPaddingException (padmode, padding, -1);
451                                 total -= padding;
452                                 break;
453                         case PaddingMode.PKCS7:
454                                 if ((padding == 0) || (padding > BlockSizeByte))
455                                         ThrowBadPaddingException (padmode, padding, -1);
456                                 for (int i = padding - 1; i > 0; i--) {
457                                         if (res [total - 1 - i] != padding)
458                                                 ThrowBadPaddingException (padmode, -1, i);
459                                 }
460                                 total -= padding;
461                                 break;
462                         case PaddingMode.None:  // nothing to do - it's a multiple of block size
463                         case PaddingMode.Zeros: // nothing to do - user must unpad himself
464                                 break;
465                         }
466
467                         // return output without padding
468                         if (total > 0) {
469                                 byte[] data = new byte [total];
470                                 Buffer.BlockCopy (res, 0, data, 0, total);
471                                 // zeroize decrypted data (copy with padding)
472                                 Array.Clear (res, 0, res.Length);
473                                 return data;
474                         }
475                         else
476                                 return new byte [0];
477                 }
478
479                 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
480                 {
481                         if (m_disposed)
482                                 throw new ObjectDisposedException ("Object is disposed");
483                         CheckInput (inputBuffer, inputOffset, inputCount);
484
485                         if (encrypt)
486                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
487                         else
488                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
489                 }
490         }
491 }