2 Copyright (c) Microsoft Corporation
9 The class is used to encrypt/decrypt user data based on established
10 security context. Presumably the context belongs to SSPI NEGO or NTLM package.
13 Alexei Vopilov 12-Aug-2003
16 12-Aug-2003 New design that has obsoleted Authenticator class
17 15-Jan-2004 Converted to a partial class, only internal NegotiateStream implementaion goes into this file.
21 namespace System.Net.Security {
24 using System.Security;
25 using System.Security.Principal;
26 using System.Security.Permissions;
27 using System.Threading;
30 // This is a wrapping stream that does data encryption/decryption based on a successfully authenticated SSPI context.
32 public partial class NegotiateStream: AuthenticatedStream
34 private static AsyncCallback _WriteCallback = new AsyncCallback(WriteCallback);
35 private static AsyncProtocolCallback _ReadCallback = new AsyncProtocolCallback(ReadCallback);
37 private int _NestedWrite;
38 private int _NestedRead;
39 private byte[] _ReadHeader;
41 // never updated directly, special properties are used
42 private byte[] _InternalBuffer;
43 private int _InternalOffset;
44 private int _InternalBufferCount;
46 FixedSizeReader _FrameReader;
49 // Private implemenation
52 private void InitializeStreamPart()
54 _ReadHeader = new byte[4];
55 _FrameReader = new FixedSizeReader(InnerStream);
60 private byte[] InternalBuffer {
62 return _InternalBuffer;
67 private int InternalOffset {
69 return _InternalOffset;
73 private int InternalBufferCount {
75 return _InternalBufferCount;
80 private void DecrementInternalBufferCount(int decrCount)
82 _InternalOffset += decrCount;
83 _InternalBufferCount -= decrCount;
87 private void EnsureInternalBufferSize(int bytes)
89 _InternalBufferCount = bytes;
91 if (InternalBuffer == null || InternalBuffer.Length < bytes)
93 _InternalBuffer = new byte[bytes];
97 private void AdjustInternalBufferOffsetSize(int bytes, int offset)
99 _InternalBufferCount = bytes;
100 _InternalOffset = offset;
103 // Validates user parameteres for all Read/Write methods
105 private void ValidateParameters(byte[] buffer, int offset, int count)
108 throw new ArgumentNullException("buffer");
111 throw new ArgumentOutOfRangeException("offset");
114 throw new ArgumentOutOfRangeException("count");
116 if (count > buffer.Length-offset)
117 throw new ArgumentOutOfRangeException("count", SR.GetString(SR.net_offset_plus_count));
120 // Combined sync/async write method. For sync requet asyncRequest==null
122 private void ProcessWrite(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
124 ValidateParameters(buffer, offset, count);
126 if (Interlocked.Exchange(ref _NestedWrite, 1) == 1)
128 throw new NotSupportedException(SR.GetString(SR.net_io_invalidnestedcall, (asyncRequest != null? "BeginWrite":"Write"), "write"));
135 StartWriting(buffer, offset, count, asyncRequest);
140 if (e is IOException) {
143 throw new IOException(SR.GetString(SR.net_io_write), e);
147 if (asyncRequest == null || failed)
156 private void StartWriting(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
158 // We loop to this method from the callback
159 // If the last chunk was just completed from async callback (count < 0), we complete user request
162 byte[] outBuffer = null;
165 int chunkBytes = Math.Min(count, NegoState.c_MaxWriteDataSize);
169 encryptedBytes = _NegoState.EncryptData(buffer, offset, chunkBytes, ref outBuffer);
171 catch (Exception e) {
172 throw new IOException(SR.GetString(SR.net_io_encrypt), e);
175 if (asyncRequest != null)
177 // prepare for the next request
178 asyncRequest.SetNextRequest(buffer, offset+chunkBytes, count-chunkBytes, null);
179 IAsyncResult ar = InnerStream.BeginWrite(outBuffer, 0, encryptedBytes, _WriteCallback, asyncRequest);
180 if (!ar.CompletedSynchronously)
184 InnerStream.EndWrite(ar);
189 InnerStream.Write(outBuffer, 0, encryptedBytes);
191 offset += chunkBytes;
193 } while (count != 0);
196 if (asyncRequest != null) {
197 asyncRequest.CompleteUser();
201 // Combined sync/async read method. For sync requet asyncRequest==null
202 // There is a little overheader because we need to pass buffer/offset/count used only in sync.
203 // Still the benefit is that we have a common sync/async code path.
205 private int ProcessRead(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
207 ValidateParameters(buffer, offset, count);
209 if (Interlocked.Exchange(ref _NestedRead, 1) == 1)
211 throw new NotSupportedException(SR.GetString(SR.net_io_invalidnestedcall, (asyncRequest!=null? "BeginRead":"Read"), "read"));
217 if (InternalBufferCount != 0)
219 int copyBytes = InternalBufferCount > count? count:InternalBufferCount;
222 Buffer.BlockCopy(InternalBuffer, InternalOffset, buffer, offset, copyBytes);
223 DecrementInternalBufferCount(copyBytes);
225 if (asyncRequest != null) {
226 asyncRequest.CompleteUser((object) copyBytes);
230 // going into real IO
231 return StartReading(buffer, offset, count, asyncRequest);
236 if (e is IOException) {
239 throw new IOException(SR.GetString(SR.net_io_read), e);
243 // if sync request or exception
244 if (asyncRequest == null || failed)
251 // To avoid recursion when decrypted 0 bytes this method will loop until decryption resulted at least in 1 byte.
253 private int StartReading(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
256 // When we read -1 bytes means we have decrypted 0 bytes, need looping.
257 while ((result = StartFrameHeader(buffer, offset, count, asyncRequest)) == -1) {
264 // Need read frame size first
266 private int StartFrameHeader(byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
269 if (asyncRequest != null)
271 asyncRequest.SetNextRequest(_ReadHeader, 0, _ReadHeader.Length, _ReadCallback);
272 _FrameReader.AsyncReadPacket(asyncRequest);
273 if (!asyncRequest.MustCompleteSynchronously)
277 readBytes = asyncRequest.Result;
281 readBytes = _FrameReader.ReadPacket(_ReadHeader, 0, _ReadHeader.Length);
283 return StartFrameBody(readBytes, buffer, offset, count, asyncRequest);
288 private int StartFrameBody(int readBytes, byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
293 if (asyncRequest != null)
295 asyncRequest.CompleteUser((object)0);
299 GlobalLog.Assert(readBytes == _ReadHeader.Length, "NegoStream::ProcessHeader()|Frame size must be 4 but received {0} bytes.", readBytes);
301 //rpelace readBytes with the body size recovered from the header content
302 readBytes = _ReadHeader[3];
303 readBytes = (readBytes<<8) | _ReadHeader[2];
304 readBytes = (readBytes<<8) | _ReadHeader[1];
305 readBytes = (readBytes<<8) | _ReadHeader[0];
308 // The body carries 4 bytes for trailer size slot plus trailer, hence <=4 frame size is always an error.
309 // Additionally we'd like to restrice the read frame size to modest 64k
311 if (readBytes <= 4 || readBytes > NegoState.c_MaxReadFrameSize)
313 throw new IOException(SR.GetString(SR.net_frame_read_size));
317 // Always pass InternalBuffer for SSPI "in place" decryption.
318 // A user buffer can be shared by many threads in that case decryption/integrity check may fail cause of data corruption.
320 EnsureInternalBufferSize(readBytes);
321 if (asyncRequest != null) //Async
323 asyncRequest.SetNextRequest(InternalBuffer, 0, readBytes, _ReadCallback);
325 _FrameReader.AsyncReadPacket(asyncRequest);
327 if (!asyncRequest.MustCompleteSynchronously)
331 readBytes = asyncRequest.Result;
335 readBytes = _FrameReader.ReadPacket(InternalBuffer, 0, readBytes);
337 return ProcessFrameBody(readBytes, buffer, offset, count, asyncRequest);
342 private int ProcessFrameBody(int readBytes, byte[] buffer, int offset, int count, AsyncProtocolRequest asyncRequest)
346 // We already checked that the frame body is bigger than 0 bytes
347 // Hence, this is an EOF ... fire.
348 throw new IOException(SR.GetString(SR.net_io_eof));
351 //Decrypt into internal buffer, change "readBytes" to count now _Decrypted Bytes_
353 readBytes = _NegoState.DecryptData(InternalBuffer, 0, readBytes, out internalOffset);
355 // Decrypted data start from zero offset, the size can be shrinked after decryption
356 AdjustInternalBufferOffsetSize(readBytes, internalOffset);
358 if (readBytes == 0 && count != 0)
364 if (readBytes > count)
368 Buffer.BlockCopy(InternalBuffer, InternalOffset, buffer, offset, readBytes);
370 // This will adjust both the remaining internal buffer count and the offset
371 DecrementInternalBufferCount(readBytes);
373 if (asyncRequest != null)
375 asyncRequest.CompleteUser((object)readBytes);
383 private static void WriteCallback(IAsyncResult transportResult)
385 if (transportResult.CompletedSynchronously)
389 GlobalLog.Assert(transportResult.AsyncState is AsyncProtocolRequest , "NegotiateSteam::WriteCallback|State type is wrong, expected AsyncProtocolRequest.");
391 AsyncProtocolRequest asyncRequest = (AsyncProtocolRequest) transportResult.AsyncState;
394 NegotiateStream negoStream = (NegotiateStream)asyncRequest.AsyncObject;
395 negoStream.InnerStream.EndWrite(transportResult);
396 if (asyncRequest.Count == 0) {
397 // this was the last chunk
398 asyncRequest.Count = -1;
400 negoStream.StartWriting(asyncRequest.Buffer, asyncRequest.Offset, asyncRequest.Count, asyncRequest);
403 catch (Exception e) {
404 if (asyncRequest.IsUserCompleted) {
405 // This will throw on a worker thread.
408 asyncRequest.CompleteWithError(e);
413 private static void ReadCallback(AsyncProtocolRequest asyncRequest)
415 // Async ONLY completion
418 NegotiateStream negoStream = (NegotiateStream)asyncRequest.AsyncObject;
419 BufferAsyncResult bufferResult = (BufferAsyncResult) asyncRequest.UserAsyncResult;
421 // This is not a hack, just optimization to avoid an additional callback.
423 if ((object) asyncRequest.Buffer == (object)negoStream._ReadHeader)
425 negoStream.StartFrameBody(asyncRequest.Result, bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest);
429 if (-1 == negoStream.ProcessFrameBody(asyncRequest.Result, bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest))
431 // in case we decrypted 0 bytes start another reading.
432 negoStream.StartReading(bufferResult.Buffer, bufferResult.Offset, bufferResult.Count, asyncRequest);
439 if (asyncRequest.IsUserCompleted) {
440 // This will throw on a worker thread.
443 asyncRequest.CompleteWithError(e);