Fix CFB for AesCryptoServiceProvider (only CFB8 worked, 16-64 did not) and move the...
[mono.git] / mcs / class / corlib / 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 #if !MOONLIGHT
54                 // Silverlight 2.0 does not support any feedback mode
55                 protected int FeedBackByte;
56 #endif
57                 private bool m_disposed = false;
58                 private bool lastBlock;
59
60                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
61                 {
62                         algo = symmAlgo;
63                         encrypt = encryption;
64                         BlockSizeByte = (algo.BlockSize >> 3);
65
66                         if (rgbIV == null) {
67                                 rgbIV = KeyBuilder.IV (BlockSizeByte);
68                         } else {
69                                 rgbIV = (byte[]) rgbIV.Clone ();
70                         }
71                         // compare the IV length with the "currently selected" block size and *ignore* IV that are too big
72                         if (rgbIV.Length < BlockSizeByte) {
73                                 string msg = Locale.GetText ("IV is too small ({0} bytes), it should be {1} bytes long.",
74                                         rgbIV.Length, BlockSizeByte);
75                                 throw new CryptographicException (msg);
76                         }
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 #if !MOONLIGHT
82                         FeedBackByte = (algo.FeedbackSize >> 3);
83 #endif
84                         // transform buffers
85                         workBuff = new byte [BlockSizeByte];
86                         workout =  new byte [BlockSizeByte];
87                 }
88
89                 ~SymmetricTransform () 
90                 {
91                         Dispose (false);
92                 }
93
94                 void IDisposable.Dispose () 
95                 {
96                         Dispose (true);
97                         GC.SuppressFinalize (this);  // Finalization is now unnecessary
98                 }
99
100                 // MUST be overriden by classes using unmanaged ressources
101                 // the override method must call the base class
102                 protected virtual void Dispose (bool disposing) 
103                 {
104                         if (!m_disposed) {
105                                 if (disposing) {
106                                         // dispose managed object: zeroize and free
107                                         Array.Clear (temp, 0, BlockSizeByte);
108                                         temp = null;
109                                         Array.Clear (temp2, 0, BlockSizeByte);
110                                         temp2 = null;
111                                 }
112                                 m_disposed = true;
113                         }
114                 }
115
116                 public virtual bool CanTransformMultipleBlocks {
117                         get { return true; }
118                 }
119
120                 public virtual bool CanReuseTransform {
121                         get { return false; }
122                 }
123
124                 public virtual int InputBlockSize {
125                         get { return BlockSizeByte; }
126                 }
127
128                 public virtual int OutputBlockSize {
129                         get { return BlockSizeByte; }
130                 }
131
132                 // note: Each block MUST be BlockSizeValue in size!!!
133                 // i.e. Any padding must be done before calling this method
134                 protected virtual void Transform (byte[] input, byte[] output) 
135                 {
136 #if MOONLIGHT
137                         // Silverlight 2.0 only supports CBC
138                         CBC (input, output);
139 #else
140                         switch (algo.Mode) {
141                         case CipherMode.ECB:
142                                 ECB (input, output);
143                                 break;
144                         case CipherMode.CBC:
145                                 CBC (input, output);
146                                 break;
147                         case CipherMode.CFB:
148                                 CFB (input, output);
149                                 break;
150                         case CipherMode.OFB:
151                                 OFB (input, output);
152                                 break;
153                         case CipherMode.CTS:
154                                 CTS (input, output);
155                                 break;
156                         default:
157                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
158                         }
159 #endif
160                 }
161
162                 // Electronic Code Book (ECB)
163                 protected abstract void ECB (byte[] input, byte[] output); 
164
165                 // Cipher-Block-Chaining (CBC)
166                 protected virtual void CBC (byte[] input, byte[] output) 
167                 {
168                         if (encrypt) {
169                                 for (int i = 0; i < BlockSizeByte; i++)
170                                         temp[i] ^= input[i];
171                                 ECB (temp, output);
172                                 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
173                         }
174                         else {
175                                 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
176                                 ECB (input, output);
177                                 for (int i = 0; i < BlockSizeByte; i++)
178                                         output[i] ^= temp[i];
179                                 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
180                         }
181                 }
182
183 #if !MOONLIGHT
184                 // Cipher-FeedBack (CFB)
185                 // this is how *CryptoServiceProvider implements CFB
186                 // only AesCryptoServiceProvider support CFB > 8
187                 // RijndaelManaged is incompatible with this implementation (and overrides it in it's own transform)
188                 protected virtual void CFB (byte[] input, byte[] output) 
189                 {
190                         if (encrypt) {
191                                 for (int x = 0; x < BlockSizeByte; x++) {
192                                         // temp is first initialized with the IV
193                                         ECB (temp, temp2);
194
195                                         for (int i = 0; i < 1; i++)
196                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
197                                         Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
198                                         Buffer.BlockCopy (output, x, temp, BlockSizeByte - 1, 1);
199                                 }
200                         }
201                         else {
202                                 for (int x = 0; x < BlockSizeByte; x++) {
203                                         // we do not really decrypt this data!
204                                         encrypt = true;
205                                         // temp is first initialized with the IV
206                                         ECB (temp, temp2);
207                                         encrypt = false;
208
209                                         Buffer.BlockCopy (temp, 1, temp, 0, BlockSizeByte - 1);
210                                         Buffer.BlockCopy (input, x, temp, BlockSizeByte - 1, 1);
211                                         for (int i = 0; i < 1; i++)
212                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
213                                 }
214                         }
215                 }
216
217                 // Output-FeedBack (OFB)
218                 protected virtual void OFB (byte[] input, byte[] output) 
219                 {
220                         throw new CryptographicException ("OFB isn't supported by the framework");
221                 }
222
223                 // Cipher Text Stealing (CTS)
224                 protected virtual void CTS (byte[] input, byte[] output) 
225                 {
226                         throw new CryptographicException ("CTS isn't supported by the framework");
227                 }
228 #endif
229
230                 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
231                 {
232                         if (inputBuffer == null)
233                                 throw new ArgumentNullException ("inputBuffer");
234                         if (inputOffset < 0)
235                                 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
236                         if (inputCount < 0)
237                                 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
238                         // ordered to avoid possible integer overflow
239                         if (inputOffset > inputBuffer.Length - inputCount)
240                                 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
241                 }
242
243                 // this method may get called MANY times so this is the one to optimize
244                 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
245                 {
246                         if (m_disposed)
247                                 throw new ObjectDisposedException ("Object is disposed");
248                         CheckInput (inputBuffer, inputOffset, inputCount);
249                         // check output parameters
250                         if (outputBuffer == null)
251                                 throw new ArgumentNullException ("outputBuffer");
252                         if (outputOffset < 0)
253                                 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
254
255                         // ordered to avoid possible integer overflow
256                         int len = outputBuffer.Length - inputCount - outputOffset;
257 #if MOONLIGHT
258                         // only PKCS7 is supported Silverlight 2.0
259                         if (KeepLastBlock) {
260 #else
261                         if (!encrypt && (0 > len) && ((algo.Padding == PaddingMode.None) || (algo.Padding == PaddingMode.Zeros))) {
262                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
263                         } else if (KeepLastBlock) {
264 #endif
265                                 if (0 > len + BlockSizeByte) {
266                                         throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
267                                 }
268                         } else {
269                                 if (0 > len) {
270                                         // there's a special case if this is the end of the decryption process
271                                         if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
272                                                 inputCount = outputBuffer.Length - outputOffset;
273                                         else
274                                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
275                                 }
276                         }
277                         return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
278                 }
279
280                 private bool KeepLastBlock {
281                         get {
282 #if MOONLIGHT
283                                 // only PKCS7 is supported Silverlight 2.0
284                                 return !encrypt;
285 #else
286                                 return ((!encrypt) && (algo.Padding != PaddingMode.None) && (algo.Padding != PaddingMode.Zeros));
287 #endif
288                         }
289                 }
290
291                 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
292                 {
293                         int offs = inputOffset;
294                         int full;
295
296                         // this way we don't do a modulo every time we're called
297                         // and we may save a division
298                         if (inputCount != BlockSizeByte) {
299                                 if ((inputCount % BlockSizeByte) != 0)
300                                         throw new CryptographicException ("Invalid input block size.");
301
302                                 full = inputCount / BlockSizeByte;
303                         }
304                         else
305                                 full = 1;
306
307                         if (KeepLastBlock)
308                                 full--;
309
310                         int total = 0;
311
312                         if (lastBlock) {
313                                 Transform (workBuff, workout);
314                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
315                                 outputOffset += BlockSizeByte;
316                                 total += BlockSizeByte;
317                                 lastBlock = false;
318                         }
319
320                         for (int i = 0; i < full; i++) {
321                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
322                                 Transform (workBuff, workout);
323                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
324                                 offs += BlockSizeByte;
325                                 outputOffset += BlockSizeByte;
326                                 total += BlockSizeByte;
327                         }
328
329                         if (KeepLastBlock) {
330                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
331                                 lastBlock = true;
332                         }
333
334                         return total;
335                 }
336
337 #if !MOONLIGHT
338                 RandomNumberGenerator _rng;
339
340                 private void Random (byte[] buffer, int start, int length)
341                 {
342                         if (_rng == null) {
343                                 _rng = RandomNumberGenerator.Create ();
344                         }
345                         byte[] random = new byte [length];
346                         _rng.GetBytes (random);
347                         Buffer.BlockCopy (random, 0, buffer, start, length);
348                 }
349
350                 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
351                 {
352                         string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
353                         if (length >= 0)
354                                 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
355                         if (position >= 0)
356                                 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
357                         throw new CryptographicException (msg);
358                 }
359 #endif
360
361                 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
362                 {
363                         // are there still full block to process ?
364                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
365                         int rem = inputCount - full;
366                         int total = full;
367
368 #if MOONLIGHT
369                         // only PKCS7 is supported Silverlight 2.0
370                         total += BlockSizeByte;
371 #else
372                         switch (algo.Padding) {
373                         case PaddingMode.ANSIX923:
374                         case PaddingMode.ISO10126:
375                         case PaddingMode.PKCS7:
376                                 // we need to add an extra block for padding
377                                 total += BlockSizeByte;
378                                 break;
379                         default:
380                                 if (inputCount == 0)
381                                         return new byte [0];
382                                 if (rem != 0) {
383                                         if (algo.Padding == PaddingMode.None)
384                                                 throw new CryptographicException ("invalid block length");
385                                         // zero padding the input (by adding a block for the partial data)
386                                         byte[] paddedInput = new byte [full + BlockSizeByte];
387                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
388                                         inputBuffer = paddedInput;
389                                         inputOffset = 0;
390                                         inputCount = paddedInput.Length;
391                                         total = inputCount;
392                                 }
393                                 break;
394                         }
395 #endif // NET_2_1
396
397                         byte[] res = new byte [total];
398                         int outputOffset = 0;
399
400                         // process all blocks except the last (final) block
401                         while (total > BlockSizeByte) {
402                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
403                                 inputOffset += BlockSizeByte;
404                                 outputOffset += BlockSizeByte;
405                                 total -= BlockSizeByte;
406                         }
407
408                         // now we only have a single last block to encrypt
409                         byte padding = (byte) (BlockSizeByte - rem);
410 #if MOONLIGHT
411                         // only PKCS7 is supported Silverlight 2.0
412                         for (int i = res.Length; --i >= (res.Length - padding);) 
413                                 res [i] = padding;
414                         Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
415                         InternalTransformBlock (res, full, BlockSizeByte, res, full);
416 #else
417                         switch (algo.Padding) {
418                         case PaddingMode.ANSIX923:
419                                 // XX 00 00 00 00 00 00 07 (zero + padding length)
420                                 res [res.Length - 1] = padding;
421                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
422                                 // the last padded block will be transformed in-place
423                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
424                                 break;
425                         case PaddingMode.ISO10126:
426                                 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
427                                 Random (res, res.Length - padding, padding - 1);
428                                 res [res.Length - 1] = padding;
429                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
430                                 // the last padded block will be transformed in-place
431                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
432                                 break;
433                         case PaddingMode.PKCS7:
434                                 // XX 07 07 07 07 07 07 07 (padding length)
435                                 for (int i = res.Length; --i >= (res.Length - padding);) 
436                                         res [i] = padding;
437                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
438                                 // the last padded block will be transformed in-place
439                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
440                                 break;
441                         default:
442                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
443                                 break;
444                         }
445 #endif // NET_2_1
446                         return res;
447                 }
448
449                 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
450                 {
451                         if ((inputCount % BlockSizeByte) > 0)
452                                 throw new CryptographicException ("Invalid input block size.");
453
454                         int total = inputCount;
455                         if (lastBlock)
456                                 total += BlockSizeByte;
457
458                         byte[] res = new byte [total];
459                         int outputOffset = 0;
460
461                         while (inputCount > 0) {
462                                 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
463                                 inputOffset += BlockSizeByte;
464                                 outputOffset += len;
465                                 inputCount -= BlockSizeByte;
466                         }
467
468                         if (lastBlock) {
469                                 Transform (workBuff, workout);
470                                 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
471                                 outputOffset += BlockSizeByte;
472                                 lastBlock = false;
473                         }
474
475                         // total may be 0 (e.g. PaddingMode.None)
476                         byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
477 #if MOONLIGHT
478                         // only PKCS7 is supported Silverlight 2.0
479                         if ((padding == 0) || (padding > BlockSizeByte))
480                                 throw new CryptographicException (Locale.GetText ("Bad padding length."));
481                         for (int i = padding - 1; i > 0; i--) {
482                                 if (res [total - 1 - i] != padding)
483                                         throw new CryptographicException (Locale.GetText ("Bad padding at position {0}.", i));
484                         }
485                         total -= padding;
486 #else
487                         switch (algo.Padding) {
488                         case PaddingMode.ANSIX923:
489                                 if ((padding == 0) || (padding > BlockSizeByte))
490                                         ThrowBadPaddingException (algo.Padding, padding, -1);
491                                 for (int i = padding - 1; i > 0; i--) {
492                                         if (res [total - 1 - i] != 0x00)
493                                                 ThrowBadPaddingException (algo.Padding, -1, i);
494                                 }
495                                 total -= padding;
496                                 break;
497                         case PaddingMode.ISO10126:
498                                 if ((padding == 0) || (padding > BlockSizeByte))
499                                         ThrowBadPaddingException (algo.Padding, padding, -1);
500                                 total -= padding;
501                                 break;
502                         case PaddingMode.PKCS7:
503                                 if ((padding == 0) || (padding > BlockSizeByte))
504                                         ThrowBadPaddingException (algo.Padding, padding, -1);
505                                 for (int i = padding - 1; i > 0; i--) {
506                                         if (res [total - 1 - i] != padding)
507                                                 ThrowBadPaddingException (algo.Padding, -1, i);
508                                 }
509                                 total -= padding;
510                                 break;
511                         case PaddingMode.None:  // nothing to do - it's a multiple of block size
512                         case PaddingMode.Zeros: // nothing to do - user must unpad himself
513                                 break;
514                         }
515 #endif // NET_2_1
516
517                         // return output without padding
518                         if (total > 0) {
519                                 byte[] data = new byte [total];
520                                 Buffer.BlockCopy (res, 0, data, 0, total);
521                                 // zeroize decrypted data (copy with padding)
522                                 Array.Clear (res, 0, res.Length);
523                                 return data;
524                         }
525                         else
526                                 return new byte [0];
527                 }
528
529                 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
530                 {
531                         if (m_disposed)
532                                 throw new ObjectDisposedException ("Object is disposed");
533                         CheckInput (inputBuffer, inputOffset, inputCount);
534
535                         if (encrypt)
536                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
537                         else
538                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
539                 }
540         }
541 }