2010-02-28 Miguel de Icaza <miguel@novell.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Cryptography / SymmetricTransform.cs
old mode 100755 (executable)
new mode 100644 (file)
index 0efad25..ab43c4a
@@ -6,7 +6,26 @@
 //     Sebastien Pouliot <sebastien@ximian.com>
 //
 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
-// (C) 2004 Novell (http://www.novell.com)
+// Copyright (C) 2004-2008 Novell, Inc (http://www.novell.com)
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
 using System;
@@ -31,21 +50,41 @@ namespace Mono.Security.Cryptography {
                private byte[] temp2;
                private byte[] workBuff;
                private byte[] workout;
+#if !NET_2_1 || MONOTOUCH
+               // Silverlight 2.0 does not support any feedback mode
                private int FeedBackByte;
                private int FeedBackIter;
+#endif
                private bool m_disposed = false;
+               private bool lastBlock;
 
                public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
                {
                        algo = symmAlgo;
                        encrypt = encryption;
                        BlockSizeByte = (algo.BlockSize >> 3);
+
+                       if (rgbIV == null) {
+                               rgbIV = KeyBuilder.IV (BlockSizeByte);
+                       } else {
+                               rgbIV = (byte[]) rgbIV.Clone ();
+                       }
+                       // compare the IV length with the "currently selected" block size and *ignore* IV that are too big
+                       if (rgbIV.Length < BlockSizeByte) {
+                               string msg = Locale.GetText ("IV is too small ({0} bytes), it should be {1} bytes long.",
+                                       rgbIV.Length, BlockSizeByte);
+                               throw new CryptographicException (msg);
+                       }
+
                        // mode buffers
                        temp = new byte [BlockSizeByte];
-                       Buffer.BlockCopy (rgbIV, 0, temp, 0, BlockSizeByte);
+                       Buffer.BlockCopy (rgbIV, 0, temp, 0, System.Math.Min (BlockSizeByte, rgbIV.Length));
                        temp2 = new byte [BlockSizeByte];
+#if !NET_2_1 || MONOTOUCH
                        FeedBackByte = (algo.FeedbackSize >> 3);
-                       FeedBackIter = (int) BlockSizeByte / FeedBackByte;
+                       if (FeedBackByte != 0)
+                               FeedBackIter = (int) BlockSizeByte / FeedBackByte;
+#endif
                        // transform buffers
                        workBuff = new byte [BlockSizeByte];
                        workout =  new byte [BlockSizeByte];
@@ -64,7 +103,7 @@ namespace Mono.Security.Cryptography {
 
                // MUST be overriden by classes using unmanaged ressources
                // the override method must call the base class
-               protected void Dispose (bool disposing) 
+               protected virtual void Dispose (bool disposing) 
                {
                        if (!m_disposed) {
                                if (disposing) {
@@ -82,7 +121,7 @@ namespace Mono.Security.Cryptography {
                        get { return true; }
                }
 
-               public bool CanReuseTransform {
+               public virtual bool CanReuseTransform {
                        get { return false; }
                }
 
@@ -96,8 +135,12 @@ namespace Mono.Security.Cryptography {
 
                // note: Each block MUST be BlockSizeValue in size!!!
                // i.e. Any padding must be done before calling this method
-               protected void Transform (byte[] input, byte[] output) 
+               protected virtual void Transform (byte[] input, byte[] output) 
                {
+#if NET_2_1 && !MONOTOUCH
+                       // Silverlight 2.0 only supports CBC
+                       CBC (input, output);
+#else
                        switch (algo.Mode) {
                        case CipherMode.ECB:
                                ECB (input, output);
@@ -117,6 +160,7 @@ namespace Mono.Security.Cryptography {
                        default:
                                throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
                        }
+#endif
                }
 
                // Electronic Code Book (ECB)
@@ -140,6 +184,7 @@ namespace Mono.Security.Cryptography {
                        }
                }
 
+#if !NET_2_1 || MONOTOUCH
                // Cipher-FeedBack (CFB)
                protected virtual void CFB (byte[] input, byte[] output) 
                {
@@ -173,24 +218,79 @@ namespace Mono.Security.Cryptography {
                // Output-FeedBack (OFB)
                protected virtual void OFB (byte[] input, byte[] output) 
                {
-                       throw new NotImplementedException ("OFB not yet supported");
+                       throw new CryptographicException ("OFB isn't supported by the framework");
                }
 
                // Cipher Text Stealing (CTS)
                protected virtual void CTS (byte[] input, byte[] output) 
                {
-                       throw new NotImplementedException ("CTS not yet supported");
+                       throw new CryptographicException ("CTS isn't supported by the framework");
+               }
+#endif
+
+               private void CheckInput (byte[] inputBuffer, int inputOffset, int inputCount)
+               {
+                       if (inputBuffer == null)
+                               throw new ArgumentNullException ("inputBuffer");
+                       if (inputOffset < 0)
+                               throw new ArgumentOutOfRangeException ("inputOffset", "< 0");
+                       if (inputCount < 0)
+                               throw new ArgumentOutOfRangeException ("inputCount", "< 0");
+                       // ordered to avoid possible integer overflow
+                       if (inputOffset > inputBuffer.Length - inputCount)
+                               throw new ArgumentException ("inputBuffer", Locale.GetText ("Overflow"));
                }
 
                // this method may get called MANY times so this is the one to optimize
-               public virtual int TransformBlock (byte [] inputBuffer, int inputOffset, int inputCount, byte [] outputBuffer, int outputOffset) 
+               public virtual int TransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
                {
                        if (m_disposed)
                                throw new ObjectDisposedException ("Object is disposed");
-                       // reordered to avoid possible integer overflow
-                       if (outputOffset > outputBuffer.Length - inputCount)
-                               throw new CryptographicException ("Insufficient output buffer size.");
+                       CheckInput (inputBuffer, inputOffset, inputCount);
+                       // check output parameters
+                       if (outputBuffer == null)
+                               throw new ArgumentNullException ("outputBuffer");
+                       if (outputOffset < 0)
+                               throw new ArgumentOutOfRangeException ("outputOffset", "< 0");
+
+                       // ordered to avoid possible integer overflow
+                       int len = outputBuffer.Length - inputCount - outputOffset;
+#if NET_2_1 && !MONOTOUCH
+                       // only PKCS7 is supported Silverlight 2.0
+                       if (KeepLastBlock) {
+#else
+                       if (!encrypt && (0 > len) && ((algo.Padding == PaddingMode.None) || (algo.Padding == PaddingMode.Zeros))) {
+                               throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
+                       } else if (KeepLastBlock) {
+#endif
+                               if (0 > len + BlockSizeByte) {
+                                       throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
+                               }
+                       } else {
+                               if (0 > len) {
+                                       // there's a special case if this is the end of the decryption process
+                                       if (inputBuffer.Length - inputOffset - outputBuffer.Length == BlockSizeByte)
+                                               inputCount = outputBuffer.Length - outputOffset;
+                                       else
+                                               throw new CryptographicException ("outputBuffer", Locale.GetText ("Overflow"));
+                               }
+                       }
+                       return InternalTransformBlock (inputBuffer, inputOffset, inputCount, outputBuffer, outputOffset);
+               }
+
+               private bool KeepLastBlock {
+                       get {
+#if NET_2_1 && !MONOTOUCH
+                               // only PKCS7 is supported Silverlight 2.0
+                               return !encrypt;
+#else
+                               return ((!encrypt) && (algo.Padding != PaddingMode.None) && (algo.Padding != PaddingMode.Zeros));
+#endif
+                       }
+               }
 
+               private int InternalTransformBlock (byte[] inputBuffer, int inputOffset, int inputCount, byte[] outputBuffer, int outputOffset) 
+               {
                        int offs = inputOffset;
                        int full;
 
@@ -205,7 +305,19 @@ namespace Mono.Security.Cryptography {
                        else
                                full = 1;
 
+                       if (KeepLastBlock)
+                               full--;
+
                        int total = 0;
+
+                       if (lastBlock) {
+                               Transform (workBuff, workout);
+                               Buffer.BlockCopy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
+                               outputOffset += BlockSizeByte;
+                               total += BlockSizeByte;
+                               lastBlock = false;
+                       }
+
                        for (int i = 0; i < full; i++) {
                                Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
                                Transform (workBuff, workout);
@@ -215,9 +327,38 @@ namespace Mono.Security.Cryptography {
                                total += BlockSizeByte;
                        }
 
+                       if (KeepLastBlock) {
+                               Buffer.BlockCopy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
+                               lastBlock = true;
+                       }
+
                        return total;
                }
 
+#if (!NET_2_1 || MONOTOUCH)
+               RandomNumberGenerator _rng;
+
+               private void Random (byte[] buffer, int start, int length)
+               {
+                       if (_rng == null) {
+                               _rng = RandomNumberGenerator.Create ();
+                       }
+                       byte[] random = new byte [length];
+                       _rng.GetBytes (random);
+                       Buffer.BlockCopy (random, 0, buffer, start, length);
+               }
+
+               private void ThrowBadPaddingException (PaddingMode padding, int length, int position)
+               {
+                       string msg = String.Format (Locale.GetText ("Bad {0} padding."), padding);
+                       if (length >= 0)
+                               msg += String.Format (Locale.GetText (" Invalid length {0}."), length);
+                       if (position >= 0)
+                               msg += String.Format (Locale.GetText (" Error found at position {0}."), position);
+                       throw new CryptographicException (msg);
+               }
+#endif
+
                private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
                {
                        // are there still full block to process ?
@@ -225,7 +366,18 @@ namespace Mono.Security.Cryptography {
                        int rem = inputCount - full;
                        int total = full;
 
-                       if (algo.Padding != PaddingMode.PKCS7) {
+#if NET_2_1 && !MONOTOUCH
+                       // only PKCS7 is supported Silverlight 2.0
+                       total += BlockSizeByte;
+#else
+                       switch (algo.Padding) {
+                       case PaddingMode.ANSIX923:
+                       case PaddingMode.ISO10126:
+                       case PaddingMode.PKCS7:
+                               // we need to add an extra block for padding
+                               total += BlockSizeByte;
+                               break;
+                       default:
                                if (inputCount == 0)
                                        return new byte [0];
                                if (rem != 0) {
@@ -239,61 +391,134 @@ namespace Mono.Security.Cryptography {
                                        inputCount = paddedInput.Length;
                                        total = inputCount;
                                }
+                               break;
                        }
-                       else {
-                               // we need to add an extra block for padding
-                               total += BlockSizeByte;
-                       }
+#endif // NET_2_1
 
                        byte[] res = new byte [total];
                        int outputOffset = 0;
 
                        // process all blocks except the last (final) block
                        while (total > BlockSizeByte) {
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
+                               InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
                                inputOffset += BlockSizeByte;
                                outputOffset += BlockSizeByte;
                                total -= BlockSizeByte;
                        }
 
                        // now we only have a single last block to encrypt
-                       if (algo.Padding == PaddingMode.PKCS7) {
-                               byte padding = (byte) (BlockSizeByte - rem);
+                       byte padding = (byte) (BlockSizeByte - rem);
+#if NET_2_1 && !MONOTOUCH
+                       // only PKCS7 is supported Silverlight 2.0
+                       for (int i = res.Length; --i >= (res.Length - padding);) 
+                               res [i] = padding;
+                       Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
+                       InternalTransformBlock (res, full, BlockSizeByte, res, full);
+#else
+                       switch (algo.Padding) {
+                       case PaddingMode.ANSIX923:
+                               // XX 00 00 00 00 00 00 07 (zero + padding length)
+                               res [res.Length - 1] = padding;
+                               Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
+                               // the last padded block will be transformed in-place
+                               InternalTransformBlock (res, full, BlockSizeByte, res, full);
+                               break;
+                       case PaddingMode.ISO10126:
+                               // XX 3F 52 2A 81 AB F7 07 (random + padding length)
+                               Random (res, res.Length - padding, padding - 1);
+                               res [res.Length - 1] = padding;
+                               Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
+                               // the last padded block will be transformed in-place
+                               InternalTransformBlock (res, full, BlockSizeByte, res, full);
+                               break;
+                       case PaddingMode.PKCS7:
+                               // XX 07 07 07 07 07 07 07 (padding length)
                                for (int i = res.Length; --i >= (res.Length - padding);) 
                                        res [i] = padding;
                                Buffer.BlockCopy (inputBuffer, inputOffset, res, full, rem);
                                // the last padded block will be transformed in-place
-                               TransformBlock (res, full, BlockSizeByte, res, full);
+                               InternalTransformBlock (res, full, BlockSizeByte, res, full);
+                               break;
+                       default:
+                               InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
+                               break;
                        }
-                       else
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
-
+#endif // NET_2_1
                        return res;
                }
 
-               private byte[] FinalDecrypt (byte [] inputBuffer, int inputOffset, int inputCount) 
+               private byte[] FinalDecrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
                {
                        if ((inputCount % BlockSizeByte) > 0)
                                throw new CryptographicException ("Invalid input block size.");
 
                        int total = inputCount;
+                       if (lastBlock)
+                               total += BlockSizeByte;
+
                        byte[] res = new byte [total];
                        int outputOffset = 0;
+
                        while (inputCount > 0) {
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
+                               int len = InternalTransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, outputOffset);
                                inputOffset += BlockSizeByte;
-                               outputOffset += BlockSizeByte;
+                               outputOffset += len;
                                inputCount -= BlockSizeByte;
                        }
 
+                       if (lastBlock) {
+                               Transform (workBuff, workout);
+                               Buffer.BlockCopy (workout, 0, res, outputOffset, BlockSizeByte);
+                               outputOffset += BlockSizeByte;
+                               lastBlock = false;
+                       }
+
+                       // total may be 0 (e.g. PaddingMode.None)
+                       byte padding = ((total > 0) ? res [total - 1] : (byte) 0);
+#if NET_2_1 && !MONOTOUCH
+                       // only PKCS7 is supported Silverlight 2.0
+                       if ((padding == 0) || (padding > BlockSizeByte))
+                               throw new CryptographicException (Locale.GetText ("Bad padding length."));
+                       for (int i = padding - 1; i > 0; i--) {
+                               if (res [total - 1 - i] != padding)
+                                       throw new CryptographicException (Locale.GetText ("Bad padding at position {0}.", i));
+                       }
+                       total -= padding;
+#else
                        switch (algo.Padding) {
-                               case PaddingMode.None:  // nothing to do - it's a multiple of block size
-                               case PaddingMode.Zeros: // nothing to do - user must unpad himself
-                                       break;
-                               case PaddingMode.PKCS7:
-                                       total -= res [total - 1];
-                                       break;
+                       case PaddingMode.ANSIX923:
+                               if ((padding == 0) || (padding > BlockSizeByte))
+                                       ThrowBadPaddingException (algo.Padding, padding, -1);
+                               for (int i = padding - 1; i > 0; i--) {
+                                       if (res [total - 1 - i] != 0x00)
+                                               ThrowBadPaddingException (algo.Padding, -1, i);
+                               }
+                               total -= padding;
+                               break;
+                       case PaddingMode.ISO10126:
+                               if ((padding == 0) || (padding > BlockSizeByte))
+                                       ThrowBadPaddingException (algo.Padding, padding, -1);
+                               total -= padding;
+                               break;
+                       case PaddingMode.PKCS7:
+                               if ((padding == 0) || (padding > BlockSizeByte))
+                                       ThrowBadPaddingException (algo.Padding, padding, -1);
+                               for (int i = padding - 1; i > 0; i--) {
+                                       if (res [total - 1 - i] != padding)
+                                               ThrowBadPaddingException (algo.Padding, -1, i);
+                               }
+                               total -= padding;
+                               break;
+#else
+                       case PaddingMode.PKCS7:
+                               total -= padding;
+                               break;
+
+                       case PaddingMode.None:  // nothing to do - it's a multiple of block size
+                       case PaddingMode.Zeros: // nothing to do - user must unpad himself
+                               break;
                        }
+#endif // NET_2_1
 
                        // return output without padding
                        if (total > 0) {
@@ -307,10 +532,11 @@ namespace Mono.Security.Cryptography {
                                return new byte [0];
                }
 
-               public virtual byte [] TransformFinalBlock (byte [] inputBuffer, int inputOffset, int inputCount) 
+               public virtual byte[] TransformFinalBlock (byte[] inputBuffer, int inputOffset, int inputCount) 
                {
                        if (m_disposed)
                                throw new ObjectDisposedException ("Object is disposed");
+                       CheckInput (inputBuffer, inputOffset, inputCount);
 
                        if (encrypt)
                                return FinalEncrypt (inputBuffer, inputOffset, inputCount);