Fix CryptoStream.Dispose
[mono.git] / mcs / class / corlib / System.Security.Cryptography / CryptoStream.cs
1 //
2 // System.Security.Cryptography CryptoStream.cs
3 //
4 // Authors:
5 //      Thomas Neidhart (tome@sbox.tugraz.at)
6 //      Sebastien Pouliot (sebastien@ximian.com)
7 //
8 // Portions (C) 2002, 2003 Motus Technologies Inc. (http://www.motus.com)
9 // Copyright (C) 2004-2005, 2007 Novell, Inc (http://www.novell.com)
10 //
11 // Permission is hereby granted, free of charge, to any person obtaining
12 // a copy of this software and associated documentation files (the
13 // "Software"), to deal in the Software without restriction, including
14 // without limitation the rights to use, copy, modify, merge, publish,
15 // distribute, sublicense, and/or sell copies of the Software, and to
16 // permit persons to whom the Software is furnished to do so, subject to
17 // the following conditions:
18 // 
19 // The above copyright notice and this permission notice shall be
20 // included in all copies or substantial portions of the Software.
21 // 
22 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
23 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
24 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
25 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
26 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
27 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
28 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
29 //
30
31 using System.Globalization;
32 using System.IO;
33 using System.Runtime.InteropServices;
34
35 namespace System.Security.Cryptography {
36
37         [ComVisible (true)]
38         public class CryptoStream : Stream {
39                 private Stream _stream;
40                 private ICryptoTransform _transform;
41                 private CryptoStreamMode _mode;
42                 private byte[] _currentBlock;
43                 private bool _disposed;
44                 private bool _flushedFinalBlock;
45                 private int _partialCount;
46                 private bool _endOfStream;
47
48                 private byte[] _waitingBlock;
49                 private int _waitingCount;
50
51                 private byte[] _transformedBlock;
52                 private int _transformedPos;
53                 private int _transformedCount;
54
55                 private byte[] _workingBlock;
56                 private int _workingCount;
57                 
58                 public CryptoStream (Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
59                 {
60                         if ((mode == CryptoStreamMode.Read) && (!stream.CanRead)) {
61                                 throw new ArgumentException (
62                                         Locale.GetText ("Can't read on stream"));
63                         }
64                         if ((mode == CryptoStreamMode.Write) && (!stream.CanWrite)) {
65                                 throw new ArgumentException (
66                                         Locale.GetText ("Can't write on stream"));
67                         }
68                         _stream = stream;
69                         _transform = transform;
70                         _mode = mode;
71                         _disposed = false;
72                         if (transform != null) {
73                                 if (mode == CryptoStreamMode.Read) {
74                                         _currentBlock = new byte [transform.InputBlockSize];
75                                         _workingBlock = new byte [transform.InputBlockSize];
76                                 }
77                                 else if (mode == CryptoStreamMode.Write) {
78                                         _currentBlock = new byte [transform.OutputBlockSize];
79                                         _workingBlock = new byte [transform.OutputBlockSize];
80                                 }
81                         }
82                 }
83
84                 ~CryptoStream () 
85                 {
86                         Dispose (false);
87                 }
88                 
89                 public override bool CanRead {
90                         get { return (_mode == CryptoStreamMode.Read); }
91                 }
92
93                 public override bool CanSeek {
94                         get { return false; }
95                 }
96
97                 public override bool CanWrite {
98                         get { return (_mode == CryptoStreamMode.Write); }
99                 }
100                 
101                 public override long Length {
102                         get { throw new NotSupportedException ("Length"); }
103                 }
104
105                 public override long Position {
106                         get { throw new NotSupportedException ("Position"); }
107                         set { throw new NotSupportedException ("Position"); }
108                 }
109
110                 public void Clear () 
111                 {
112                         Close ();
113                 }
114
115                 // LAMESPEC: A CryptoStream can be close in read mode
116                 public override void Close () 
117                 {
118                         base.Close ();
119                 }
120
121                 public override int Read ([In,Out] byte[] buffer, int offset, int count)
122                 {
123                         if (_mode != CryptoStreamMode.Read) {
124                                 throw new NotSupportedException (
125                                         Locale.GetText ("not in Read mode"));
126                         }
127                         if (offset < 0) {
128                                 throw new ArgumentOutOfRangeException ("offset", 
129                                         Locale.GetText ("negative"));
130                         }
131                         if (count < 0) {
132                                 throw new ArgumentOutOfRangeException ("count",
133                                         Locale.GetText ("negative"));
134                         }
135                         // yes - buffer.Length will throw a NullReferenceException if buffer is null
136                         // but by doing so we match MS implementation
137                         // re-ordered to avoid integer overflow
138                         if (offset > buffer.Length - count) {
139                                 throw new ArgumentException ("(offset+count)", 
140                                         Locale.GetText ("buffer overflow"));
141                         }
142                         // for some strange reason ObjectDisposedException isn't throw
143                         if (_workingBlock == null) {
144                                 return 0;
145                         }
146
147                         int result = 0;
148                         if ((count == 0) || ((_transformedPos == _transformedCount) && (_endOfStream)))
149                                 return result;
150
151                         if (_waitingBlock == null) {
152                                 _transformedBlock = new byte [_transform.OutputBlockSize << 2];
153                                 _transformedPos = 0;
154                                 _transformedCount = 0;
155                                 _waitingBlock = new byte [_transform.InputBlockSize];
156                                 _waitingCount = _stream.Read (_waitingBlock, 0, _waitingBlock.Length);
157                         }
158                         
159                         while (count > 0) {
160                                 // transformed but not yet returned
161                                 int length = (_transformedCount - _transformedPos);
162
163                                 // need more data - at least one full block must be available if we haven't reach the end of the stream
164                                 if (length < _transform.InputBlockSize) {
165                                         int transformed = 0;
166
167                                         // load a new block
168                                         _workingCount = _stream.Read (_workingBlock, 0, _transform.InputBlockSize);
169                                         _endOfStream = (_workingCount < _transform.InputBlockSize);
170
171                                         if (!_endOfStream) {
172                                                 // transform the waiting block
173                                                 transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
174
175                                                 // transfer temporary to waiting
176                                                 Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
177                                                 _waitingCount = _workingCount;
178                                         }
179                                         else {
180                                                 if (_workingCount > 0) {
181                                                         // transform the waiting block
182                                                         transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
183
184                                                         // transfer temporary to waiting
185                                                         Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
186                                                         _waitingCount = _workingCount;
187
188                                                         length += transformed;
189                                                         _transformedCount += transformed;
190                                                 }
191                                                 if (!_flushedFinalBlock) {
192                                                         byte[] input = _transform.TransformFinalBlock (_waitingBlock, 0, _waitingCount);
193                                                         transformed = input.Length;
194                                                         Buffer.BlockCopy (input, 0, _transformedBlock, _transformedCount, input.Length);
195                                                         // zeroize this last block
196                                                         Array.Clear (input, 0, input.Length);
197                                                         _flushedFinalBlock = true;
198                                                 }
199                                         }
200
201                                         length += transformed;
202                                         _transformedCount += transformed;
203                                 }
204                                 // compaction
205                                 if (_transformedPos > _transform.OutputBlockSize) {
206                                         Buffer.BlockCopy (_transformedBlock, _transformedPos, _transformedBlock, 0, length);
207                                         _transformedCount -= _transformedPos;
208                                         _transformedPos = 0;
209                                 }
210
211                                 length = ((count < length) ? count : length);
212                                 if (length > 0) {
213                                         Buffer.BlockCopy (_transformedBlock, _transformedPos, buffer, offset, length);
214                                         _transformedPos += length;
215
216                                         result += length;
217                                         offset += length;
218                                         count -= length;
219                                 }
220
221                                 // there may not be enough data in the stream for a 
222                                 // complete block
223                                 if (((length != _transform.InputBlockSize) && (_waitingCount != _transform.InputBlockSize)) || (_endOfStream)) {
224                                         count = 0;      // no more data can be read
225                                 }
226                         }
227                         
228                         return result;
229                 }
230
231                 public override void Write (byte[] buffer, int offset, int count)
232                 {
233                         if (_mode != CryptoStreamMode.Write) {
234                                 throw new NotSupportedException (
235                                         Locale.GetText ("not in Write mode"));
236                         }
237                         if (offset < 0) { 
238                                 throw new ArgumentOutOfRangeException ("offset", 
239                                         Locale.GetText ("negative"));
240                         }
241                         if (count < 0) {
242                                 throw new ArgumentOutOfRangeException ("count", 
243                                         Locale.GetText ("negative"));
244                         }
245                         // re-ordered to avoid integer overflow
246                         if (offset > buffer.Length - count) {
247                                 throw new ArgumentException ("(offset+count)", 
248                                         Locale.GetText ("buffer overflow"));
249                         }
250
251                         if (_stream == null)
252                                 throw new ArgumentNullException ("inner stream was diposed");
253
254                         int buffer_length = count;
255
256                         // partial block (in progress)
257                         if ((_partialCount > 0) && (_partialCount != _transform.InputBlockSize)) {
258                                 int remainder = _transform.InputBlockSize - _partialCount;
259                                 remainder = ((count < remainder) ? count : remainder);
260                                 Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, remainder);
261                                 _partialCount += remainder;
262                                 offset += remainder;
263                                 count -= remainder;
264                         }
265
266                         int bufferPos = offset;
267                         while (count > 0) {
268                                 if (_partialCount == _transform.InputBlockSize) {
269                                         // use partial block to avoid (re)allocation
270                                         int len = _transform.TransformBlock (_workingBlock, 0, _partialCount, _currentBlock, 0);
271                                         _stream.Write (_currentBlock, 0, len);
272                                         // reset
273                                         _partialCount = 0;
274                                 }
275
276                                 if (_transform.CanTransformMultipleBlocks) {
277                                         // get the biggest multiple of InputBlockSize in count (without mul or div)
278                                         int size = (count & ~(_transform.InputBlockSize - 1));
279                                         int rem = (count & (_transform.InputBlockSize - 1));
280                                         // avoid reallocating memory at each call (reuse same buffer whenever possible)
281                                         int sizeWorkingBlock = (1 + size / _transform.InputBlockSize) * _transform.OutputBlockSize;
282                                         if (_workingBlock.Length < sizeWorkingBlock) {
283                                                 Array.Clear (_workingBlock, 0, _workingBlock.Length);
284                                                 _workingBlock = new byte [sizeWorkingBlock];
285                                         }
286
287                                         if (size > 0) {
288                                                 int len = _transform.TransformBlock (buffer, offset, size, _workingBlock, 0);
289                                                 _stream.Write (_workingBlock, 0, len);
290                                         }
291
292                                         if (rem > 0)
293                                                 Buffer.BlockCopy (buffer, buffer_length - rem, _workingBlock, 0, rem);
294                                         _partialCount = rem;
295                                         count = 0; // the last block, if any, is in _workingBlock
296                                 } else {
297                                         int len = Math.Min (_transform.InputBlockSize - _partialCount, count);
298                                         Buffer.BlockCopy (buffer, bufferPos, _workingBlock, _partialCount, len);
299                                         bufferPos += len;
300                                         _partialCount += len;
301                                         count -= len;
302                                         // here block may be full, but we wont TransformBlock it until next iteration
303                                         // so that the last block will be called in FlushFinalBlock using TransformFinalBlock
304                                 }
305                         }
306                 }
307
308                 public override void Flush ()
309                 {
310                         if (_stream != null)
311                                 _stream.Flush ();
312                 }
313
314                 public void FlushFinalBlock ()
315                 {
316                         if (_flushedFinalBlock)
317                                 throw new NotSupportedException (Locale.GetText ("This method cannot be called twice."));
318                         if (_disposed)
319                                 throw new NotSupportedException (Locale.GetText ("CryptoStream was disposed."));
320                         if (_mode != CryptoStreamMode.Write)
321                                 return;
322                         _flushedFinalBlock = true;
323                         byte[] finalBuffer = _transform.TransformFinalBlock (_workingBlock, 0, _partialCount);
324                         if (_stream != null) {
325                                 _stream.Write (finalBuffer, 0, finalBuffer.Length);
326                                 if (_stream is CryptoStream) {
327                                         // for cascading crypto streams
328                                         (_stream as CryptoStream).FlushFinalBlock ();
329                                 }
330                                 _stream.Flush ();
331                         }
332                         // zeroize
333                         Array.Clear (finalBuffer, 0, finalBuffer.Length);
334                 }
335
336                 public override long Seek (long offset, SeekOrigin origin)
337                 {
338                         throw new NotSupportedException ("Seek");
339                 }
340                 
341                 // LAMESPEC: Exception NotSupportedException not documented
342                 public override void SetLength (long value)
343                 {
344                         throw new NotSupportedException ("SetLength");
345                 }
346
347                 protected override void Dispose (bool disposing) 
348                 {
349                         if (!_disposed) {
350                                 if (disposing) {
351                                         // only flush in write mode (bugzilla 46143)
352                                         if (!_flushedFinalBlock) {
353                                                 if (_mode == CryptoStreamMode.Write) {
354                                                         FlushFinalBlock ();
355                                                 } else {
356                                                         // See bug #644648
357                                                         _transform.TransformFinalBlock (new byte [0], 0, 0);
358                                                 }
359                                                 _flushedFinalBlock = true;
360                                         }
361
362                                         if (_stream != null)
363                                                 _stream.Close ();
364                                 }
365                                 _disposed = true;
366                                 // always cleared for security reason
367                                 if (_workingBlock != null)
368                                         Array.Clear (_workingBlock, 0, _workingBlock.Length);
369                                 if (_currentBlock != null)
370                                         Array.Clear (_currentBlock, 0, _currentBlock.Length);
371                                 if (disposing) {
372                                         _stream = null;
373                                         _workingBlock = null;
374                                         _currentBlock = null;
375                                 }
376                         }
377                 }
378         }
379 }