New test.
[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 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 namespace Mono.Security.Protocol.Tls
35 {
36         public abstract class SslStreamBase: Stream, IDisposable
37         {
38                 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
39                 
40                 #region Fields
41
42                 private const int WaitTimeOut = 5 * 60 * 1000;
43
44                 internal Stream innerStream;
45                 internal MemoryStream inputBuffer;
46                 internal Context context;
47                 internal RecordProtocol protocol;
48                 internal bool ownsStream;
49                 private volatile bool disposed;
50                 private bool checkCertRevocationStatus;
51                 private object negotiate;
52                 private object read;
53                 private object write;
54                 private ManualResetEvent negotiationComplete;
55
56                 #endregion
57
58
59                 #region Constructors
60
61                 protected SslStreamBase(
62                         Stream stream,
63                         bool ownsStream)
64                 {
65                         if (stream == null)
66                         {
67                                 throw new ArgumentNullException("stream is null.");
68                         }
69                         if (!stream.CanRead || !stream.CanWrite)
70                         {
71                                 throw new ArgumentNullException("stream is not both readable and writable.");
72                         }
73
74                         this.inputBuffer = new MemoryStream();
75                         this.innerStream = stream;
76                         this.ownsStream = ownsStream;
77                         this.negotiate = new object();
78                         this.read = new object();
79                         this.write = new object();
80                         this.negotiationComplete = new ManualResetEvent(false);
81                 }
82
83                 #endregion
84
85                 #region Handshakes
86                 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
87                 {
88                         InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
89
90                         try
91                         {
92                                 try
93                                 {
94                                         this.OnNegotiateHandshakeCallback(asyncResult);
95                                 }
96                                 catch (TlsException ex)
97                                 {
98                                         this.protocol.SendAlert(ex.Alert);
99
100                                         throw new IOException("The authentication or decryption has failed.", ex);
101                                 }
102                                 catch (Exception ex)
103                                 {
104                                         this.protocol.SendAlert(AlertDescription.InternalError);
105
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 OnBeginNegotiateHandshake(AsyncCallback callback, object state);
178                 internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
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
187                 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
188
189                 #endregion
190
191                 #region Event Methods
192
193                 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
194                                                                                                                         X509Certificate remoteCertificate,
195                                                                                                                         string targetHost,
196                                                                                                                         X509CertificateCollection requestedCertificates)
197                 {
198                         return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
199                 }
200
201                 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
202                 {
203                         return OnRemoteCertificateValidation(certificate, errors);
204                 }
205
206                 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
207                         X509Certificate certificate,
208                         string targetHost)
209                 {
210                         return OnLocalPrivateKeySelection(certificate, targetHost);
211                 }
212                 #endregion
213
214                 #region Security Properties
215
216                 public bool CheckCertRevocationStatus
217                 {
218                         get { return this.checkCertRevocationStatus; }
219                         set { this.checkCertRevocationStatus = value; }
220                 }
221
222                 public CipherAlgorithmType CipherAlgorithm
223                 {
224                         get
225                         {
226                                 if (this.context.HandshakeState == HandshakeState.Finished)
227                                 {
228                                         return this.context.Current.Cipher.CipherAlgorithmType;
229                                 }
230
231                                 return CipherAlgorithmType.None;
232                         }
233                 }
234
235                 public int CipherStrength
236                 {
237                         get
238                         {
239                                 if (this.context.HandshakeState == HandshakeState.Finished)
240                                 {
241                                         return this.context.Current.Cipher.EffectiveKeyBits;
242                                 }
243
244                                 return 0;
245                         }
246                 }
247
248                 public HashAlgorithmType HashAlgorithm
249                 {
250                         get
251                         {
252                                 if (this.context.HandshakeState == HandshakeState.Finished)
253                                 {
254                                         return this.context.Current.Cipher.HashAlgorithmType;
255                                 }
256
257                                 return HashAlgorithmType.None;
258                         }
259                 }
260
261                 public int HashStrength
262                 {
263                         get
264                         {
265                                 if (this.context.HandshakeState == HandshakeState.Finished)
266                                 {
267                                         return this.context.Current.Cipher.HashSize * 8;
268                                 }
269
270                                 return 0;
271                         }
272                 }
273
274                 public int KeyExchangeStrength
275                 {
276                         get
277                         {
278                                 if (this.context.HandshakeState == HandshakeState.Finished)
279                                 {
280                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
281                                 }
282
283                                 return 0;
284                         }
285                 }
286
287                 public ExchangeAlgorithmType KeyExchangeAlgorithm
288                 {
289                         get
290                         {
291                                 if (this.context.HandshakeState == HandshakeState.Finished)
292                                 {
293                                         return this.context.Current.Cipher.ExchangeAlgorithmType;
294                                 }
295
296                                 return ExchangeAlgorithmType.None;
297                         }
298                 }
299
300                 public SecurityProtocolType SecurityProtocol
301                 {
302                         get
303                         {
304                                 if (this.context.HandshakeState == HandshakeState.Finished)
305                                 {
306                                         return this.context.SecurityProtocol;
307                                 }
308
309                                 return 0;
310                         }
311                 }
312
313                 public X509Certificate ServerCertificate
314                 {
315                         get
316                         {
317                                 if (this.context.HandshakeState == HandshakeState.Finished)
318                                 {
319                                         if (this.context.ServerSettings.Certificates != null &&
320                                                 this.context.ServerSettings.Certificates.Count > 0)
321                                         {
322                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
323                                         }
324                                 }
325
326                                 return null;
327                         }
328                 }
329
330                 // this is used by Mono's certmgr tool to download certificates
331                 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
332                 {
333                         get { return context.ServerSettings.Certificates; }
334                 }
335
336                 #endregion
337
338                 #region Internal Async Result/State Class
339
340                 private class InternalAsyncResult : IAsyncResult
341                 {
342                         private object locker = new object ();
343                         private AsyncCallback _userCallback;
344                         private object _userState;
345                         private Exception _asyncException;
346                         private ManualResetEvent handle;
347                         private bool completed;
348                         private int _bytesRead;
349                         private bool _fromWrite;
350                         private bool _proceedAfterHandshake;
351
352                         private byte[] _buffer;
353                         private int _offset;
354                         private int _count;
355
356                         public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
357                         {
358                                 _userCallback = userCallback;
359                                 _userState = userState;
360                                 _buffer = buffer;
361                                 _offset = offset;
362                                 _count = count;
363                                 _fromWrite = fromWrite;
364                                 _proceedAfterHandshake = proceedAfterHandshake;
365                         }
366
367                         public bool ProceedAfterHandshake
368                         {
369                                 get { return _proceedAfterHandshake; }
370                         }
371
372                         public bool FromWrite
373                         {
374                                 get { return _fromWrite; }
375                         }
376
377                         public byte[] Buffer
378                         {
379                                 get { return _buffer; }
380                         }
381
382                         public int Offset
383                         {
384                                 get { return _offset; }
385                         }
386
387                         public int Count
388                         {
389                                 get { return _count; }
390                         }
391
392                         public int BytesRead
393                         {
394                                 get { return _bytesRead; }
395                         }
396
397                         public object AsyncState
398                         {
399                                 get { return _userState; }
400                         }
401
402                         public Exception AsyncException
403                         {
404                                 get { return _asyncException; }
405                         }
406
407                         public bool CompletedWithError
408                         {
409                                 get {
410                                         if (IsCompleted == false)
411                                                 return false;
412                                         return null != _asyncException;
413                                 }
414                         }
415
416                         public WaitHandle AsyncWaitHandle
417                         {
418                                 get {
419                                         lock (locker) {
420                                                 if (handle == null)
421                                                         handle = new ManualResetEvent (completed);
422                                         }
423                                         return handle;
424                                 }
425                         }
426
427                         public bool CompletedSynchronously
428                         {
429                                 get { return false; }
430                         }
431
432                         public bool IsCompleted
433                         {
434                                 get {
435                                         lock (locker)
436                                                 return completed;
437                                 }
438                         }
439
440                         private void SetComplete(Exception ex, int bytesRead)
441                         {
442                                 lock (locker) {
443                                         if (completed)
444                                                 return;
445
446                                         completed = true;
447                                         _asyncException = ex;
448                                         _bytesRead = bytesRead;
449                                         if (handle != null)
450                                                 handle.Set ();
451                                 }
452                                 if (_userCallback != null)
453                                         _userCallback.BeginInvoke (this, null, null);
454                         }
455
456                         public void SetComplete(Exception ex)
457                         {
458                                 SetComplete(ex, 0);
459                         }
460
461                         public void SetComplete(int bytesRead)
462                         {
463                                 SetComplete(null, bytesRead);
464                         }
465
466                         public void SetComplete()
467                         {
468                                 SetComplete(null, 0);
469                         }
470                 }
471                 #endregion
472
473                 #region Stream Overrides and Async Stream Operations
474
475                 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
476                 {
477                         try
478                         {
479                                 lock (this.negotiate)
480                                 {
481                                         if (this.context.HandshakeState == HandshakeState.None)
482                                         {
483                                                 this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
484
485                                                 return true;
486                                         }
487                                         else
488                                         {
489                                                 return false;
490                                         }
491                                 }
492                         }
493                         catch (TlsException ex)
494                         {
495                                 this.negotiationComplete.Set();
496                                 this.protocol.SendAlert(ex.Alert);
497
498                                 throw new IOException("The authentication or decryption has failed.", ex);
499                         }
500                         catch (Exception ex)
501                         {
502                                 this.negotiationComplete.Set();
503                                 this.protocol.SendAlert(AlertDescription.InternalError);
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 (!this.context.ConnectionEnd)
607                                 {
608                                         // this will read data from the network until we have (at least) one
609                                         // record to send back to the caller
610                                         this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
611                                                 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
612                                 }
613                                 else
614                                 {
615                                         // We're done with the connection so we need to let the caller know with 0 bytes read
616                                         asyncResult.SetComplete(0);
617                                 }
618                         }
619                         catch (TlsException ex)
620                         {
621                                 this.protocol.SendAlert(ex.Alert);
622
623                                 throw new IOException("The authentication or decryption has failed.", ex);
624                         }
625                         catch (Exception ex)
626                         {
627                                 throw new IOException("IO exception during read.", ex);
628                         }
629                 }
630
631
632                 private MemoryStream recordStream = new MemoryStream();
633
634                 // read encrypted data until we have enough to decrypt (at least) one
635                 // record and return are the records (may be more than one) we have
636                 private void InternalReadCallback(IAsyncResult result)
637                 {
638                         if (this.disposed)
639                                 return;
640
641                         object[] state = (object[])result.AsyncState;
642                         byte[] recbuf = (byte[])state[0];
643                         InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
644
645                         try
646                         {
647                                 int n = innerStream.EndRead(result);
648                                 if (n > 0)
649                                 {
650                                         // Add the just received data to the waiting data
651                                         recordStream.Write(recbuf, 0, n);
652                                 }
653                                 else
654                                 {
655                                         // 0 length data means this read operation is done (lost connection in the case of a network stream).
656                                         internalResult.SetComplete(0);
657                                         return;
658                                 }
659
660                                 bool dataToReturn = false;
661                                 long pos = recordStream.Position;
662
663                                 recordStream.Position = 0;
664                                 byte[] record = null;
665
666                                 // don't try to decode record unless we have at least 5 bytes
667                                 // i.e. type (1), protocol (2) and length (2)
668                                 if (recordStream.Length >= 5)
669                                 {
670                                         record = this.protocol.ReceiveRecord(recordStream);
671                                 }
672
673                                 // a record of 0 length is valid (and there may be more record after it)
674                                 while (record != null)
675                                 {
676                                         // we probably received more stuff after the record, and we must keep it!
677                                         long remainder = recordStream.Length - recordStream.Position;
678                                         byte[] outofrecord = null;
679                                         if (remainder > 0)
680                                         {
681                                                 outofrecord = new byte[remainder];
682                                                 recordStream.Read(outofrecord, 0, outofrecord.Length);
683                                         }
684
685                                         lock (this.read)
686                                         {
687                                                 long position = this.inputBuffer.Position;
688
689                                                 if (record.Length > 0)
690                                                 {
691                                                         // Write new data to the inputBuffer
692                                                         this.inputBuffer.Seek(0, SeekOrigin.End);
693                                                         this.inputBuffer.Write(record, 0, record.Length);
694
695                                                         // Restore buffer position
696                                                         this.inputBuffer.Seek(position, SeekOrigin.Begin);
697                                                         dataToReturn = true;
698                                                 }
699                                         }
700
701                                         recordStream.SetLength(0);
702                                         record = null;
703
704                                         if (remainder > 0)
705                                         {
706                                                 recordStream.Write(outofrecord, 0, outofrecord.Length);
707                                                 // type (1), protocol (2) and length (2)
708                                                 if (recordStream.Length >= 5)
709                                                 {
710                                                         // try to see if another record is available
711                                                         recordStream.Position = 0;
712                                                         record = this.protocol.ReceiveRecord(recordStream);
713                                                         if (record == null)
714                                                                 pos = recordStream.Length;
715                                                 }
716                                                 else
717                                                         pos = remainder;
718                                         }
719                                         else
720                                                 pos = 0;
721                                 }
722
723                                 if (!dataToReturn && (n > 0))
724                                 {
725                                         // there is no record to return to caller and (possibly) more data waiting
726                                         // so continue reading from network (and appending to stream)
727                                         recordStream.Position = recordStream.Length;
728                                         this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
729                                                 new AsyncCallback(InternalReadCallback), state);
730                                 }
731                                 else
732                                 {
733                                         // we have record(s) to return -or- no more available to read from network
734                                         // reset position for further reading
735                                         recordStream.Position = pos;
736
737                                         int bytesRead = 0;
738                                         lock (this.read)
739                                         {
740                                                 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
741                                         }
742
743                                         internalResult.SetComplete(bytesRead);
744                                 }
745                         }
746                         catch (Exception ex)
747                         {
748                                 internalResult.SetComplete(ex);
749                         }
750
751                 }
752
753                 private void InternalBeginWrite(InternalAsyncResult asyncResult)
754                 {
755                         try
756                         {
757                                 // Send the buffer as a TLS record
758
759                                 lock (this.write)
760                                 {
761                                         byte[] record = this.protocol.EncodeRecord(
762                                                 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
763
764                                         this.innerStream.BeginWrite(
765                                                 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
766                                 }
767                         }
768                         catch (TlsException ex)
769                         {
770                                 this.protocol.SendAlert(ex.Alert);
771                                 this.Close();
772
773                                 throw new IOException("The authentication or decryption has failed.", ex);
774                         }
775                         catch (Exception ex)
776                         {
777                                 throw new IOException("IO exception during Write.", ex);
778                         }
779                 }
780
781                 private void InternalWriteCallback(IAsyncResult ar)
782                 {
783                         if (this.disposed)
784                                 return;
785                         
786                         InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
787
788                         try
789                         {
790                                 this.innerStream.EndWrite(ar);
791                                 internalResult.SetComplete();
792                         }
793                         catch (Exception ex)
794                         {
795                                 internalResult.SetComplete(ex);
796                         }
797                 }
798
799                 public override IAsyncResult BeginWrite(
800                         byte[] buffer,
801                         int offset,
802                         int count,
803                         AsyncCallback callback,
804                         object state)
805                 {
806                         this.checkDisposed();
807
808                         if (buffer == null)
809                         {
810                                 throw new ArgumentNullException("buffer is a null reference.");
811                         }
812                         if (offset < 0)
813                         {
814                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
815                         }
816                         if (offset > buffer.Length)
817                         {
818                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
819                         }
820                         if (count < 0)
821                         {
822                                 throw new ArgumentOutOfRangeException("count is less than 0.");
823                         }
824                         if (count > (buffer.Length - offset))
825                         {
826                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
827                         }
828
829
830                         InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
831
832                         if (this.MightNeedHandshake)
833                         {
834                                 if (! BeginNegotiateHandshake(asyncResult))
835                                 {
836                                         //we made it down here so the handshake was not started.
837                                         //another thread must have started it in the mean time.
838                                         //wait for it to complete and then perform our original operation
839                                         this.negotiationComplete.WaitOne();
840
841                                         InternalBeginWrite(asyncResult);
842                                 }
843                         }
844                         else
845                         {
846                                 InternalBeginWrite(asyncResult);
847                         }
848
849                         return asyncResult;
850                 }
851
852                 public override int EndRead(IAsyncResult asyncResult)
853                 {
854                         this.checkDisposed();
855
856                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
857                         if (internalResult == null)
858                         {
859                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
860                         }
861
862                         // Always wait until the read is complete
863                         if (!asyncResult.IsCompleted)
864                         {
865                                 if (!asyncResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
866                                         throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
867                         }
868
869                         if (internalResult.CompletedWithError)
870                         {
871                                 throw internalResult.AsyncException;
872                         }
873
874                         return internalResult.BytesRead;
875                 }
876
877                 public override void EndWrite(IAsyncResult asyncResult)
878                 {
879                         this.checkDisposed();
880
881                         InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
882                         if (internalResult == null)
883                         {
884                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
885                         }
886
887
888                         if (!asyncResult.IsCompleted)
889                         {
890                                 if (!internalResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
891                                         throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
892                         }
893
894                         if (internalResult.CompletedWithError)
895                         {
896                                 throw internalResult.AsyncException;
897                         }
898                 }
899
900                 public override void Close()
901                 {
902                         ((IDisposable)this).Dispose();
903                 }
904
905                 public override void Flush()
906                 {
907                         this.checkDisposed();
908
909                         this.innerStream.Flush();
910                 }
911
912                 public int Read(byte[] buffer)
913                 {
914                         return this.Read(buffer, 0, buffer.Length);
915                 }
916
917                 public override int Read(byte[] buffer, int offset, int count)
918                 {
919                         this.checkDisposed ();
920                         
921                         if (buffer == null)
922                         {
923                                 throw new ArgumentNullException ("buffer");
924                         }
925                         if (offset < 0)
926                         {
927                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
928                         }
929                         if (offset > buffer.Length)
930                         {
931                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
932                         }
933                         if (count < 0)
934                         {
935                                 throw new ArgumentOutOfRangeException("count is less than 0.");
936                         }
937                         if (count > (buffer.Length - offset))
938                         {
939                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
940                         }
941
942                         if (this.context.HandshakeState != HandshakeState.Finished)
943                         {
944                                 this.NegotiateHandshake (); // Handshake negotiation
945                         }
946
947                         lock (this.read) {
948                                 try {
949                                         // do we already have some decrypted data ?
950                                         if (this.inputBuffer.Position > 0) {
951                                                 // or maybe we used all the buffer before ?
952                                                 if (this.inputBuffer.Position == this.inputBuffer.Length) {
953                                                         this.inputBuffer.SetLength (0);
954                                                 } else {
955                                                         int n = this.inputBuffer.Read (buffer, offset, count);
956                                                         if (n > 0)
957                                                                 return n;
958                                                 }
959                                         }
960
961                                         bool needMoreData = false;
962                                         while (true) {
963                                                 // we first try to process the read with the data we already have
964                                                 if ((recordStream.Position == 0) || needMoreData) {
965                                                         needMoreData = false;
966                                                         // if we loop, then it either means we need more data
967                                                         byte[] recbuf = new byte[16384];
968                                                         int n = innerStream.Read (recbuf, 0, recbuf.Length);
969                                                         if (n > 0) {
970                                                                 // Add the new received data to the waiting data
971                                                                 if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
972                                                                         recordStream.Seek (0, SeekOrigin.End);
973                                                                 recordStream.Write (recbuf, 0, n);
974                                                         } else {
975                                                                 // or that the read operation is done (lost connection in the case of a network stream).
976                                                                 return 0;
977                                                         }
978                                                 }
979
980                                                 bool dataToReturn = false;
981
982                                                 recordStream.Position = 0;
983                                                 byte[] record = null;
984
985                                                 // don't try to decode record unless we have at least 5 bytes
986                                                 // i.e. type (1), protocol (2) and length (2)
987                                                 if (recordStream.Length >= 5) {
988                                                         record = this.protocol.ReceiveRecord (recordStream);
989                                                         needMoreData = (record == null);
990                                                 }
991
992                                                 // a record of 0 length is valid (and there may be more record after it)
993                                                 while (record != null) {
994                                                         // we probably received more stuff after the record, and we must keep it!
995                                                         long remainder = recordStream.Length - recordStream.Position;
996                                                         byte[] outofrecord = null;
997                                                         if (remainder > 0) {
998                                                                 outofrecord = new byte[remainder];
999                                                                 recordStream.Read (outofrecord, 0, outofrecord.Length);
1000                                                         }
1001
1002                                                         long position = this.inputBuffer.Position;
1003
1004                                                         if (record.Length > 0) {
1005                                                                 // Write new data to the inputBuffer
1006                                                                 this.inputBuffer.Seek (0, SeekOrigin.End);
1007                                                                 this.inputBuffer.Write (record, 0, record.Length);
1008
1009                                                                 // Restore buffer position
1010                                                                 this.inputBuffer.Seek (position, SeekOrigin.Begin);
1011                                                                 dataToReturn = true;
1012                                                         }
1013
1014                                                         recordStream.SetLength (0);
1015                                                         record = null;
1016
1017                                                         if (remainder > 0) {
1018                                                                 recordStream.Write (outofrecord, 0, outofrecord.Length);
1019                                                         }
1020
1021                                                         if (dataToReturn) {
1022                                                                 // we have record(s) to return -or- no more available to read from network
1023                                                                 // reset position for further reading
1024                                                                 return this.inputBuffer.Read (buffer, offset, count);
1025                                                         }
1026                                                 }
1027                                         }
1028                                 }
1029                                 catch (TlsException ex)
1030                                 {
1031                                         throw new IOException("The authentication or decryption has failed.", ex);
1032                                 }
1033                                 catch (Exception ex)
1034                                 {
1035                                         throw new IOException("IO exception during read.", ex);
1036                                 }
1037                         }
1038                 }
1039
1040                 public override long Seek(long offset, SeekOrigin origin)
1041                 {
1042                         throw new NotSupportedException();
1043                 }
1044
1045                 public override void SetLength(long value)
1046                 {
1047                         throw new NotSupportedException();
1048                 }
1049
1050                 public void Write(byte[] buffer)
1051                 {
1052                         this.Write(buffer, 0, buffer.Length);
1053                 }
1054
1055                 public override void Write(byte[] buffer, int offset, int count)
1056                 {
1057                         this.checkDisposed ();
1058                         
1059                         if (buffer == null)
1060                         {
1061                                 throw new ArgumentNullException ("buffer");
1062                         }
1063                         if (offset < 0)
1064                         {
1065                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
1066                         }
1067                         if (offset > buffer.Length)
1068                         {
1069                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
1070                         }
1071                         if (count < 0)
1072                         {
1073                                 throw new ArgumentOutOfRangeException("count is less than 0.");
1074                         }
1075                         if (count > (buffer.Length - offset))
1076                         {
1077                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
1078                         }
1079
1080                         if (this.context.HandshakeState != HandshakeState.Finished)
1081                         {
1082                                 this.NegotiateHandshake ();
1083                         }
1084
1085                         lock (this.write)
1086                         {
1087                                 try
1088                                 {
1089                                         // Send the buffer as a TLS record
1090                                         byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
1091                                         this.innerStream.Write (record, 0, record.Length);
1092                                 }
1093                                 catch (TlsException ex)
1094                                 {
1095                                         this.protocol.SendAlert(ex.Alert);
1096                                         this.Close();
1097                                         throw new IOException("The authentication or decryption has failed.", ex);
1098                                 }
1099                                 catch (Exception ex)
1100                                 {
1101                                         throw new IOException("IO exception during Write.", ex);
1102                                 }
1103                         }
1104                 }
1105
1106                 public override bool CanRead
1107                 {
1108                         get { return this.innerStream.CanRead; }
1109                 }
1110
1111                 public override bool CanSeek
1112                 {
1113                         get { return false; }
1114                 }
1115
1116                 public override bool CanWrite
1117                 {
1118                         get { return this.innerStream.CanWrite; }
1119                 }
1120
1121                 public override long Length
1122                 {
1123                         get { throw new NotSupportedException(); }
1124                 }
1125
1126                 public override long Position
1127                 {
1128                         get
1129                         {
1130                                 throw new NotSupportedException();
1131                         }
1132                         set
1133                         {
1134                                 throw new NotSupportedException();
1135                         }
1136                 }
1137                 #endregion
1138
1139                 #region IDisposable Members and Finalizer
1140
1141                 ~SslStreamBase()
1142                 {
1143                         this.Dispose(false);
1144                 }
1145
1146                 public void Dispose()
1147                 {
1148                         this.Dispose(true);
1149                         GC.SuppressFinalize(this);
1150                 }
1151
1152                 protected virtual void Dispose(bool disposing)
1153                 {
1154                         if (!this.disposed)
1155                         {
1156                                 if (disposing)
1157                                 {
1158                                         if (this.innerStream != null)
1159                                         {
1160                                                 if (this.context.HandshakeState == HandshakeState.Finished &&
1161                                                         !this.context.ConnectionEnd)
1162                                                 {
1163                                                         // Write close notify                                                   
1164                                                         this.protocol.SendAlert(AlertDescription.CloseNotify);
1165                                                 }
1166
1167                                                 if (this.ownsStream)
1168                                                 {
1169                                                         // Close inner stream
1170                                                         this.innerStream.Close();
1171                                                 }
1172                                         }
1173                                         this.ownsStream = false;
1174                                         this.innerStream = null;
1175                                 }
1176
1177                                 this.disposed = true;
1178                         }
1179                 }
1180
1181                 #endregion
1182
1183                 #region Misc Methods
1184
1185                 private void resetBuffer()
1186                 {
1187                         this.inputBuffer.SetLength(0);
1188                         this.inputBuffer.Position = 0;
1189                 }
1190
1191                 internal void checkDisposed()
1192                 {
1193                         if (this.disposed)
1194                         {
1195                                 throw new ObjectDisposedException("The Stream is closed.");
1196                         }
1197                 }
1198
1199                 #endregion
1200
1201         }
1202 }