merge r98600
[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                 private int BlockSizeByte;
49                 private byte[] temp;
50                 private byte[] temp2;
51                 private byte[] workBuff;
52                 private byte[] workout;
53                 private int FeedBackByte;
54                 private int FeedBackIter;
55                 private bool m_disposed = false;
56                 private bool lastBlock;
57
58                 public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
59                 {
60                         algo = symmAlgo;
61                         encrypt = encryption;
62                         BlockSizeByte = (algo.BlockSize >> 3);
63
64                         if (rgbIV == null) {
65                                 rgbIV = KeyBuilder.IV (BlockSizeByte);
66                         } else {
67                                 rgbIV = (byte[]) rgbIV.Clone ();
68                         }
69 #if NET_2_0
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 #endif
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                         if (FeedBackByte != 0)
83                                 FeedBackIter = (int) BlockSizeByte / FeedBackByte;
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                         switch (algo.Mode) {
137                         case CipherMode.ECB:
138                                 ECB (input, output);
139                                 break;
140                         case CipherMode.CBC:
141                                 CBC (input, output);
142                                 break;
143                         case CipherMode.CFB:
144                                 CFB (input, output);
145                                 break;
146                         case CipherMode.OFB:
147                                 OFB (input, output);
148                                 break;
149                         case CipherMode.CTS:
150                                 CTS (input, output);
151                                 break;
152                         default:
153                                 throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
154                         }
155                 }
156
157                 // Electronic Code Book (ECB)
158                 protected abstract void ECB (byte[] input, byte[] output); 
159
160                 // Cipher-Block-Chaining (CBC)
161                 protected virtual void CBC (byte[] input, byte[] output) 
162                 {
163                         if (encrypt) {
164                                 for (int i = 0; i < BlockSizeByte; i++)
165                                         temp[i] ^= input[i];
166                                 ECB (temp, output);
167                                 Buffer.BlockCopy (output, 0, temp, 0, BlockSizeByte);
168                         }
169                         else {
170                                 Buffer.BlockCopy (input, 0, temp2, 0, BlockSizeByte);
171                                 ECB (input, output);
172                                 for (int i = 0; i < BlockSizeByte; i++)
173                                         output[i] ^= temp[i];
174                                 Buffer.BlockCopy (temp2, 0, temp, 0, BlockSizeByte);
175                         }
176                 }
177
178                 // Cipher-FeedBack (CFB)
179                 protected virtual void CFB (byte[] input, byte[] output) 
180                 {
181                         if (encrypt) {
182                                 for (int x = 0; x < FeedBackIter; x++) {
183                                         // temp is first initialized with the IV
184                                         ECB (temp, temp2);
185
186                                         for (int i = 0; i < FeedBackByte; i++)
187                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
188                                         Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
189                                         Buffer.BlockCopy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
190                                 }
191                         }
192                         else {
193                                 for (int x = 0; x < FeedBackIter; x++) {
194                                         // we do not really decrypt this data!
195                                         encrypt = true;
196                                         // temp is first initialized with the IV
197                                         ECB (temp, temp2);
198                                         encrypt = false;
199
200                                         Buffer.BlockCopy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
201                                         Buffer.BlockCopy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
202                                         for (int i = 0; i < FeedBackByte; i++)
203                                                 output[i + x] = (byte)(temp2[i] ^ input[i + x]);
204                                 }
205                         }
206                 }
207
208                 // Output-FeedBack (OFB)
209                 protected virtual void OFB (byte[] input, byte[] output) 
210                 {
211                         throw new CryptographicException ("OFB isn't supported by the framework");
212                 }
213
214                 // Cipher Text Stealing (CTS)
215                 protected virtual void CTS (byte[] input, byte[] output) 
216                 {
217                         throw new CryptographicException ("CTS isn't supported by the framework");
218                 }
219
220                 private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
221                 {
222                         if (inputBuffer == null)
223                                 throw new ArgumentNullException ("inputBuffer");
224                         if (inputOffset < 0)
225                                 throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
226                         if (inputCount < 0)
227                                 throw new ArgumentOutOfRangeException ("inputCount", "< 0");
228                         // ordered to avoid possible integer overflow
229                         if (inputOffset > inputBuffer.Length - inputCount)
230                                 throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
231                 }
232
233                 // this method may get called MANY times so this is the one to optimize
234                 public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
235                 {
236                         if (m_disposed)
237                                 throw new ObjectDisposedException ("Object is disposed");
238                         CheckInput (inputBuffer, inputOffset, inputCount);
239                         // check output parameters
240                         if (outputBuffer == null)
241                                 throw new ArgumentNullException ("outputBuffer");
242                         if (outputOffset < 0)
243                                 throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
244
245                         // ordered to avoid possible integer overflow
246                         int len = outputBuffer.Length - inputCount - outputOffset;
247                         if (!encrypt && (0 > len) && ((algo.Padding == PaddingMode.None) || (algo.Padding == PaddingMode.Zeros))) {
248                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
249                         } else  if (KeepLastBlock) {
250                                 if (0 > len + BlockSizeByte) {
251 #if NET_2_0
252                                         throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
253 #else
254                                         throw new IndexOutOfRangeException (Locale.GetText ("Overflow"));
255 #endif
256                                 }
257                         } else {
258                                 if (0 > len) {
259                                         // there's a special case if this is the end of the decryption process
260                                         if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
261                                                 inputCount = outputBuffer.Length - outputOffset;
262                                         else
263                                                 throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
264                                 }
265                         }
266                         return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
267                 }
268
269                 private bool KeepLastBlock {
270                         get {
271                                 return ((!encrypt) && (algo.Padding != PaddingMode.None) && (algo.Padding != PaddingMode.Zeros));
272                         }
273                 }
274
275                 private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
276                 {
277                         int offs = inputOffset;
278                         int full;
279
280                         // this way we don't do a modulo every time we're called
281                         // and we may save a division
282                         if (inputCount != BlockSizeByte) {
283                                 if ((inputCount % BlockSizeByte) != 0)
284                                         throw new CryptographicException ("Invalid input block size.");
285
286                                 full = inputCount / BlockSizeByte;
287                         }
288                         else
289                                 full = 1;
290
291                         if (KeepLastBlock)
292                                 full--;
293
294                         int total = 0;
295
296                         if (lastBlock) {
297                                 Transform (workBuff, workout);
298                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
299                                 outputOffset += BlockSizeByte;
300                                 total += BlockSizeByte;
301                                 lastBlock = false;
302                         }
303
304                         for (int i = 0; i < full; i++) {
305                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
306                                 Transform (workBuff, workout);
307                                 Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
308                                 offs += BlockSizeByte;
309                                 outputOffset += BlockSizeByte;
310                                 total += BlockSizeByte;
311                         }
312
313                         if (KeepLastBlock) {
314                                 Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
315                                 lastBlock = true;
316                         }
317
318                         return total;
319                 }
320
321 #if NET_2_0
322                 RandomNumberGenerator _rng;
323
324                 private void Random (byte[] buffer, int start, int length)
325                 {
326                         if (_rng == null) {
327                                 _rng = RandomNumberGenerator.Create ();
328                         }
329                         byte[] random = new byte [length];
330                         _rng.GetBytes (random);
331                         Buffer.BlockCopy (random, 0, buffer, start, length);
332                 }
333
334                 private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
335                 {
336                         string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
337                         if (length >= 0)
338                                 msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
339                         if (position >= 0)
340                                 msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
341                         throw new CryptographicException (msg);
342                 }
343 #endif
344
345                 private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
346                 {
347                         // are there still full block to process ?
348                         int full = (inputCount / BlockSizeByte) * BlockSizeByte;
349                         int rem = inputCount - full;
350                         int total = full;
351
352                         switch (algo.Padding) {
353 #if NET_2_0
354                         case PaddingMode.ANSIX923:
355                         case PaddingMode.ISO10126:
356 #endif
357                         case PaddingMode.PKCS7:
358                                 // we need to add an extra block for padding
359                                 total += BlockSizeByte;
360                                 break;
361                         default:
362                                 if (inputCount == 0)
363                                         return new byte [0];
364                                 if (rem != 0) {
365                                         if (algo.Padding == PaddingMode.None)
366                                                 throw new CryptographicException ("invalid block length");
367                                         // zero padding the input (by adding a block for the partial data)
368                                         byte[] paddedInput = new byte [full + BlockSizeByte];
369                                         Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
370                                         inputBuffer = paddedInput;
371                                         inputOffset = 0;
372                                         inputCount = paddedInput.Length;
373                                         total = inputCount;
374                                 }
375                                 break;
376                         }
377
378                         byte[] res = new byte [total];
379                         int outputOffset = 0;
380
381                         // process all blocks except the last (final) block
382                         while (total > BlockSizeByte) {
383                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
384                                 inputOffset += BlockSizeByte;
385                                 outputOffset += BlockSizeByte;
386                                 total -= BlockSizeByte;
387                         }
388
389                         // now we only have a single last block to encrypt
390                         byte padding = (byte) (BlockSizeByte - rem);
391                         switch (algo.Padding) {
392 #if NET_2_0
393                         case PaddingMode.ANSIX923:
394                                 // XX 00 00 00 00 00 00 07 (zero + padding length)
395                                 res [res.Length - 1] = padding;
396                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
397                                 // the last padded block will be transformed in-place
398                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
399                                 break;
400                         case PaddingMode.ISO10126:
401                                 // XX 3F 52 2A 81 AB F7 07 (random + padding length)
402                                 Random (res, res.Length - padding, padding - 1);
403                                 res [res.Length - 1] = padding;
404                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
405                                 // the last padded block will be transformed in-place
406                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
407                                 break;
408 #endif
409                         case PaddingMode.PKCS7:
410                                 // XX 07 07 07 07 07 07 07 (padding length)
411                                 for (int i = res.Length; --i >= (res.Length - padding);) 
412                                         res [i] = padding;
413                                 Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
414                                 // the last padded block will be transformed in-place
415                                 InternalTransformBlock (res, full, BlockSizeByte, res, full);
416                                 break;
417                         default:
418                                 InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
419                                 break;
420                         }
421                         return res;
422                 }
423
424                 private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
425                 {
426                         if ((inputCount % BlockSizeByte) > 0)
427                                 throw new CryptographicException ("Invalid input block size.");
428
429                         int total = inputCount;
430                         if (lastBlock)
431                                 total += BlockSizeByte;
432
433                         byte[] res = new byte [total];
434                         int outputOffset = 0;
435
436                         while (inputCount > 0) {
437                                 int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
438                                 inputOffset += BlockSizeByte;
439                                 outputOffset += len;
440                                 inputCount -= BlockSizeByte;
441                         }
442
443                         if (lastBlock) {
444                                 Transform (workBuff, workout);
445                                 Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
446                                 outputOffset += BlockSizeByte;
447                                 lastBlock = false;
448                         }
449
450                         // total may be 0 (e.g. PaddingMode.None)
451                         byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
452                         switch (algo.Padding) {
453 #if NET_2_0
454                         case PaddingMode.ANSIX923:
455                                 if ((padding == 0) || (padding > BlockSizeByte))
456                                         ThrowBadPaddingException (algo.Padding, padding, -1);
457                                 for (int i = padding - 1; i > 0; i--) {
458                                         if (res [total - 1 - i] != 0x00)
459                                                 ThrowBadPaddingException (algo.Padding, -1, i);
460                                 }
461                                 total -= padding;
462                                 break;
463                         case PaddingMode.ISO10126:
464                                 if ((padding == 0) || (padding > BlockSizeByte))
465                                         ThrowBadPaddingException (algo.Padding, padding, -1);
466                                 total -= padding;
467                                 break;
468                         case PaddingMode.PKCS7:
469                                 if ((padding == 0) || (padding > BlockSizeByte))
470                                         ThrowBadPaddingException (algo.Padding, padding, -1);
471                                 for (int i = padding - 1; i > 0; i--) {
472                                         if (res [total - 1 - i] != padding)
473                                                 ThrowBadPaddingException (algo.Padding, -1, i);
474                                 }
475                                 total -= padding;
476                                 break;
477 #else
478                         case PaddingMode.PKCS7:
479                                 total -= padding;
480                                 break;
481 #endif
482                         case PaddingMode.None:  // nothing to do - it's a multiple of block size
483                         case PaddingMode.Zeros: // nothing to do - user must unpad himself
484                                 break;
485                         }
486
487                         // return output without padding
488                         if (total > 0) {
489                                 byte[] data = new byte [total];
490                                 Buffer.BlockCopy (res, 0, data, 0, total);
491                                 // zeroize decrypted data (copy with padding)
492                                 Array.Clear (res, 0, res.Length);
493                                 return data;
494                         }
495                         else
496                                 return new byte [0];
497                 }
498
499                 public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
500                 {
501                         if (m_disposed)
502                                 throw new ObjectDisposedException ("Object is disposed");
503                         CheckInput (inputBuffer, inputOffset, inputCount);
504
505                         if (encrypt)
506                                 return FinalEncrypt (inputBuffer, inputOffset, inputCount);
507                         else
508                                 return FinalDecrypt (inputBuffer, inputOffset, inputCount);
509                 }
510         }
511 }