2006-02-03 Zoltan Varga <vargaz@gmail.com>
[mono.git] / mcs / class / corlib / System.Security.Cryptography / CryptoStream.cs
old mode 100755 (executable)
new mode 100644 (file)
index 0f4df3c..28511ee
 //
 // System.Security.Cryptography CryptoStream.cs
 //
-// Author:
-//   Thomas Neidhart (tome@sbox.tugraz.at)
-//   Sebastien Pouliot (spouliot@motus.com)
+// Authors:
+//     Thomas Neidhart (tome@sbox.tugraz.at)
+//     Sebastien Pouliot (sebastien@ximian.com)
 //
-// Portions (C) 2002 Motus Technologies Inc. (http://www.motus.com)
+// Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
+// Copyright (C) 2004-2005 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;
-       
-       public CryptoStream(Stream stream, ICryptoTransform transform, CryptoStreamMode mode) 
-       {
-               _stream = stream;
-               _transform = transform;
-               _mode = mode;
-       }
+#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; }
+               }
 
-       [MonoTODO("Limited support for HMACSHA1")]
-       public override void Close () 
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ();
-               // TODO: limited implemention for HMACSHA1
-               byte[] buffer = new byte [0];
-               _transform.TransformFinalBlock (buffer, 0, 0);
-               if (_stream != null)
-                       _stream.Close();
-       }
+               public override bool CanWrite {
+                       get { return (_mode == CryptoStreamMode.Write); }
+               }
+               
+               public override long Length {
+                       get { throw new NotSupportedException ("Length"); }
+               }
 
-       [MonoTODO]
-       public override int Read (byte[] buffer, int offset, int count)
-       {
-               if (_mode != CryptoStreamMode.Read)
-                       throw new NotSupportedException ();
-               if ((offset < 0) || (count < 0))
-                       throw new ArgumentOutOfRangeException ();
-               if (offset + count > buffer.Length)
-                       throw new ArgumentException ();
-               // TODO: implement
-               return 0;
-       }
+               public override long Position {
+                       get { throw new NotSupportedException ("Position"); }
+                       set { throw new NotSupportedException ("Position"); }
+               }
 
-       [MonoTODO("Limited support for HMACSHA1")]
-       public override void Write (byte[] buffer, int offset, int count)
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ();
-               if ((offset < 0) || (count < 0))
-                       throw new ArgumentOutOfRangeException ();
-               if (offset + count > buffer.Length)
-                       throw new ArgumentException ();
-               // TODO: limited implemention for HMACSHA1
-               byte[] output = new byte [count];
-               _transform.TransformBlock (buffer, offset, count, output, 0);
-       }
+               public void Clear () 
+               {
+                       Dispose (true);
+                       GC.SuppressFinalize (this); // not called in Stream.Dispose
+               }
 
-       [MonoTODO]
-       public override void Flush ()
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ("cannot flush a non-writeable CryptoStream");
-               // TODO: implement
-       }
+               // 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 ();
 
-       [MonoTODO]
-       public void FlushFinalBlock ()
-       {
-               if (_mode != CryptoStreamMode.Write)
-                       throw new NotSupportedException ("cannot flush a non-writeable CryptoStream");
-               // TODO: implement
-       }
+                       if (_stream != null)
+                               _stream.Close ();
+               }
 
-       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");
-       }
+               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
+                       // instead we get a ArgumentNullException (probably from an internal method)
+                       if (_workingBlock == null) {
+                               throw new ArgumentNullException (
+                                       Locale.GetText ("object _disposed"));
+                       }
+
+                       int result = 0;
+                       if ((count == 0) || ((_transformedPos == _transformedCount) && (_endOfStream)))
+                               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;
 
-       protected virtual void Dispose (bool disposing) 
-       {
+                                       // load a new block
+                                       _workingCount = _stream.Read (_workingBlock, 0, _workingBlock.Length);
+                                       _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;
+                                               }
+                                               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);
+                                       }
+
+                                       length += transformed;
+                                       _transformedCount += transformed;
+                               }
+                               // compaction
+                               if (_transformedPos > _transform.InputBlockSize) {
+                                       Buffer.BlockCopy (_transformedBlock, _transformedPos, _transformedBlock, 0, length);
+                                       _transformedCount -= _transformedPos;
+                                       _transformedPos = 0;
+                               }
+
+                               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 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"));
+                       }
+
+                       // 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;
+                       }
+
+                       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;
+                               }
+
+                               if (_transform.CanTransformMultipleBlocks) {
+                                       // transform all except the last block (which may be the last block
+                                       // of the stream and require TransformFinalBlock)
+                                       int numBlock = ((_partialCount + count) / _transform.InputBlockSize);
+                                       int multiSize = (numBlock * _transform.InputBlockSize);
+                                       if (numBlock > 0) {
+                                               byte[] multiBlocks = new byte [multiSize];
+                                               int len = _transform.TransformBlock (buffer, offset, multiSize, multiBlocks, 0);
+                                               _stream.Write (multiBlocks, 0, len); 
+                                               // copy last block into _currentBlock
+                                               _partialCount = count - multiSize;
+                                               Buffer.BlockCopy (buffer, offset + multiSize, _workingBlock, 0, _partialCount);
+                                       }
+                                       else {
+                                               Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, count);
+                                               _partialCount += count;
+                                       }
+                                       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.Flush ();
+               }
+
+               public void FlushFinalBlock ()
+               {
+                       if (_flushedFinalBlock) {
+                               throw new NotSupportedException (
+                                       Locale.GetText ("This method cannot be called twice."));
+                       }
+                       if (_mode != CryptoStreamMode.Write) {
+                               throw new NotSupportedException (
+                                       Locale.GetText ("cannot flush a non-writeable CryptoStream"));
+                       }
+
+                       _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
+}