do not check order sequence if option /order was not used
[mono.git] / mcs / class / corlib / System.Security.Cryptography / SymmetricAlgorithm.cs
old mode 100755 (executable)
new mode 100644 (file)
index 9781c7e..1a33141
 //
 // Authors:
 //   Thomas Neidhart (tome@sbox.tugraz.at)
-//   Sebastien Pouliot (spouliot@motus.com)
+//   Sebastien Pouliot <sebastien@ximian.com>
 //
 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2006 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;
+using System.Globalization;
+using System.Runtime.InteropServices;
+using Mono.Security.Cryptography;
 
 namespace System.Security.Cryptography {
 
-       // This class implement most of the common code required for symmetric
-       // algorithm transforms, like:
-       // - CipherMode: Builds CBC and CFB on top of (descendant supplied) ECB
-       // - PaddingMode, transform properties, multiple blocks, reuse...
-       //
-       // Descendants MUST:
-       // - intialize themselves (like key expansion, ...)
-       // - override the ECB (Electronic Code Book) method which will only be
-       //   called using BlockSize byte[] array.
-       internal abstract class SymmetricTransform : ICryptoTransform {
-               protected SymmetricAlgorithm algo;
-               protected bool encrypt;
-               private int BlockSizeByte;
-               private byte[] temp;
-               private byte[] temp2;
-               private byte[] workBuff;
-               private byte[] workout;
-               private int FeedBackByte;
-               private int FeedBackIter;
-               private bool m_disposed = false;
-
-               public SymmetricTransform (SymmetricAlgorithm symmAlgo, bool encryption, byte[] rgbIV) 
-               {
-                       algo = symmAlgo;
-                       encrypt = encryption;
-                       BlockSizeByte = (algo.BlockSize >> 3);
-                       // mode buffers
-                       temp = new byte [BlockSizeByte];
-                       Array.Copy (rgbIV, 0, temp, 0, BlockSizeByte);
-                       temp2 = new byte [BlockSizeByte];
-                       FeedBackByte = (algo.FeedbackSize >> 3);
-                       FeedBackIter = (int) BlockSizeByte / FeedBackByte;
-                       // transform buffers
-                       workBuff = new byte [BlockSizeByte];
-                       workout =  new byte [BlockSizeByte];
-               }
-
-               ~SymmetricTransform () 
-               {
-                       Dispose (false);
-               }
-
-               void IDisposable.Dispose () 
-               {
-                       Dispose (true);
-                       GC.SuppressFinalize (this);  // Finalization is now unnecessary
-               }
-
-               // MUST be overriden by classes using unmanaged ressources
-               // the override method must call the base class
-               protected void Dispose (bool disposing) 
-               {
-                       if (!m_disposed) {
-                               if (disposing) {
-                                       // dispose managed object: zeroize and free
-                                       Array.Clear (temp, 0, BlockSizeByte);
-                                       temp = null;
-                                       Array.Clear (temp2, 0, BlockSizeByte);
-                                       temp2 = null;
-                               }
-                               m_disposed = true;
-                       }
-               }
-
-               public virtual bool CanTransformMultipleBlocks {
-                       get { return true; }
-               }
-
-               public bool CanReuseTransform {
-                       get { return false; }
-               }
-
-               public virtual int InputBlockSize {
-                       get { return BlockSizeByte; }
-               }
-
-               public virtual int OutputBlockSize {
-                       get { return BlockSizeByte; }
-               }
-
-               // 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) 
-               {
-                       switch (algo.Mode) {
-                       case CipherMode.ECB:
-                               ECB (input, output);
-                               break;
-                       case CipherMode.CBC:
-                               CBC (input, output);
-                               break;
-                       case CipherMode.CFB:
-                               CFB (input, output);
-                               break;
-                       case CipherMode.OFB:
-                               OFB (input, output);
-                               break;
-                       case CipherMode.CTS:
-                               CTS (input, output);
-                               break;
-                       default:
-                               throw new NotImplementedException ("Unkown CipherMode" + algo.Mode.ToString ());
-                       }
-               }
-
-               // Electronic Code Book (ECB)
-               protected abstract void ECB (byte[] input, byte[] output); 
-
-               // Cipher-Block-Chaining (CBC)
-               protected virtual void CBC (byte[] input, byte[] output) 
-               {
-                       if (encrypt) {
-                               for (int i = 0; i < BlockSizeByte; i++)
-                                       temp[i] ^= input[i];
-                               ECB (temp, output);
-                               Array.Copy (output, 0, temp, 0, BlockSizeByte);
-                       }
-                       else {
-                               Array.Copy (input, 0, temp2, 0, BlockSizeByte);
-                               ECB (input, output);
-                               for (int i = 0; i < BlockSizeByte; i++)
-                                       output[i] ^= temp[i];
-                               Array.Copy (temp2, 0, temp, 0, BlockSizeByte);
-                       }
-               }
-
-               // Cipher-FeedBack (CFB)
-               protected virtual void CFB (byte[] input, byte[] output) 
-               {
-                       if (encrypt) {
-                               for (int x = 0; x < FeedBackIter; x++) {
-                                       // temp is first initialized with the IV
-                                       ECB (temp, temp2);
-
-                                       for (int i = 0; i < FeedBackByte; i++)
-                                               output[i + x] = (byte)(temp2[i] ^ input[i + x]);
-                                       Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
-                                       Array.Copy (output, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
-                               }
-                       }
-                       else {
-                               for (int x = 0; x < FeedBackIter; x++) {
-                                       // we do not really decrypt this data!
-                                       encrypt = true;
-                                       // temp is first initialized with the IV
-                                       ECB (temp, temp2);
-                                       encrypt = false;
-
-                                       Array.Copy (temp, FeedBackByte, temp, 0, BlockSizeByte - FeedBackByte);
-                                       Array.Copy (input, x, temp, BlockSizeByte - FeedBackByte, FeedBackByte);
-                                       for (int i = 0; i < FeedBackByte; i++)
-                                               output[i + x] = (byte)(temp2[i] ^ input[i + x]);
-                               }
-                       }
-               }
-
-               // Output-FeedBack (OFB)
-               protected virtual void OFB (byte[] input, byte[] output) 
-               {
-                       throw new NotImplementedException ("OFB not yet supported");
-               }
-
-               // Cipher Text Stealing (CTS)
-               protected virtual void CTS (byte[] input, byte[] output) 
-               {
-                       throw new NotImplementedException ("CTS not yet supported");
-               }
-
-               // 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) 
-               {
-                       if (m_disposed)
-                               throw new ObjectDisposedException ("Object is disposed");
-
-                       if (outputOffset + inputCount > outputBuffer.Length)
-                               throw new CryptographicException ("Insufficient output buffer size.");
-
-                       int offs = inputOffset;
-                       int full;
-
-                       // this way we don't do a modulo every time we're called
-                       // and we may save a division
-                       if (inputCount != BlockSizeByte) {
-                               if ((inputCount % BlockSizeByte) != 0)
-                                       throw new CryptographicException ("Invalid input block size.");
-
-                               full = inputCount / BlockSizeByte;
-                       }
-                       else
-                               full = 1;
-
-                       int total = 0;
-                       for (int i = 0; i < full; i++) {
-                               Array.Copy (inputBuffer, offs, workBuff, 0, BlockSizeByte);
-                               Transform (workBuff, workout);
-                               Array.Copy (workout, 0, outputBuffer, outputOffset, BlockSizeByte);
-                               offs += BlockSizeByte;
-                               outputOffset += BlockSizeByte;
-                               total += BlockSizeByte;
-                       }
-
-                       return total;
-               }
-
-               private byte[] FinalEncrypt (byte[] inputBuffer, int inputOffset, int inputCount) 
-               {
-                       // are there still full block to process ?
-                       int full = (inputCount / BlockSizeByte) * BlockSizeByte;
-                       int rem = inputCount - full;
-                       int total = full;
-
-                       if (algo.Padding != PaddingMode.PKCS7) {
-                               if (inputCount == 0)
-                                       return new byte [0];
-                               if (rem != 0) {
-                                       if (algo.Padding == PaddingMode.None)
-                                               throw new CryptographicException ("invalid block length");
-                                       // zero padding the input (by adding a block for the partial data)
-                                       byte[] paddedInput = new byte [full + BlockSizeByte];
-                                       Buffer.BlockCopy (inputBuffer, inputOffset, paddedInput, 0, inputCount);
-                                       inputBuffer = paddedInput;
-                                       inputOffset = 0;
-                                       inputCount = paddedInput.Length;
-                                       total = inputCount;
-                               }
-                       }
-                       else {
-                               // we need to add an extra block for padding
-                               total += BlockSizeByte;
-                       }
-
-                       byte[] res = new byte [total];
-
-                       // process all blocks except the last (final) block
-                       while (total > BlockSizeByte) {
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
-                               inputOffset += BlockSizeByte;
-                               total -= BlockSizeByte;
-                       }
-
-                       // now we only have a single last block to encrypt
-                       if (algo.Padding == PaddingMode.PKCS7) {
-                               byte padding = (byte) (BlockSizeByte - rem);
-                               for (int i = res.Length; --i >= (res.Length - padding);) 
-                                       res [i] = padding;
-                               Array.Copy (inputBuffer, inputOffset, res, full, rem);
-                               // the last padded block will be transformed in-place
-                               TransformBlock (res, full, BlockSizeByte, res, full);
-                       }
-                       else
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
-
-                       return res;
-               }
-
-               private byte[] FinalDecrypt (byte [] inputBuffer, int inputOffset, int inputCount) 
-               {
-                       if ((inputCount % BlockSizeByte) > 0)
-                               throw new CryptographicException ("Invalid input block size.");
-
-                       int total = inputCount;
-                       byte[] res = new byte [total];
-                       while (inputCount > 0) {
-                               TransformBlock (inputBuffer, inputOffset, BlockSizeByte, res, inputOffset);
-                               inputOffset += BlockSizeByte;
-                               inputCount -= BlockSizeByte;
-                       }
-
-                       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;
-                       }
-
-                       // return output without padding
-                       if (total > 0) {
-                               byte[] data = new byte [total];
-                               Array.Copy (res, 0, data, 0, total);
-                               // zeroize decrypted data (copy with padding)
-                               Array.Clear (res, 0, res.Length);
-                               return data;
-                       }
-                       else
-                               return res;
-               }
-
-               public virtual byte [] TransformFinalBlock (byte [] inputBuffer, int inputOffset, int inputCount) 
-               {
-                       if (m_disposed)
-                               throw new ObjectDisposedException ("Object is disposed");
-
-                       if (encrypt)
-                               return FinalEncrypt (inputBuffer, inputOffset, inputCount);
-                       else
-                               return FinalDecrypt (inputBuffer, inputOffset, inputCount);
-               }
-       }
-
-       /// <summary>
-       /// Abstract base class for all cryptographic symmetric algorithms.
-       /// Available algorithms include:
-       /// DES, RC2, Rijndael, TripleDES
-       /// </summary>
+       [ComVisible (true)]
        public abstract class SymmetricAlgorithm : IDisposable {
-               protected int BlockSizeValue; // The block size of the cryptographic operation in bits. 
-               protected int FeedbackSizeValue; // The feedback size of the cryptographic operation in bits. 
-               protected byte[] IVValue; // The initialization vector ( IV) for the symmetric algorithm. 
-               protected int KeySizeValue; // The size of the secret key used by the symmetric algorithm in bits. 
-               protected byte[] KeyValue; // The secret key for the symmetric algorithm. 
-               protected KeySizes[] LegalBlockSizesValue; // Specifies the block sizes that are supported by the symmetric algorithm. 
-               protected KeySizes[] LegalKeySizesValue; // Specifies the key sizes that are supported by the symmetric algorithm. 
-               protected CipherMode ModeValue; // Represents the cipher mode used in the symmetric algorithm. 
-               protected PaddingMode PaddingValue; // Represents the padding mode used in the symmetric algorithm. 
+               protected int BlockSizeValue; 
+               protected byte[] IVValue; 
+               protected int KeySizeValue; 
+               protected byte[] KeyValue; 
+               protected KeySizes[] LegalBlockSizesValue; 
+               protected KeySizes[] LegalKeySizesValue; 
+#if MOONLIGHT
+               // Silverlight 2.0 only supports CBC
+               internal int FeedbackSizeValue;
+               internal CipherMode ModeValue;
+               internal PaddingMode PaddingValue;
+#else
+               protected int FeedbackSizeValue;
+               protected CipherMode ModeValue;
+               protected PaddingMode PaddingValue;
+#endif
                private bool m_disposed;
 
-               /// <summary>
-               /// Called from constructor of derived class.
-               /// </summary>
-               public SymmetricAlgorithm () 
+               protected SymmetricAlgorithm ()
                {
                        ModeValue = CipherMode.CBC;
                        PaddingValue = PaddingMode.PKCS7;
-                       m_disposed = false;
-               }
-               
-               /// <summary>
-               /// Called from constructor of derived class.
-               /// </summary>
-               ~SymmetricAlgorithm () 
-               {
-                       Dispose (false);
                }
 
-               public void Clear() 
+#if NET_4_0
+               public void Dispose ()
+#else
+               void IDisposable.Dispose () 
+#endif
                {
                        Dispose (true);
+                       GC.SuppressFinalize (this);  // Finalization is now unnecessary
                }
 
-               void IDisposable.Dispose () 
+               public void Clear() 
                {
                        Dispose (true);
-                       GC.SuppressFinalize (this);  // Finalization is now unnecessary
                }
 
                protected virtual void Dispose (bool disposing) 
@@ -376,189 +92,146 @@ namespace System.Security.Cryptography {
                        }
                }
 
-               /// <summary>
-               /// Gets or sets the actual BlockSize
-               /// </summary>
                public virtual int BlockSize {
                        get { return this.BlockSizeValue; }
                        set {
-                               if (KeySizes.IsLegalKeySize (this.LegalBlockSizesValue, value))
-                                       this.BlockSizeValue = value;
-                               else
-                                       throw new CryptographicException("block size not supported by algorithm");
+                               if (!KeySizes.IsLegalKeySize (this.LegalBlockSizesValue, value)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("block size not supported by algorithm"));
+                               }
+                               // re-setting the same BlockSize *doesn't* regenerate the IV
+                               if (BlockSizeValue != value) {
+                                       BlockSizeValue = value;
+                                       IVValue = null;
+                               }
                        }
                }
 
-               /// <summary>
-               /// Gets or sets the actual FeedbackSize
-               /// </summary>
                public virtual int FeedbackSize {
                        get { return this.FeedbackSizeValue; }
                        set {
-                               if (value > this.BlockSizeValue)
-                                       throw new CryptographicException("feedback size larger than block size");
-                               else
-                                       this.FeedbackSizeValue = value;
+                               if ((value <= 0) || (value > this.BlockSizeValue)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("feedback size larger than block size"));
+                               }
+                               this.FeedbackSizeValue = value;
                        }
                }
                
-               /// <summary>
-               /// Gets or sets the actual Initial Vector
-               /// </summary>
                public virtual byte[] IV {
                        get {
                                if (this.IVValue == null)
                                        GenerateIV();
 
-                               return this.IVValue;
+                               return (byte[]) this.IVValue.Clone ();
                        }
                        set {
                                if (value == null)
-                                       throw new ArgumentNullException ("tried setting initial vector to null");
-                                       
-                               if (value.Length * 8 != this.BlockSizeValue)
-                                       throw new CryptographicException ("IV length must match block size");
-                               
-                               this.IVValue = new byte [value.Length];
-                               Array.Copy (value, 0, this.IVValue, 0, value.Length);
+                                       throw new ArgumentNullException ("IV");
+                               // 2.0 is stricter for IV length - which is bad for IV-less stream ciphers like RC4
+                               if ((value.Length << 3) != this.BlockSizeValue) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("IV length is different than block size"));
+                               }
+                               this.IVValue = (byte[]) value.Clone ();
                        }
                }
 
-               /// <summary>
-               /// Gets or sets the actual key
-               /// </summary>
                public virtual byte[] Key {
                        get {
                                if (this.KeyValue == null)
                                        GenerateKey();
 
-                               return this.KeyValue;
+                               return (byte[]) this.KeyValue.Clone ();
                        }
                        set {
                                if (value == null)
-                                       throw new ArgumentNullException ("tried setting key to null");
-
-                               if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, value.Length * 8))
-                                       throw new CryptographicException ("key size not supported by algorithm");
+                                       throw new ArgumentNullException ("Key");
 
-                               this.KeySizeValue = value.Length * 8;
-                               this.KeyValue = new byte [value.Length];
-                               Array.Copy (value, 0, this.KeyValue, 0, value.Length);
+                               int length = (value.Length << 3);
+                               if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, length)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("Key size not supported by algorithm"));
+                               }
+                               this.KeySizeValue = length;
+                               this.KeyValue = (byte[]) value.Clone ();
                        }
                }
                
-               /// <summary>
-               /// Gets or sets the actual key size in bits
-               /// </summary>
                public virtual int KeySize {
                        get { return this.KeySizeValue; }
                        set {
-                               if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, value))
-                                       throw new CryptographicException ("key size not supported by algorithm");
-                               
-                               this.KeyValue = null;
-                               this.KeySizeValue = value;
+                               if (!KeySizes.IsLegalKeySize (this.LegalKeySizesValue, value)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("Key size not supported by algorithm"));
+                               }
+                               // re-setting the same KeySize *does* regenerate the key
+                               KeySizeValue = value;
+                               KeyValue = null;
                        }
                }
 
-               /// <summary>
-               /// Gets all legal block sizes
-               /// </summary>
                public virtual KeySizes[] LegalBlockSizes {
                        get { return this.LegalBlockSizesValue; }
                }
 
-               /// <summary>
-               /// Gets all legal key sizes
-               /// </summary>
                public virtual KeySizes[] LegalKeySizes {
                        get { return this.LegalKeySizesValue; }
                }
 
-               /// <summary>
-               /// Gets or sets the actual cipher mode
-               /// </summary>
                public virtual CipherMode Mode {
                        get { return this.ModeValue; }
                        set {
-                               if (Enum.IsDefined( ModeValue.GetType (), value))
-                                       this.ModeValue = value;
-                               else
-                                       throw new CryptographicException ("padding mode not available");
+                               if (!Enum.IsDefined (ModeValue.GetType (), value)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("Cipher mode not available"));
+                               }
+                               
+                               this.ModeValue = value;
                        }
                }
 
-               /// <summary>
-               /// Gets or sets the actual padding
-               /// </summary>
                public virtual PaddingMode Padding {
                        get { return this.PaddingValue; }
                        set {
-                               if (Enum.IsDefined (PaddingValue.GetType (), value))
-                                       this.PaddingValue = value;
-                               else
-                                       throw new CryptographicException ("padding mode not available");
+                               if (!Enum.IsDefined (PaddingValue.GetType (), value)) {
+                                       throw new CryptographicException (
+                                               Locale.GetText ("Padding mode not available"));
+                               }
+                               
+                               this.PaddingValue = value;
                        }
                }
 
-               /// <summary>
-               /// Gets an Decryptor transform object to work with a CryptoStream
-               /// </summary>
                public virtual ICryptoTransform CreateDecryptor () 
                {
                        return CreateDecryptor (Key, IV);
                }
 
-               /// <summary>
-               /// Gets an Decryptor transform object to work with a CryptoStream
-               /// </summary>
                public abstract ICryptoTransform CreateDecryptor (byte[] rgbKey, byte[] rgbIV);
 
-               /// <summary>
-               /// Gets an Encryptor transform object to work with a CryptoStream
-               /// </summary>
                public virtual ICryptoTransform CreateEncryptor() 
                {
                        return CreateEncryptor (Key, IV);
                }
 
-               /// <summary>
-               /// Gets an Encryptor transform object to work with a CryptoStream
-               /// </summary>
                public abstract ICryptoTransform CreateEncryptor (byte[] rgbKey, byte[] rgbIV);
 
-               /// <summary>
-               /// used to generate an inital vector if none is specified
-               /// </summary>
                public abstract void GenerateIV ();
 
-               /// </summary>
-               /// used to generate a random key if none is specified
-               /// </summary>
                public abstract void GenerateKey ();
 
-               /// <summary>
-               /// Checks wether the given keyLength is valid for the current algorithm
-               /// </summary>
-               /// <param name="bitLength">the given keyLength</param>
                public bool ValidKeySize (int bitLength) 
                {
                        return KeySizes.IsLegalKeySize (LegalKeySizesValue, bitLength);
                }
                
-               /// <summary>
-               /// Creates the default implementation of the default symmetric algorithm (Rijndael).
-               /// </summary>
                // LAMESPEC: Default is Rijndael - not TripleDES
                public static SymmetricAlgorithm Create () 
                {
                        return Create ("System.Security.Cryptography.SymmetricAlgorithm");
                }
 
-               /// <summary>
-               /// Creates a specific implementation of the given symmetric algorithm.
-               /// </summary>
-               /// <param name="algName">Specifies which derived class to create</param>
                public static SymmetricAlgorithm Create (string algName) 
                {
                        return (SymmetricAlgorithm) CryptoConfig.CreateFromName (algName);