2004-05-26 Sebastien Pouliot <sebastien@ximian.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 using System;
13 using System.Globalization;
14 using System.IO;
15 using System.Runtime.InteropServices;
16
17 namespace System.Security.Cryptography {
18
19         public class CryptoStream : Stream {
20                 private Stream _stream;
21                 private ICryptoTransform _transform;
22                 private CryptoStreamMode _mode;
23                 private byte[] _currentBlock;
24                 private bool _disposed;
25                 private bool _flushedFinalBlock;
26                 private int _partialCount;
27                 private bool _endOfStream;
28
29                 private byte[] _waitingBlock;
30                 private int _waitingCount;
31
32                 private byte[] _transformedBlock;
33                 private int _transformedPos;
34                 private int _transformedCount;
35
36                 private byte[] _workingBlock;
37                 private int _workingCount;
38                 
39                 public CryptoStream (Stream stream, ICryptoTransform transform, CryptoStreamMode mode)
40                 {
41                         if ((mode == CryptoStreamMode.Read) && (!stream.CanRead)) {
42                                 throw new ArgumentException (
43                                         Locale.GetText ("Can't read on stream"));
44                         }
45                         if ((mode == CryptoStreamMode.Write) && (!stream.CanWrite)) {
46                                 throw new ArgumentException (
47                                         Locale.GetText ("Can't write on stream"));
48                         }
49                         _stream = stream;
50                         _transform = transform;
51                         _mode = mode;
52                         _disposed = false;
53                         if (transform != null) {
54                                 _workingBlock = new byte [transform.InputBlockSize];
55                                 if (mode == CryptoStreamMode.Read)
56                                         _currentBlock = new byte [transform.InputBlockSize];
57                                 else if (mode == CryptoStreamMode.Write)
58                                         _currentBlock = new byte [transform.OutputBlockSize];
59                         }
60                 }
61
62                 ~CryptoStream () 
63                 {
64                         Dispose (false);
65                 }
66                 
67                 public override bool CanRead {
68                         get { return (_mode == CryptoStreamMode.Read); }
69                 }
70
71                 public override bool CanSeek {
72                         get { return false; }
73                 }
74
75                 public override bool CanWrite {
76                         get { return (_mode == CryptoStreamMode.Write); }
77                 }
78                 
79                 public override long Length {
80                         get { throw new NotSupportedException ("Length"); }
81                 }
82
83                 public override long Position {
84                         get { throw new NotSupportedException ("Position"); }
85                         set { throw new NotSupportedException ("Position"); }
86                 }
87
88                 public void Clear () 
89                 {
90                         Dispose (true);
91                         GC.SuppressFinalize (this); // not called in Stream.Dispose
92                 }
93
94                 // LAMESPEC: A CryptoStream can be close in read mode
95                 public override void Close () 
96                 {
97                         // only flush in write mode (bugzilla 46143)
98                         if ((!_flushedFinalBlock) && (_mode == CryptoStreamMode.Write))
99                                 FlushFinalBlock ();
100
101                         if (_stream != null)
102                                 _stream.Close ();
103                 }
104
105                 public override int Read ([In,Out] byte[] buffer, int offset, int count)
106                 {
107                         if (_mode != CryptoStreamMode.Read) {
108                                 throw new NotSupportedException (
109                                         Locale.GetText ("not in Read mode"));
110                         }
111                         if (offset < 0) {
112                                 throw new ArgumentOutOfRangeException ("offset", 
113                                         Locale.GetText ("negative"));
114                         }
115                         if (count < 0) {
116                                 throw new ArgumentOutOfRangeException ("count",
117                                         Locale.GetText ("negative"));
118                         }
119                         // yes - buffer.Length will throw a NullReferenceException if buffer is null
120                         // but by doing so we match MS implementation
121                         // re-ordered to avoid integer overflow
122                         if (offset > buffer.Length - count) {
123                                 throw new ArgumentException ("(offset+count)", 
124                                         Locale.GetText ("buffer overflow"));
125                         }
126                         // for some strange reason ObjectDisposedException isn't throw
127                         // instead we get a ArgumentNullException (probably from an internal method)
128                         if (_workingBlock == null) {
129                                 throw new ArgumentNullException (
130                                         Locale.GetText ("object _disposed"));
131                         }
132
133                         int result = 0;
134                         if ((count == 0) || ((_transformedPos == _transformedCount) && (_endOfStream)))
135                                 return result;
136
137                         if (_waitingBlock == null) {
138                                 _transformedBlock = new byte [_transform.OutputBlockSize << 2];
139                                 _transformedPos = 0;
140                                 _transformedCount = 0;
141                                 _waitingBlock = new byte [_transform.InputBlockSize];
142                                 _waitingCount = _stream.Read (_waitingBlock, 0, _waitingBlock.Length);
143                         }
144                         
145                         while (count > 0) {
146                                 // transformed but not yet returned
147                                 int length = (_transformedCount - _transformedPos);
148
149                                 // need more data - at least one full block must be available if we haven't reach the end of the stream
150                                 if (length < _transform.InputBlockSize) {
151                                         int transformed = 0;
152
153                                         // load a new block
154                                         _workingCount = _stream.Read (_workingBlock, 0, _workingBlock.Length);
155                                         _endOfStream = (_workingCount < _transform.InputBlockSize);
156
157                                         if (!_endOfStream) {
158                                                 // transform the waiting block
159                                                 transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
160
161                                                 // transfer temporary to waiting
162                                                 Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
163                                                 _waitingCount = _workingCount;
164                                         }
165                                         else {
166                                                 if (_workingCount > 0) {
167                                                         // transform the waiting block
168                                                         transformed = _transform.TransformBlock (_waitingBlock, 0, _waitingBlock.Length, _transformedBlock, _transformedCount);
169
170                                                         // transfer temporary to waiting
171                                                         Buffer.BlockCopy (_workingBlock, 0, _waitingBlock, 0, _workingCount);
172                                                         _waitingCount = _workingCount;
173
174                                                         length += transformed;
175                                                         _transformedCount += transformed;
176                                                 }
177                                                 byte[] input = _transform.TransformFinalBlock (_waitingBlock, 0, _waitingCount);
178                                                 transformed = input.Length;
179                                                 Buffer.BlockCopy (input, 0, _transformedBlock, _transformedCount, input.Length);
180                                                 // zeroize this last block
181                                                 Array.Clear (input, 0, input.Length);
182                                         }
183
184                                         length += transformed;
185                                         _transformedCount += transformed;
186                                 }
187                                 // compaction
188                                 if (_transformedPos > _transform.InputBlockSize) {
189                                         Buffer.BlockCopy (_transformedBlock, _transformedPos, _transformedBlock, 0, length);
190                                         _transformedCount -= _transformedPos;
191                                         _transformedPos = 0;
192                                 }
193
194                                 length = ((count < length) ? count : length);
195                                 if (length > 0) {
196                                         Buffer.BlockCopy (_transformedBlock, _transformedPos, buffer, offset, length);
197                                         _transformedPos += length;
198
199                                         result += length;
200                                         offset += length;
201                                         count -= length;
202                                 }
203
204                                 // there may not be enough data in the stream for a 
205                                 // complete block
206                                 if (((length != _transform.InputBlockSize) && (_waitingCount != _transform.InputBlockSize)) || (_endOfStream)) {
207                                         count = 0;      // no more data can be read
208                                 }
209                         }
210                         
211                         return result;
212                 }
213
214                 public override void Write (byte[] buffer, int offset, int count)
215                 {
216                         if (_mode != CryptoStreamMode.Write) {
217                                 throw new NotSupportedException (
218                                         Locale.GetText ("not in Write mode"));
219                         }
220                         if (offset < 0) { 
221                                 throw new ArgumentOutOfRangeException ("offset", 
222                                         Locale.GetText ("negative"));
223                         }
224                         if (count < 0) {
225                                 throw new ArgumentOutOfRangeException ("count", 
226                                         Locale.GetText ("negative"));
227                         }
228                         // re-ordered to avoid integer overflow
229                         if (offset > buffer.Length - count) {
230                                 throw new ArgumentException ("(offset+count)", 
231                                         Locale.GetText ("buffer overflow"));
232                         }
233
234                         // partial block (in progress)
235                         if ((_partialCount > 0) && (_partialCount != _transform.InputBlockSize)) {
236                                 int remainder = _transform.InputBlockSize - _partialCount;
237                                 remainder = ((count < remainder) ? count : remainder);
238                                 Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, remainder);
239                                 _partialCount += remainder;
240                                 offset += remainder;
241                                 count -= remainder;
242                         }
243
244                         int bufferPos = offset;
245                         while (count > 0) {
246                                 if (_partialCount == _transform.InputBlockSize) {
247                                         // use partial block to avoid (re)allocation
248                                         int len = _transform.TransformBlock (_workingBlock, 0, _partialCount, _currentBlock, 0);
249                                         _stream.Write (_currentBlock, 0, len);
250                                         // reset
251                                         _partialCount = 0;
252                                 }
253
254                                 if (_transform.CanTransformMultipleBlocks) {
255                                         // transform all except the last block (which may be the last block
256                                         // of the stream and require TransformFinalBlock)
257                                         int numBlock = ((_partialCount + count) / _transform.InputBlockSize);
258                                         if (((_partialCount + count) % _transform.InputBlockSize) == 0) // partial block ?
259                                                 numBlock--; // no then reduce
260                                         int multiSize = (numBlock * _transform.InputBlockSize);
261                                         if (numBlock > 0) {
262                                                 byte[] multiBlocks = new byte [multiSize];
263                                                 int len = _transform.TransformBlock (buffer, offset, multiSize, multiBlocks, 0);
264                                                 _stream.Write (multiBlocks, 0, len); 
265                                                 // copy last block into _currentBlock
266                                                 _partialCount = count - multiSize;
267                                                 Buffer.BlockCopy (buffer, offset + multiSize, _workingBlock, 0, _partialCount);
268                                         }
269                                         else {
270                                                 Buffer.BlockCopy (buffer, offset, _workingBlock, _partialCount, count);
271                                                 _partialCount += count;
272                                         }
273                                         count = 0; // the last block, if any, is in _workingBlock
274                                 }
275                                 else {
276                                         int len = Math.Min (_transform.InputBlockSize - _partialCount, count);
277                                         Buffer.BlockCopy (buffer, bufferPos, _workingBlock, _partialCount, len);
278                                         bufferPos += len;
279                                         _partialCount += len;
280                                         count -= len;
281                                         // here block may be full, but we wont TransformBlock it until next iteration
282                                         // so that the last block will be called in FlushFinalBlock using TransformFinalBlock
283                                 }
284                         }
285                 }
286
287                 public override void Flush ()
288                 {
289                         if (_stream != null)
290                                 _stream.Flush ();
291                 }
292
293                 public void FlushFinalBlock ()
294                 {
295                         if (_flushedFinalBlock) {
296                                 throw new NotSupportedException (
297                                         Locale.GetText ("This method cannot be called twice."));
298                         }
299                         if (_mode != CryptoStreamMode.Write) {
300                                 throw new NotSupportedException (
301                                         Locale.GetText ("cannot flush a non-writeable CryptoStream"));
302                         }
303
304                         _flushedFinalBlock = true;
305                         byte[] finalBuffer = _transform.TransformFinalBlock (_workingBlock, 0, _partialCount);
306                         if (_stream != null) {
307                                 _stream.Write (finalBuffer, 0, finalBuffer.Length);
308                                 if (_stream is CryptoStream) {
309                                         // for cascading crypto streams
310                                         (_stream as CryptoStream).FlushFinalBlock ();
311                                 }
312                                 _stream.Flush ();
313                         }
314                         // zeroize
315                         Array.Clear (finalBuffer, 0, finalBuffer.Length);
316                 }
317
318                 public override long Seek (long offset, SeekOrigin origin)
319                 {
320                         throw new NotSupportedException ("Seek");
321                 }
322                 
323                 // LAMESPEC: Exception NotSupportedException not documented
324                 public override void SetLength (long value)
325                 {
326                         throw new NotSupportedException ("SetLength");
327                 }
328
329                 protected virtual void Dispose (bool disposing) 
330                 {
331                         if (!_disposed) {
332                                 _disposed = true;
333                                 // always cleared for security reason
334                                 if (_workingBlock != null)
335                                         Array.Clear (_workingBlock, 0, _workingBlock.Length);
336                                 if (_currentBlock != null)
337                                         Array.Clear (_currentBlock, 0, _currentBlock.Length);
338                                 if (disposing) {
339                                         _stream = null;
340                                         _workingBlock = null;
341                                         _currentBlock = null;
342                                 }
343                         }
344                 }
345         }
346 }