bring Mono Security to monotouch
[mono.git] / mcs / class / corlib / System.Security.Cryptography / CryptoStream.cs
old mode 100755 (executable)
new mode 100644 (file)
index 936f1e7..9190921
 //
 // 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-2005, 2007 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.IO;
+using System.Runtime.InteropServices;
 
 namespace System.Security.Cryptography {
 
-public class CryptoStream : Stream {
-       private Stream _stream;
-       private ICryptoTransform _transform;
-       private CryptoStreamMode _mode;
-       private byte[] workingBlock;
-       private byte[] partialBlock;
-       private int workPos;
-       private bool disposed;
-       private bool _flushedFinalBlock;
-       private int blockSize;
-       private int partialCount;
-       
-       public CryptoStream (Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
-       {
-               if ((mode == CryptoStreamMode.Read) && (!stream.CanRead))
-                       throw new ArgumentException ("Can't read on stream");
-               if ((mode == CryptoStreamMode.Write) && (!stream.CanWrite))
-                       throw new ArgumentException ("Can't write on stream");
-               _stream = stream;
-               _transform = transform;
-               _mode = mode;
-               disposed = false;
-               if (transform != null) {
-                       if (mode == CryptoStreamMode.Read)
-                               blockSize = transform.InputBlockSize;
-                       else if (mode == CryptoStreamMode.Write)
-                               blockSize = transform.OutputBlockSize;
-                       workingBlock = new byte [blockSize];
-                       partialBlock = new byte [blockSize];
-               }
-               workPos = 0;
-       }
+#if NET_2_0
+       [ComVisible (true)]
+#endif
+       public class CryptoStream : Stream {
+               private Stream _stream;
+               private ICryptoTransform _transform;
+               private CryptoStreamMode _mode;
+               private byte[] _currentBlock;
+               private bool _disposed;
+               private bool _flushedFinalBlock;
+               private int _partialCount;
+               private bool _endOfStream;
 
-       ~CryptoStream () 
-       {
-               Dispose (false);
-       }
-       
-       public override bool CanRead {
-               get { return (_mode == CryptoStreamMode.Read); }
-       }
+               private byte[] _waitingBlock;
+               private int _waitingCount;
 
-       public override bool CanSeek {
-               get { return false; }
-       }
+               private byte[] _transformedBlock;
+               private int _transformedPos;
+               private int _transformedCount;
 
-       public override bool CanWrite {
-               get { return (_mode == CryptoStreamMode.Write); }
-       }
-       
-       public override long Length {
-               get {
-                       throw new NotSupportedException ("Length property not supported by CryptoStream");
+               private byte[] _workingBlock;
+               private int _workingCount;
+               
+               public CryptoStream (Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
+               {
+                       if ((mode == CryptoStreamMode.Read) && (!stream.CanRead)) {
+                               throw new ArgumentException (
+                                       Locale.GetText ("Can't read on stream"));
+                       }
+                       if ((mode == CryptoStreamMode.Write) && (!stream.CanWrite)) {
+                               throw new ArgumentException (
+                                       Locale.GetText ("Can't write on stream"));
+                       }
+                       _stream = stream;
+                       _transform = transform;
+                       _mode = mode;
+                       _disposed = false;
+                       if (transform != null) {
+                               _workingBlock = new byte [transform.InputBlockSize];
+                               if (mode == CryptoStreamMode.Read)
+                                       _currentBlock = new byte [transform.InputBlockSize];
+                               else if (mode == CryptoStreamMode.Write)
+                                       _currentBlock = new byte [transform.OutputBlockSize];
+                       }
                }
-       }
 
-       public override long Position {
-               get {
-                       throw new NotSupportedException ("Position property not supported by CryptoStream");
+               ~CryptoStream () 
+               {
+                       Dispose (false);
                }
-               set {
-                       throw new NotSupportedException ("Position property not supported by CryptoStream");
+               
+               public override bool CanRead {
+                       get { return (_mode == CryptoStreamMode.Read); }
                }
-       }
 
-       public void Clear () 
-       {
-               Dispose (true);
-       }
+               public override bool CanSeek {
+                       get { return false; }
+               }
 
-       public override void Close () 
-       {
-               // LAMESPEC: A CryptoStream can be close in read mode
-               //if (_mode != CryptoStreamMode.Write)
-               //      throw new NotSupportedException ();
+               public override bool CanWrite {
+                       get { return (_mode == CryptoStreamMode.Write); }
+               }
+               
+               public override long Length {
+                       get { throw new NotSupportedException ("Length"); }
+               }
 
-               // only flush in write mode (bugzilla 46143)
-               if ((!_flushedFinalBlock) && (_mode == CryptoStreamMode.Write))
-                       FlushFinalBlock ();
+               public override long Position {
+                       get { throw new NotSupportedException ("Position"); }
+                       set { throw new NotSupportedException ("Position"); }
+               }
 
-               if (_stream != null)
-                       _stream.Close ();
-       }
+               public void Clear () 
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this); // not called in Stream.Dispose
+               }
 
-       private int ReadBlock (byte[] buffer, int offset, byte[] workspace) 
-       {
-               _stream.Read (workspace, 0, workspace.Length);
-               if (_stream.Position == _stream.Length) {
-                       // last block
-                       byte[] input = _transform.TransformFinalBlock (workspace, 0, workspace.Length);
-                       Array.Copy (input, 0, buffer, offset, input.Length);
-                       // zeroize this last block
-                       Array.Clear (input, 0, input.Length);
-                       // return past blocks + last block size
-                       return input.Length;
-               } 
-               return _transform.TransformBlock (workspace, 0, workspace.Length, buffer, offset);
-       }
+               // LAMESPEC: A CryptoStream can be close in read mode
+               public override void Close () 
+               {
+                       // only flush in write mode (bugzilla 46143)
+                       if ((!_flushedFinalBlock) && (_mode == CryptoStreamMode.Write))
+                               FlushFinalBlock ();
 
-       private int ReadBlocks (byte[] buffer, int offset, int numBlock) 
-       {
-               int result = 0;
-               // if supported do a single transform, if not iterate for each block
-               // but only if numBlock > 1 as we don't want to re-allocate memory for 1 block
-               if ((numBlock > 1) && (_transform.CanTransformMultipleBlocks)) {
-                       int size = numBlock * blockSize;
-                       byte[] multiBlocks = new byte [size];
-                       result = ReadBlock (buffer, offset, multiBlocks);
-                       // zeroize data
-                       Array.Clear (multiBlocks, 0, size);
+                       if (_stream != null)
+                               _stream.Close ();
                }
-               else {
-                       for (int i=0; i < numBlock; i++) {
-                               int written = ReadBlock (buffer, offset, workingBlock);
-                               result += written;
-                               offset += written;
+
+               public override int Read ([In,Out] byte[] buffer, int offset, int count)
+               {
+                       if (_mode != CryptoStreamMode.Read) {
+                               throw new NotSupportedException (
+                                       Locale.GetText ("not in Read mode"));
+                       }
+                       if (offset < 0) {
+                               throw new ArgumentOutOfRangeException ("offset", 
+                                       Locale.GetText ("negative"));
+                       }
+                       if (count < 0) {
+                               throw new ArgumentOutOfRangeException ("count",
+                                       Locale.GetText ("negative"));
+                       }
+                       // yes - buffer.Length will throw a NullReferenceException if buffer is null
+                       // but by doing so we match MS implementation
+                       // re-ordered to avoid integer overflow
+                       if (offset > buffer.Length - count) {
+                               throw new ArgumentException ("(offset+count)", 
+                                       Locale.GetText ("buffer overflow"));
+                       }
+                       // for some strange reason ObjectDisposedException isn't throw
+                       if (_workingBlock == null) {
+#if NET_2_0
+                               return 0;
+#else
+                               // instead we get a ArgumentNullException (probably from an internal method)
+                               throw new ArgumentNullException (Locale.GetText ("CryptoStream was disposed."));
+#endif
                        }
-               }
-               return result;
-       }
 
-       public override int Read (byte[] buffer, int offset, int count)
-       {
-               if (_mode != CryptoStreamMode.Read)
-                       throw new NotSupportedException ("not in Read mode");
-               if (offset < 0) 
-                       throw new ArgumentOutOfRangeException ("offset", "negative");
-               if (count < 0)
-                       throw new ArgumentOutOfRangeException ("count", "negative");
-               if (offset + count > buffer.Length)
-                       throw new ArgumentException ("(offset+count)", "buffer overflow");
-
-               // reached end of stream ?
-               if (_stream.Position == _stream.Length)
-                       return 0;
-
-               int result = 0;
-               int bufferPos = offset;
-
-               // is there a previous partial block to complete ?
-               if (partialCount > 0) {
-                       // if yes, the copy this (already decrypted) block
-                       int remainder = (blockSize - partialCount);
-                       Array.Copy (partialBlock, partialCount, buffer, bufferPos, remainder);
-                       // zeroize the partial block
-                       Array.Clear (partialBlock, 0, blockSize);
-                       bufferPos += remainder;
-                       count -= remainder;
-               }
-               
-               // read all complete blocks
-               int written = ReadBlocks (buffer, bufferPos, (count / blockSize));
-               bufferPos += written;
-               result += written;
-               
-               // is there a partial block ?
-               partialCount = (count % blockSize);
-               if (partialCount > 0) {
-                       // if yes we must read the process the next entire block
-                       ReadBlocks (partialBlock, 0, 1);
-                       result += partialCount;
-                       // return a copy of the first part (as requested)
-                       Array.Copy (partialBlock, 0, buffer, bufferPos, partialCount);
-                       // and keep the partial block for "possible" next read (no zeroize)
-               }
+                       int result = 0;
+                       if ((count == 0) || ((_transformedPos == _transformedCount) && (_endOfStream)))
+                               return result;
 
-               return result;
-       }
+                       if (_waitingBlock == null) {
+                               _transformedBlock = new byte [_transform.OutputBlockSize << 2];
+                               _transformedPos = 0;
+                               _transformedCount = 0;
+                               _waitingBlock = new byte [_transform.InputBlockSize];
+                               _waitingCount = _stream.Read (_waitingBlock, 0, _waitingBlock.Length);
+                       }
+                       
+                       while (count > 0) {
+                               // transformed but not yet returned
+                               int length = (_transformedCount - _transformedPos);
+
+                               // need more data - at least one full block must be available if we haven't reach the end of the stream
+                               if (length < _transform.InputBlockSize) {
+                                       int transformed = 0;
+
+                                       // load a new block
+                                       _workingCount = _stream.Read (_workingBlock, 0, _transform.InputBlockSize);
+                                       _endOfStream = (_workingCount < _transform.InputBlockSize);
+
+                                       if (!_endOfStream) {
+                                               // transform the waiting block
+                                               transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
+
+                                               // transfer temporary to waiting
+                                               Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
+                                               _waitingCount = _workingCount;
+                                       }
+                                       else {
+                                               if (_workingCount > 0) {
+                                                       // transform the waiting block
+                                                       transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
+
+                                                       // transfer temporary to waiting
+                                                       Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
+                                                       _waitingCount = _workingCount;
+
+                                                       length += transformed;
+                                                       _transformedCount += transformed;
+                                               }
+                                               if (!_flushedFinalBlock) {
+                                                       byte[] input = _transform.TransformFinalBlock (_waitingBlock, 0, _waitingCount);
+                                                       transformed = input.Length;
+                                                       Buffer.BlockCopy (input, 0, _transformedBlock, _transformedCount, input.Length);
+                                                       // zeroize this last block
+                                                       Array.Clear (input, 0, input.Length);
+                                                       _flushedFinalBlock = true;
+                                               }
+                                       }
+
+                                       length += transformed;
+                                       _transformedCount += transformed;
+                               }
+                               // compaction
+                               if (_transformedPos > _transform.InputBlockSize) {
+                                       Buffer.BlockCopy (_transformedBlock, _transformedPos, _transformedBlock, 0, length);
+                                       _transformedCount -= _transformedPos;
+                                       _transformedPos = 0;
+                               }
 
-       public override void Write (byte[] buffer, int offset, int count)
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ("not in Write mode");
-               if (offset < 0) 
-                       throw new ArgumentOutOfRangeException ("offset", "negative");
-               if (count < 0)
-                       throw new ArgumentOutOfRangeException ("count", "negative");
-               if (offset + count > buffer.Length)
-                       throw new ArgumentException ("(offset+count)", "buffer overflow");
-
-               int bufferPos = offset;
-               while (count > 0) {
-                       int len = Math.Min (workingBlock.Length - workPos, count);
-                       Array.Copy (buffer, bufferPos, workingBlock, workPos, len);
-                       bufferPos += len;
-                       workPos += len;
-                       count -= len;
-                       if (workPos == workingBlock.Length) {
-                               workPos = 0;
-                               byte[] output = new byte[_transform.OutputBlockSize];
-                               _transform.TransformBlock (workingBlock, 0, workingBlock.Length, output, 0);
-                               _stream.Write (output, 0, output.Length);
+                               length = ((count < length) ? count : length);
+                               if (length > 0) {
+                                       Buffer.BlockCopy (_transformedBlock, _transformedPos, buffer, offset, length);
+                                       _transformedPos += length;
+
+                                       result += length;
+                                       offset += length;
+                                       count -= length;
+                               }
+
+                               // there may not be enough data in the stream for a 
+                               // complete block
+                               if (((length != _transform.InputBlockSize) && (_waitingCount != _transform.InputBlockSize)) || (_endOfStream)) {
+                                       count = 0;      // no more data can be read
+                               }
                        }
+                       
+                       return result;
                }
-       }
 
-       public override void Flush ()
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ("cannot flush a non-writeable CryptoStream");
+               public override void Write (byte[] buffer, int offset, int count)
+               {
+                       if (_mode != CryptoStreamMode.Write) {
+                               throw new NotSupportedException (
+                                       Locale.GetText ("not in Write mode"));
+                       }
+                       if (offset < 0) { 
+                               throw new ArgumentOutOfRangeException ("offset", 
+                                       Locale.GetText ("negative"));
+                       }
+                       if (count < 0) {
+                               throw new ArgumentOutOfRangeException ("count", 
+                                       Locale.GetText ("negative"));
+                       }
+                       // re-ordered to avoid integer overflow
+                       if (offset > buffer.Length - count) {
+                               throw new ArgumentException ("(offset+count)", 
+                                       Locale.GetText ("buffer overflow"));
+                       }
 
-               if (_stream != null)
-                       _stream.Flush ();
-       }
+                       if (_stream == null)
+                               throw new ArgumentNullException ("inner stream was diposed");
 
-       public void FlushFinalBlock ()
-       {
-               if (_flushedFinalBlock)
-                       throw new NotSupportedException ("This method cannot be called twice.");
+                       int buffer_length = count;
 
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ("cannot flush a non-writeable CryptoStream");
+                       // partial block (in progress)
+                       if ((_partialCount > 0) && (_partialCount != _transform.InputBlockSize)) {
+                               int remainder = _transform.InputBlockSize - _partialCount;
+                               remainder = ((count < remainder) ? count : remainder);
+                               Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, remainder);
+                               _partialCount += remainder;
+                               offset += remainder;
+                               count -= remainder;
+                       }
 
-               _flushedFinalBlock = true;
-               byte[] finalBuffer = _transform.TransformFinalBlock (workingBlock, 0, workPos);
-               if (_stream != null) {
-                       _stream.Write (finalBuffer, 0, finalBuffer.Length);
-                       _stream.Flush ();
-               }
-               // zeroize
-               Array.Clear (finalBuffer, 0, finalBuffer.Length);
-       }
+                       int bufferPos = offset;
+                       while (count > 0) {
+                               if (_partialCount == _transform.InputBlockSize) {
+                                       // use partial block to avoid (re)allocation
+                                       int len = _transform.TransformBlock (_workingBlock, 0, _partialCount, _currentBlock, 0);
+                                       _stream.Write (_currentBlock, 0, len);
+                                       // reset
+                                       _partialCount = 0;
+                               }
 
-       public override long Seek (long offset, SeekOrigin origin)
-       {
-               throw new NotSupportedException ("cannot Seek a CryptoStream");
-       }
-       
-       // LAMESPEC: Exception NotSupportedException not documented
-       public override void SetLength (long value)
-       {
-               throw new NotSupportedException ("cannot SetLength a CryptoStream");
-       }
+                               if (_transform.CanTransformMultipleBlocks) {
+                                       // get the biggest multiple of InputBlockSize in count (without mul or div)
+                                       int size = (count & ~(_transform.OutputBlockSize - 1));
+                                       int rem = (count & (_transform.OutputBlockSize - 1));
+                                       // avoid reallocating memory at each call (reuse same buffer whenever possible)
+                                       if (_workingBlock.Length < size) {
+                                               Array.Clear (_workingBlock, 0, _workingBlock.Length);
+                                               _workingBlock = new byte [size];
+                                       }
+
+                                       if (size > 0) {
+                                               int len = _transform.TransformBlock (buffer, offset, size, _workingBlock, 0);
+                                               _stream.Write (_workingBlock, 0, len);
+                                       }
 
-       protected virtual void Dispose (bool disposing) 
-       {
-               if (!disposed) {
+                                       if (rem > 0)
+                                               Buffer.BlockCopy (buffer, buffer_length - rem, _workingBlock, 0, rem);
+                                       _partialCount = rem;
+                                       count = 0; // the last block, if any, is in _workingBlock
+                               } else {
+                                       int len = Math.Min (_transform.InputBlockSize - _partialCount, count);
+                                       Buffer.BlockCopy (buffer, bufferPos, _workingBlock, _partialCount, len);
+                                       bufferPos += len;
+                                       _partialCount += len;
+                                       count -= len;
+                                       // here block may be full, but we wont TransformBlock it until next iteration
+                                       // so that the last block will be called in FlushFinalBlock using TransformFinalBlock
+                               }
+                       }
+               }
+
+               public override void Flush ()
+               {
                        if (_stream != null)
-                               _stream.Close ();
-                       // always cleared for security reason
-                       Array.Clear (workingBlock, 0, workingBlock.Length);
-                       Array.Clear (partialBlock, 0, partialBlock.Length);
-                       if (disposing) {
-                               _stream = null;
-                               workingBlock = null;
-                               partialBlock = null;
+                               _stream.Flush ();
+               }
+
+               public void FlushFinalBlock ()
+               {
+                       if (_flushedFinalBlock)
+                               throw new NotSupportedException (Locale.GetText ("This method cannot be called twice."));
+#if NET_2_0
+                       if (_disposed)
+                               throw new NotSupportedException (Locale.GetText ("CryptoStream was disposed."));
+                       if (_mode != CryptoStreamMode.Write)
+                               return;
+#else
+                       if (_mode != CryptoStreamMode.Write)
+                               throw new NotSupportedException (Locale.GetText ("cannot flush a non-writeable CryptoStream"));
+#endif
+                       _flushedFinalBlock = true;
+                       byte[] finalBuffer = _transform.TransformFinalBlock (_workingBlock, 0, _partialCount);
+                       if (_stream != null) {
+                               _stream.Write (finalBuffer, 0, finalBuffer.Length);
+                               if (_stream is CryptoStream) {
+                                       // for cascading crypto streams
+                                       (_stream as CryptoStream).FlushFinalBlock ();
+                               }
+                               _stream.Flush ();
+                       }
+                       // zeroize
+                       Array.Clear (finalBuffer, 0, finalBuffer.Length);
+               }
+
+               public override long Seek (long offset, SeekOrigin origin)
+               {
+                       throw new NotSupportedException ("Seek");
+               }
+               
+               // LAMESPEC: Exception NotSupportedException not documented
+               public override void SetLength (long value)
+               {
+                       throw new NotSupportedException ("SetLength");
+               }
+
+#if NET_2_0
+               protected override void Dispose (bool disposing) 
+#else
+               protected virtual void Dispose (bool disposing) 
+#endif
+               {
+                       if (!_disposed) {
+                               _disposed = true;
+                               // always cleared for security reason
+                               if (_workingBlock != null)
+                                       Array.Clear (_workingBlock, 0, _workingBlock.Length);
+                               if (_currentBlock != null)
+                                       Array.Clear (_currentBlock, 0, _currentBlock.Length);
+                               if (disposing) {
+                                       _stream = null;
+                                       _workingBlock = null;
+                                       _currentBlock = null;
+                               }
                        }
                }
        }
-       
-} // CryptoStream
-       
-} // System.Security.Cryptography
+}