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