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