Changed link from GUID to URL
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslStreamBase.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 // 
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 // 
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Net;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
33
34 using Mono.Security.Interface;
35
36 namespace Mono.Security.Protocol.Tls
37 {
38 #if INSIDE_SYSTEM
39         internal
40 #else
41         public
42 #endif
43         abstract class SslStreamBase: Stream, IDisposable
44         {
45                 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
46                 
47                 #region Fields
48
49                 static ManualResetEvent record_processing = new ManualResetEvent (true);        
50
51                 internal Stream innerStream;
52                 internal MemoryStream inputBuffer;
53                 internal Context context;
54                 internal RecordProtocol protocol;
55                 internal bool ownsStream;
56                 private volatile bool disposed;
57                 private bool checkCertRevocationStatus;
58                 private object negotiate;
59                 private object read;
60                 private object write;
61                 private ManualResetEvent negotiationComplete;
62
63                 #endregion
64
65
66                 #region Constructors
67
68                 protected SslStreamBase(
69                         Stream stream,
70                         bool ownsStream)
71                 {
72                         if (stream == null)
73                         {
74                                 throw new ArgumentNullException("stream is null.");
75                         }
76                         if (!stream.CanRead || !stream.CanWrite)
77                         {
78                                 throw new ArgumentNullException("stream is not both readable and writable.");
79                         }
80
81                         this.inputBuffer = new MemoryStream();
82                         this.innerStream = stream;
83                         this.ownsStream = ownsStream;
84                         this.negotiate = new object();
85                         this.read = new object();
86                         this.write = new object();
87                         this.negotiationComplete = new ManualResetEvent(false);
88                 }
89
90                 #endregion
91
92                 #region Handshakes
93                 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
94                 {
95                         InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
96
97                         try
98                         {
99                                 try
100                                 {
101                                         this.EndNegotiateHandshake(asyncResult);
102                                 }
103                                 catch (Exception ex)
104                                 {
105                                         this.protocol.SendAlert(ref ex);
106                                         throw new IOException("The authentication or decryption has failed.", ex);
107                                 }
108
109                                 if (internalResult.ProceedAfterHandshake)
110                                 {
111                                         //kick off the read or write process (whichever called us) after the handshake is complete
112                                         if (internalResult.FromWrite)
113                                         {
114                                                 InternalBeginWrite(internalResult);
115                                         }
116                                         else
117                                         {
118                                                 InternalBeginRead(internalResult);
119                                         }
120                                         negotiationComplete.Set();
121                                 }
122                                 else
123                                 {
124                                         negotiationComplete.Set();
125                                         internalResult.SetComplete();
126                                 }
127
128                         }
129                         catch (Exception ex)
130                         {
131                                 negotiationComplete.Set();
132                                 internalResult.SetComplete(ex);
133                         }
134                 }
135
136                 internal bool MightNeedHandshake
137                 {
138                         get
139                         {
140                                 if (this.context.HandshakeState == HandshakeState.Finished)
141                                 {
142                                         return false;
143                                 }
144                                 else
145                                 {
146                                         lock (this.negotiate)
147                                         {
148                                                 return (this.context.HandshakeState != HandshakeState.Finished);
149                                         }
150                                 }
151                         }
152                 }
153
154                 internal void NegotiateHandshake()
155                 {
156                         if (this.MightNeedHandshake)
157                         {
158                                 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
159
160                                 //if something already started negotiation, wait for it.
161                                 //otherwise end it ourselves.
162                                 if (!BeginNegotiateHandshake(ar))
163                                 {
164                                         this.negotiationComplete.WaitOne();
165                                 }
166                                 else
167                                 {
168                                         this.EndNegotiateHandshake(ar);
169                                 }
170                         }
171                 }
172
173                 #endregion
174
175                 #region Abstracts/Virtuals
176
177                 internal abstract IAsyncResult BeginNegotiateHandshake (AsyncCallback callback, object state);
178                 internal abstract void EndNegotiateHandshake (IAsyncResult result);
179
180                 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
181                                                                                                                         X509Certificate serverCertificate,
182                                                                                                                         string targetHost,
183                                                                                                                         X509CertificateCollection serverRequestedCertificates);
184
185                 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
186                 internal abstract ValidationResult OnRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection);
187                 internal abstract bool HaveRemoteValidation2Callback { get; }
188
189                 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
190
191                 #endregion
192
193                 #region Event Methods
194
195                 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
196                                                                                                                         X509Certificate remoteCertificate,
197                                                                                                                         string targetHost,
198                                                                                                                         X509CertificateCollection requestedCertificates)
199                 {
200                         return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
201                 }
202
203                 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
204                 {
205                         return OnRemoteCertificateValidation(certificate, errors);
206                 }
207
208                 internal ValidationResult RaiseRemoteCertificateValidation2 (Mono.Security.X509.X509CertificateCollection collection)
209                 {
210                         return OnRemoteCertificateValidation2 (collection);
211                 }
212
213                 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
214                         X509Certificate certificate,
215                         string targetHost)
216                 {
217                         return OnLocalPrivateKeySelection(certificate, targetHost);
218                 }
219                 #endregion
220
221                 #region Security Properties
222
223                 public bool CheckCertRevocationStatus
224                 {
225                         get { return this.checkCertRevocationStatus; }
226                         set { this.checkCertRevocationStatus = value; }
227                 }
228
229                 public CipherAlgorithmType CipherAlgorithm
230                 {
231                         get
232                         {
233                                 if (this.context.HandshakeState == HandshakeState.Finished)
234                                 {
235                                         return this.context.Current.Cipher.CipherAlgorithmType;
236                                 }
237
238                                 return CipherAlgorithmType.None;
239                         }
240                 }
241
242                 public int CipherStrength
243                 {
244                         get
245                         {
246                                 if (this.context.HandshakeState == HandshakeState.Finished)
247                                 {
248                                         return this.context.Current.Cipher.EffectiveKeyBits;
249                                 }
250
251                                 return 0;
252                         }
253                 }
254
255                 public HashAlgorithmType HashAlgorithm
256                 {
257                         get
258                         {
259                                 if (this.context.HandshakeState == HandshakeState.Finished)
260                                 {
261                                         return this.context.Current.Cipher.HashAlgorithmType;
262                                 }
263
264                                 return HashAlgorithmType.None;
265                         }
266                 }
267
268                 public int HashStrength
269                 {
270                         get
271                         {
272                                 if (this.context.HandshakeState == HandshakeState.Finished)
273                                 {
274                                         return this.context.Current.Cipher.HashSize * 8;
275                                 }
276
277                                 return 0;
278                         }
279                 }
280
281                 public int KeyExchangeStrength
282                 {
283                         get
284                         {
285                                 if (this.context.HandshakeState == HandshakeState.Finished)
286                                 {
287                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
288                                 }
289
290                                 return 0;
291                         }
292                 }
293
294                 public ExchangeAlgorithmType KeyExchangeAlgorithm
295                 {
296                         get
297                         {
298                                 if (this.context.HandshakeState == HandshakeState.Finished)
299                                 {
300                                         return this.context.Current.Cipher.ExchangeAlgorithmType;
301                                 }
302
303                                 return ExchangeAlgorithmType.None;
304                         }
305                 }
306
307                 public SecurityProtocolType SecurityProtocol
308                 {
309                         get
310                         {
311                                 if (this.context.HandshakeState == HandshakeState.Finished)
312                                 {
313                                         return this.context.SecurityProtocol;
314                                 }
315
316                                 return 0;
317                         }
318                 }
319
320                 public X509Certificate ServerCertificate
321                 {
322                         get
323                         {
324                                 if (this.context.HandshakeState == HandshakeState.Finished)
325                                 {
326                                         if (this.context.ServerSettings.Certificates != null &&
327                                                 this.context.ServerSettings.Certificates.Count > 0)
328                                         {
329                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
330                                         }
331                                 }
332
333                                 return null;
334                         }
335                 }
336
337                 // this is used by Mono's certmgr tool to download certificates
338                 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
339                 {
340                         get { return context.ServerSettings.Certificates; }
341                 }
342
343                 #endregion
344
345                 #region Internal Async Result/State Class
346
347                 private class InternalAsyncResult : IAsyncResult
348                 {
349                         private object locker = new object ();
350                         private AsyncCallback _userCallback;
351                         private object _userState;
352                         private Exception _asyncException;
353                         private ManualResetEvent handle;
354                         private bool completed;
355                         private int _bytesRead;
356                         private bool _fromWrite;
357                         private bool _proceedAfterHandshake;
358
359                         private byte[] _buffer;
360                         private int _offset;
361                         private int _count;
362
363                         public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
364                         {
365                                 _userCallback = userCallback;
366                                 _userState = userState;
367                                 _buffer = buffer;
368                                 _offset = offset;
369                                 _count = count;
370                                 _fromWrite = fromWrite;
371                                 _proceedAfterHandshake = proceedAfterHandshake;
372                         }
373
374                         public bool ProceedAfterHandshake
375                         {
376                                 get { return _proceedAfterHandshake; }
377                         }
378
379                         public bool FromWrite
380                         {
381                                 get { return _fromWrite; }
382                         }
383
384                         public byte[] Buffer
385                         {
386                                 get { return _buffer; }
387                         }
388
389                         public int Offset
390                         {
391                                 get { return _offset; }
392                         }
393
394                         public int Count
395                         {
396                                 get { return _count; }
397                         }
398
399                         public int BytesRead
400                         {
401                                 get { return _bytesRead; }
402                         }
403
404                         public object AsyncState
405                         {
406                                 get { return _userState; }
407                         }
408
409                         public Exception AsyncException
410                         {
411                                 get { return _asyncException; }
412                         }
413
414                         public bool CompletedWithError
415                         {
416                                 get {
417                                         if (IsCompleted == false)
418                                                 return false;
419                                         return null != _asyncException;
420                                 }
421                         }
422
423                         public WaitHandle AsyncWaitHandle
424                         {
425                                 get {
426                                         lock (locker) {
427                                                 if (handle == null)
428                                                         handle = new ManualResetEvent (completed);
429                                         }
430                                         return handle;
431                                 }
432                         }
433
434                         public bool CompletedSynchronously
435                         {
436                                 get { return false; }
437                         }
438
439                         public bool IsCompleted
440                         {
441                                 get {
442                                         lock (locker)
443                                                 return completed;
444                                 }
445                         }
446
447                         private void SetComplete(Exception ex, int bytesRead)
448                         {
449                                 lock (locker) {
450                                         if (completed)
451                                                 return;
452
453                                         completed = true;
454                                         _asyncException = ex;
455                                         _bytesRead = bytesRead;
456                                         if (handle != null)
457                                                 handle.Set ();
458                                 }
459                                 if (_userCallback != null)
460                                         _userCallback.BeginInvoke (this, null, null);
461                         }
462
463                         public void SetComplete(Exception ex)
464                         {
465                                 SetComplete(ex, 0);
466                         }
467
468                         public void SetComplete(int bytesRead)
469                         {
470                                 SetComplete(null, bytesRead);
471                         }
472
473                         public void SetComplete()
474                         {
475                                 SetComplete(null, 0);
476                         }
477                 }
478                 #endregion
479
480                 #region Stream Overrides and Async Stream Operations
481
482                 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
483                 {
484                         try
485                         {
486                                 lock (this.negotiate)
487                                 {
488                                         if (this.context.HandshakeState == HandshakeState.None)
489                                         {
490                                                 this.BeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
491
492                                                 return true;
493                                         }
494                                         else
495                                         {
496                                                 return false;
497                                         }
498                                 }
499                         }
500                         catch (Exception ex)
501                         {
502                                 this.negotiationComplete.Set();
503                                 this.protocol.SendAlert(ref ex);
504
505                                 throw new IOException("The authentication or decryption has failed.", ex);
506                         }
507                 }
508
509                 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
510                 {
511                         if (asyncResult.IsCompleted == false)
512                                 asyncResult.AsyncWaitHandle.WaitOne();
513
514                         if (asyncResult.CompletedWithError)
515                         {
516                                 throw asyncResult.AsyncException;
517                         }
518                 }
519
520                 public override IAsyncResult BeginRead(
521                         byte[] buffer,
522                         int offset,
523                         int count,
524                         AsyncCallback callback,
525                         object state)
526                 {
527                         this.checkDisposed();
528
529                         if (buffer == null)
530                         {
531                                 throw new ArgumentNullException("buffer is a null reference.");
532                         }
533                         if (offset < 0)
534                         {
535                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
536                         }
537                         if (offset > buffer.Length)
538                         {
539                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
540                         }
541                         if (count < 0)
542                         {
543                                 throw new ArgumentOutOfRangeException("count is less than 0.");
544                         }
545                         if (count > (buffer.Length - offset))
546                         {
547                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
548                         }
549
550                         InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
551
552                         if (this.MightNeedHandshake)
553                         {
554                                 if (! BeginNegotiateHandshake(asyncResult))
555                                 {
556                                         //we made it down here so the handshake was not started.
557                                         //another thread must have started it in the mean time.
558                                         //wait for it to complete and then perform our original operation
559                                         this.negotiationComplete.WaitOne();
560
561                                         InternalBeginRead(asyncResult);
562                                 }
563                         }
564                         else
565                         {
566                                 InternalBeginRead(asyncResult);
567                         }
568
569                         return asyncResult;
570                 }
571
572                 // bigger than max record length for SSL/TLS
573                 private byte[] recbuf = new byte[16384];
574
575                 private void InternalBeginRead(InternalAsyncResult asyncResult)
576                 {
577                         try
578                         {
579                                 int preReadSize = 0;
580
581                                 lock (this.read)
582                                 {
583                                         // If actual buffer is fully read, reset it
584                                         bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
585
586                                         // If the buffer isn't fully read, but does have data, we need to immediately
587                                         // read the info from the buffer and let the user know that they have more data.
588                                         bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
589
590                                         if (shouldReset)
591                                         {
592                                                 this.resetBuffer();
593                                         }
594                                         else if (shouldReadImmediately)
595                                         {
596                                                 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
597                                         }
598                                 }
599
600                                 // This is explicitly done outside the synclock to avoid 
601                                 // any potential deadlocks in the delegate call.
602                                 if (0 < preReadSize)
603                                 {
604                                         asyncResult.SetComplete(preReadSize);
605                                 }
606                                 else if (recordStream.Position < recordStream.Length) {
607                                         InternalReadCallback_inner (asyncResult, recbuf, new object[] { recbuf, asyncResult }, false, 0);
608                                 }
609                                 else if (!this.context.ReceivedConnectionEnd)
610                                 {
611                                         // this will read data from the network until we have (at least) one
612                                         // record to send back to the caller
613                                         this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
614                                                 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
615                                 }
616                                 else
617                                 {
618                                         // We're done with the connection so we need to let the caller know with 0 bytes read
619                                         asyncResult.SetComplete(0);
620                                 }
621                         }
622                         catch (Exception ex)
623                         {
624                                 this.protocol.SendAlert(ref ex);
625                                 throw new IOException("The authentication or decryption has failed.", ex);
626                         }
627                 }
628
629
630                 private MemoryStream recordStream = new MemoryStream();
631
632                 // read encrypted data until we have enough to decrypt (at least) one
633                 // record and return are the records (may be more than one) we have
634                 private void InternalReadCallback(IAsyncResult result)
635                 {
636                         if (this.disposed)
637                                 return;
638
639                         object[] state = (object[])result.AsyncState;
640                         byte[] recbuf = (byte[])state[0];
641                         InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
642
643                         try
644                         {
645                                 int n = innerStream.EndRead(result);
646                                 if (n > 0)
647                                 {
648                                         // Add the just received data to the waiting data
649                                         recordStream.Write(recbuf, 0, n);
650                                 }
651                                 else
652                                 {
653                                         // 0 length data means this read operation is done (lost connection in the case of a network stream).
654                                         internalResult.SetComplete(0);
655                                         return;
656                                 }
657
658                                 InternalReadCallback_inner(internalResult, recbuf, state, true, n);
659                         }
660                         catch (Exception ex)
661                         {
662                                 internalResult.SetComplete(ex);
663                         }
664
665                 }
666
667                 // read encrypted data until we have enough to decrypt (at least) one
668                 // record and return are the records (may be more than one) we have
669                 private void InternalReadCallback_inner(InternalAsyncResult internalResult, byte[] recbuf, object[] state, bool didRead, int n)
670                 {
671                         if (this.disposed)
672                                 return;
673
674                         try
675                         {
676                                 bool dataToReturn = false;
677                                 long pos = recordStream.Position;
678
679                                 recordStream.Position = 0;
680                                 byte[] record = null;
681
682                                 // don't try to decode record unless we have at least 5 bytes
683                                 // i.e. type (1), protocol (2) and length (2)
684                                 if (recordStream.Length >= 5)
685                                 {
686                                         record = this.protocol.ReceiveRecord(recordStream);
687                                 }
688
689                                 // a record of 0 length is valid (and there may be more record after it)
690                                 while (record != null)
691                                 {
692                                         // we probably received more stuff after the record, and we must keep it!
693                                         long remainder = recordStream.Length - recordStream.Position;
694                                         byte[] outofrecord = null;
695                                         if (remainder > 0)
696                                         {
697                                                 outofrecord = new byte[remainder];
698                                                 recordStream.Read(outofrecord, 0, outofrecord.Length);
699                                         }
700
701                                         lock (this.read)
702                                         {
703                                                 long position = this.inputBuffer.Position;
704
705                                                 if (record.Length > 0)
706                                                 {
707                                                         // Write new data to the inputBuffer
708                                                         this.inputBuffer.Seek(0, SeekOrigin.End);
709                                                         this.inputBuffer.Write(record, 0, record.Length);
710
711                                                         // Restore buffer position
712                                                         this.inputBuffer.Seek(position, SeekOrigin.Begin);
713                                                         dataToReturn = true;
714                                                 }
715                                         }
716
717                                         recordStream.SetLength(0);
718                                         record = null;
719
720                                         if (remainder > 0)
721                                         {
722                                                 recordStream.Write(outofrecord, 0, outofrecord.Length);
723                                                 // type (1), protocol (2) and length (2)
724                                                 if (recordStream.Length >= 5)
725                                                 {
726                                                         // try to see if another record is available
727                                                         recordStream.Position = 0;
728                                                         record = this.protocol.ReceiveRecord(recordStream);
729                                                         if (record == null)
730                                                                 pos = recordStream.Length;
731                                                 }
732                                                 else
733                                                         pos = remainder;
734                                         }
735                                         else
736                                                 pos = 0;
737                                 }
738
739                                 if (!dataToReturn && (!didRead || (n > 0)))
740                                 {
741                                         if (context.ReceivedConnectionEnd) {
742                                                 internalResult.SetComplete (0);
743                                         } else {
744                                                 // there is no record to return to caller and (possibly) more data waiting
745                                                 // so continue reading from network (and appending to stream)
746                                                 recordStream.Position = recordStream.Length;
747                                                 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
748                                                         new AsyncCallback(InternalReadCallback), state);
749                                         }
750                                 }
751                                 else
752                                 {
753                                         // we have record(s) to return -or- no more available to read from network
754                                         // reset position for further reading
755                                         recordStream.Position = pos;
756
757                                         int bytesRead = 0;
758                                         lock (this.read)
759                                         {
760                                                 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
761                                         }
762
763                                         internalResult.SetComplete(bytesRead);
764                                 }
765                         }
766                         catch (Exception ex)
767                         {
768                                 internalResult.SetComplete(ex);
769                         }
770                 }
771
772                 private void InternalBeginWrite(InternalAsyncResult asyncResult)
773                 {
774                         try
775                         {
776                                 // Send the buffer as a TLS record
777
778                                 lock (this.write)
779                                 {
780                                         byte[] record = this.protocol.EncodeRecord(
781                                                 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
782
783                                         this.innerStream.BeginWrite(
784                                                 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
785                                 }
786                         }
787                         catch (Exception ex)
788                         {
789                                 this.protocol.SendAlert (ref ex);
790                                 this.Close();
791
792                                 throw new IOException("The authentication or decryption has failed.", ex);
793                         }
794                 }
795
796                 private void InternalWriteCallback(IAsyncResult ar)
797                 {
798                         if (this.disposed)
799                                 return;
800                         
801                         InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
802
803                         try
804                         {
805                                 this.innerStream.EndWrite(ar);
806                                 internalResult.SetComplete();
807                         }
808                         catch (Exception ex)
809                         {
810                                 internalResult.SetComplete(ex);
811                         }
812                 }
813
814                 public override IAsyncResult BeginWrite(
815                         byte[] buffer,
816                         int offset,
817                         int count,
818                         AsyncCallback callback,
819                         object state)
820                 {
821                         this.checkDisposed();
822
823                         if (buffer == null)
824                         {
825                                 throw new ArgumentNullException("buffer is a null reference.");
826                         }
827                         if (offset < 0)
828                         {
829                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
830                         }
831                         if (offset > buffer.Length)
832                         {
833                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
834                         }
835                         if (count < 0)
836                         {
837                                 throw new ArgumentOutOfRangeException("count is less than 0.");
838                         }
839                         if (count > (buffer.Length - offset))
840                         {
841                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
842                         }
843
844
845                         InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
846
847                         if (this.MightNeedHandshake)
848                         {
849                                 if (! BeginNegotiateHandshake(asyncResult))
850                                 {
851                                         //we made it down here so the handshake was not started.
852                                         //another thread must have started it in the mean time.
853                                         //wait for it to complete and then perform our original operation
854                                         this.negotiationComplete.WaitOne();
855
856                                         InternalBeginWrite(asyncResult);
857                                 }
858                         }
859                         else
860                         {
861                                 InternalBeginWrite(asyncResult);
862                         }
863
864                         return asyncResult;
865                 }
866
867                 public override int EndRead(IAsyncResult asyncResult)
868                 {
869                         this.checkDisposed();
870
871                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
872                         if (internalResult == null)
873                         {
874                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
875                         }
876
877                         // Always wait until the read is complete
878                         if (!asyncResult.IsCompleted)
879                         {
880                                 if (!asyncResult.AsyncWaitHandle.WaitOne ())
881                                         throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
882                         }
883
884                         if (internalResult.CompletedWithError)
885                         {
886                                 throw internalResult.AsyncException;
887                         }
888
889                         return internalResult.BytesRead;
890                 }
891
892                 public override void EndWrite(IAsyncResult asyncResult)
893                 {
894                         this.checkDisposed();
895
896                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
897                         if (internalResult == null)
898                         {
899                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
900                         }
901
902
903                         if (!asyncResult.IsCompleted)
904                         {
905                                 if (!internalResult.AsyncWaitHandle.WaitOne ())
906                                         throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
907                         }
908
909                         if (internalResult.CompletedWithError)
910                         {
911                                 throw internalResult.AsyncException;
912                         }
913                 }
914
915                 public override void Close()
916                 {
917                         base.Close ();
918                 }
919
920                 public override void Flush()
921                 {
922                         this.checkDisposed();
923
924                         this.innerStream.Flush();
925                 }
926
927                 public int Read(byte[] buffer)
928                 {
929                         return this.Read(buffer, 0, buffer.Length);
930                 }
931
932                 public override int Read(byte[] buffer, int offset, int count)
933                 {
934                         this.checkDisposed ();
935                         
936                         if (buffer == null)
937                         {
938                                 throw new ArgumentNullException ("buffer");
939                         }
940                         if (offset < 0)
941                         {
942                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
943                         }
944                         if (offset > buffer.Length)
945                         {
946                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
947                         }
948                         if (count < 0)
949                         {
950                                 throw new ArgumentOutOfRangeException("count is less than 0.");
951                         }
952                         if (count > (buffer.Length - offset))
953                         {
954                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
955                         }
956
957                         if (this.context.HandshakeState != HandshakeState.Finished)
958                         {
959                                 this.NegotiateHandshake (); // Handshake negotiation
960                         }
961
962                         lock (this.read) {
963                                 try {
964                                         record_processing.Reset ();
965                                         // do we already have some decrypted data ?
966                                         if (this.inputBuffer.Position > 0) {
967                                                 // or maybe we used all the buffer before ?
968                                                 if (this.inputBuffer.Position == this.inputBuffer.Length) {
969                                                         this.inputBuffer.SetLength (0);
970                                                 } else {
971                                                         int n = this.inputBuffer.Read (buffer, offset, count);
972                                                         if (n > 0) {
973                                                                 record_processing.Set ();
974                                                                 return n;
975                                                         }
976                                                 }
977                                         }
978
979                                         bool needMoreData = false;
980                                         while (true) {
981                                                 // we first try to process the read with the data we already have
982                                                 if ((recordStream.Position == 0) || needMoreData) {
983                                                         needMoreData = false;
984                                                         // if we loop, then it either means we need more data
985                                                         byte[] recbuf = new byte[16384];
986                                                         int n = 0;
987                                                         if (count == 1) {
988                                                                 int value = innerStream.ReadByte ();
989                                                                 if (value >= 0) {
990                                                                         recbuf[0] = (byte) value;
991                                                                         n = 1;
992                                                                 }
993                                                         } else {
994                                                                 n = innerStream.Read (recbuf, 0, recbuf.Length);
995                                                         }
996                                                         if (n > 0) {
997                                                                 // Add the new received data to the waiting data
998                                                                 if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
999                                                                         recordStream.Seek (0, SeekOrigin.End);
1000                                                                 recordStream.Write (recbuf, 0, n);
1001                                                         } else {
1002                                                                 // or that the read operation is done (lost connection in the case of a network stream).
1003                                                                 record_processing.Set ();
1004                                                                 return 0;
1005                                                         }
1006                                                 }
1007
1008                                                 bool dataToReturn = false;
1009
1010                                                 recordStream.Position = 0;
1011                                                 byte[] record = null;
1012
1013                                                 // don't try to decode record unless we have at least 5 bytes
1014                                                 // i.e. type (1), protocol (2) and length (2)
1015                                                 if (recordStream.Length >= 5) {
1016                                                         record = this.protocol.ReceiveRecord (recordStream);
1017                                                         needMoreData = (record == null);
1018                                                 }
1019
1020                                                 // a record of 0 length is valid (and there may be more record after it)
1021                                                 while (record != null) {
1022                                                         // we probably received more stuff after the record, and we must keep it!
1023                                                         long remainder = recordStream.Length - recordStream.Position;
1024                                                         byte[] outofrecord = null;
1025                                                         if (remainder > 0) {
1026                                                                 outofrecord = new byte[remainder];
1027                                                                 recordStream.Read (outofrecord, 0, outofrecord.Length);
1028                                                         }
1029
1030                                                         long position = this.inputBuffer.Position;
1031
1032                                                         if (record.Length > 0) {
1033                                                                 // Write new data to the inputBuffer
1034                                                                 this.inputBuffer.Seek (0, SeekOrigin.End);
1035                                                                 this.inputBuffer.Write (record, 0, record.Length);
1036
1037                                                                 // Restore buffer position
1038                                                                 this.inputBuffer.Seek (position, SeekOrigin.Begin);
1039                                                                 dataToReturn = true;
1040                                                         }
1041
1042                                                         recordStream.SetLength (0);
1043                                                         record = null;
1044
1045                                                         if (remainder > 0) {
1046                                                                 recordStream.Write (outofrecord, 0, outofrecord.Length);
1047                                                                 recordStream.Position = 0;
1048                                                         }
1049
1050                                                         if (dataToReturn) {
1051                                                                 // we have record(s) to return -or- no more available to read from network
1052                                                                 // reset position for further reading
1053                                                                 int i = inputBuffer.Read (buffer, offset, count);
1054                                                                 record_processing.Set ();
1055                                                                 return i;
1056                                                         }
1057                                                 }
1058                                         }
1059                                 }
1060                                 catch (TlsException ex)
1061                                 {
1062                                         throw new IOException("The authentication or decryption has failed.", ex);
1063                                 }
1064                                 catch (Exception ex)
1065                                 {
1066                                         throw new IOException("IO exception during read.", ex);
1067                                 }
1068                         }
1069                 }
1070
1071                 public override long Seek(long offset, SeekOrigin origin)
1072                 {
1073                         throw new NotSupportedException();
1074                 }
1075
1076                 public override void SetLength(long value)
1077                 {
1078                         throw new NotSupportedException();
1079                 }
1080
1081                 public void Write(byte[] buffer)
1082                 {
1083                         this.Write(buffer, 0, buffer.Length);
1084                 }
1085
1086                 public override void Write(byte[] buffer, int offset, int count)
1087                 {
1088                         this.checkDisposed ();
1089                         
1090                         if (buffer == null)
1091                         {
1092                                 throw new ArgumentNullException ("buffer");
1093                         }
1094                         if (offset < 0)
1095                         {
1096                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
1097                         }
1098                         if (offset > buffer.Length)
1099                         {
1100                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
1101                         }
1102                         if (count < 0)
1103                         {
1104                                 throw new ArgumentOutOfRangeException("count is less than 0.");
1105                         }
1106                         if (count > (buffer.Length - offset))
1107                         {
1108                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
1109                         }
1110
1111                         if (this.context.HandshakeState != HandshakeState.Finished)
1112                         {
1113                                 this.NegotiateHandshake ();
1114                         }
1115
1116                         lock (this.write)
1117                         {
1118                                 try
1119                                 {
1120                                         // Send the buffer as a TLS record
1121                                         byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
1122                                         this.innerStream.Write (record, 0, record.Length);
1123                                 }
1124                                 catch (Exception ex)
1125                                 {
1126                                         this.protocol.SendAlert(ref ex);
1127                                         this.Close();
1128                                         throw new IOException("The authentication or decryption has failed.", ex);
1129                                 }
1130                         }
1131                 }
1132
1133                 public override bool CanRead
1134                 {
1135                         get { return this.innerStream.CanRead; }
1136                 }
1137
1138                 public override bool CanSeek
1139                 {
1140                         get { return false; }
1141                 }
1142
1143                 public override bool CanWrite
1144                 {
1145                         get { return this.innerStream.CanWrite; }
1146                 }
1147
1148                 public override long Length
1149                 {
1150                         get { throw new NotSupportedException(); }
1151                 }
1152
1153                 public override long Position
1154                 {
1155                         get
1156                         {
1157                                 throw new NotSupportedException();
1158                         }
1159                         set
1160                         {
1161                                 throw new NotSupportedException();
1162                         }
1163                 }
1164                 #endregion
1165
1166                 #region IDisposable Members and Finalizer
1167
1168                 ~SslStreamBase()
1169                 {
1170                         this.Dispose(false);
1171                 }
1172
1173                 protected override void Dispose (bool disposing)
1174                 {
1175                         if (!this.disposed)
1176                         {
1177                                 if (disposing)
1178                                 {
1179                                         if (this.innerStream != null)
1180                                         {
1181                                                 if (this.context.HandshakeState == HandshakeState.Finished &&
1182                                                         !this.context.SentConnectionEnd)
1183                                                 {
1184                                                         // Write close notify
1185                                                         try {
1186                                                                 this.protocol.SendAlert(AlertDescription.CloseNotify);
1187                                                         } catch {
1188                                                         }
1189                                                 }
1190
1191                                                 if (this.ownsStream)
1192                                                 {
1193                                                         // Close inner stream
1194                                                         this.innerStream.Close();
1195                                                 }
1196                                         }
1197                                         this.ownsStream = false;
1198                                         this.innerStream = null;
1199                                 }
1200
1201                                 this.disposed = true;
1202                                 base.Dispose (disposing);
1203                         }
1204                 }
1205
1206                 #endregion
1207
1208                 #region Misc Methods
1209
1210                 private void resetBuffer()
1211                 {
1212                         this.inputBuffer.SetLength(0);
1213                         this.inputBuffer.Position = 0;
1214                 }
1215
1216                 internal void checkDisposed()
1217                 {
1218                         if (this.disposed)
1219                         {
1220                                 throw new ObjectDisposedException("The Stream is closed.");
1221                         }
1222                 }
1223
1224                 #endregion
1225
1226         }
1227 }