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