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