2005-05-10 Gonzalo Paniagua Javier <gonzalo@ximian.com>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslClientStream.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3
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.Protocol.Tls.Handshake;
35
36 namespace Mono.Security.Protocol.Tls
37 {
38         #region Delegates
39
40         public delegate bool CertificateValidationCallback(
41                 X509Certificate certificate, 
42                 int[]                   certificateErrors);
43
44         public delegate X509Certificate CertificateSelectionCallback(
45                 X509CertificateCollection       clientCertificates, 
46                 X509Certificate                         serverCertificate, 
47                 string                                          targetHost, 
48                 X509CertificateCollection       serverRequestedCertificates);
49
50         public delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
51                 X509Certificate certificate, 
52                 string                  targetHost);
53
54         delegate int ReadDelegate (byte [] buffer, int offset, int count);
55         #endregion
56
57         public class SslClientStream : Stream, IDisposable
58         {
59                 #region Internal Events
60                 
61                 internal event CertificateValidationCallback    ServerCertValidation;
62                 internal event CertificateSelectionCallback             ClientCertSelection;
63                 internal event PrivateKeySelectionCallback              PrivateKeySelection;
64                 
65                 #endregion
66
67                 #region Fields
68
69                 private Stream                                                  innerStream;
70                 private MemoryStream                                    inputBuffer;
71                 private ClientContext                                   context;
72                 private ClientRecordProtocol                    protocol;
73                 private bool                                                    ownsStream;
74                 private bool                                                    disposed;
75                 private bool                                                    checkCertRevocationStatus;
76                 private object                                                  negotiate;
77                 private object                                                  read;
78                 private object                                                  write;
79                 private ReadDelegate                                            rd;
80
81                 #endregion
82
83                 #region Properties
84
85                 public override bool CanRead
86                 {
87                         get { return this.innerStream.CanRead; }
88                 }
89
90                 public override bool CanSeek
91                 {
92                         get { return false; }
93                 }
94
95                 public override bool CanWrite
96                 {
97                         get { return this.innerStream.CanWrite; }
98                 }
99
100                 public override long Length
101                 {
102                         get { throw new NotSupportedException(); }
103                 }
104
105                 public override long Position
106                 {
107                         get { throw new NotSupportedException(); }
108                         set { throw new NotSupportedException(); }
109                 }
110
111                 // required by HttpsClientStream for proxy support
112                 internal Stream InputBuffer {
113                         get { return inputBuffer; }
114                 }
115                 #endregion
116
117                 #region Security Properties
118
119                 public bool CheckCertRevocationStatus 
120                 {
121                         get { return this.checkCertRevocationStatus ; }
122                         set { this.checkCertRevocationStatus = value; }
123                 }
124
125                 public CipherAlgorithmType CipherAlgorithm 
126                 {
127                         get 
128                         { 
129                                 if (this.context.HandshakeState == HandshakeState.Finished)
130                                 {
131                                         return this.context.Cipher.CipherAlgorithmType;
132                                 }
133
134                                 return CipherAlgorithmType.None;
135                         }
136                 }
137                 
138                 public int CipherStrength 
139                 {
140                         get 
141                         { 
142                                 if (this.context.HandshakeState == HandshakeState.Finished)
143                                 {
144                                         return this.context.Cipher.EffectiveKeyBits;
145                                 }
146
147                                 return 0;
148                         }
149                 }
150                 
151                 public X509CertificateCollection ClientCertificates 
152                 {
153                         get { return this.context.ClientSettings.Certificates;}
154                 }
155                 
156                 public HashAlgorithmType HashAlgorithm 
157                 {
158                         get 
159                         { 
160                                 if (this.context.HandshakeState == HandshakeState.Finished)
161                                 {
162                                         return this.context.Cipher.HashAlgorithmType; 
163                                 }
164
165                                 return HashAlgorithmType.None;
166                         }
167                 }
168                 
169                 public int HashStrength
170                 {
171                         get 
172                         { 
173                                 if (this.context.HandshakeState == HandshakeState.Finished)
174                                 {
175                                         return this.context.Cipher.HashSize * 8; 
176                                 }
177
178                                 return 0;
179                         }
180                 }
181                 
182                 public int KeyExchangeStrength 
183                 {
184                         get 
185                         { 
186                                 if (this.context.HandshakeState == HandshakeState.Finished)
187                                 {
188                                         return this.context.ServerSettings.Certificates[0].RSA.KeySize;
189                                 }
190
191                                 return 0;
192                         }
193                 }
194                 
195                 public ExchangeAlgorithmType KeyExchangeAlgorithm 
196                 {
197                         get 
198                         { 
199                                 if (this.context.HandshakeState == HandshakeState.Finished)
200                                 {
201                                         return this.context.Cipher.ExchangeAlgorithmType; 
202                                 }
203
204                                 return ExchangeAlgorithmType.None;
205                         }
206                 }
207                 
208                 public SecurityProtocolType SecurityProtocol 
209                 {
210                         get 
211                         { 
212                                 if (this.context.HandshakeState == HandshakeState.Finished)
213                                 {
214                                         return this.context.SecurityProtocol; 
215                                 }
216
217                                 return 0;
218                         }
219                 }
220                 
221                 public X509Certificate SelectedClientCertificate 
222                 {
223                         get { return this.context.ClientSettings.ClientCertificate; }
224                 }
225
226                 public X509Certificate ServerCertificate 
227                 {
228                         get 
229                         { 
230                                 if (this.context.HandshakeState == HandshakeState.Finished)
231                                 {
232                                         if (this.context.ServerSettings.Certificates != null &&
233                                                 this.context.ServerSettings.Certificates.Count > 0)
234                                         {
235                                                 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
236                                         }
237                                 }
238
239                                 return null;
240                         }
241                 } 
242
243                 // this is used by Mono's certmgr tool to download certificates
244                 internal Mono.Security.X509.X509CertificateCollection ServerCertificates {
245                         get { return context.ServerSettings.Certificates; }
246                 }
247
248                 #endregion
249
250                 #region Callback Properties
251
252                 public CertificateValidationCallback ServerCertValidationDelegate
253                 {
254                         get { return this.ServerCertValidation; }
255                         set { this.ServerCertValidation = value; }                      
256                 }
257
258                 public CertificateSelectionCallback ClientCertSelectionDelegate 
259                 {
260                         get { return this.ClientCertSelection; }
261                         set { this.ClientCertSelection = value; }
262                 }
263
264                 public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate 
265                 {
266                         get { return this.PrivateKeySelection; }
267                         set { this.PrivateKeySelection = value; }
268                 }
269
270                 #endregion
271
272                 #region Constructors
273                 
274                 public SslClientStream(
275                         Stream  stream, 
276                         string  targetHost, 
277                         bool    ownsStream) 
278                         : this(
279                                 stream, targetHost, ownsStream, 
280                                 SecurityProtocolType.Default, null)
281                 {
282                 }
283                 
284                 public SslClientStream(
285                         Stream                          stream, 
286                         string                          targetHost, 
287                         X509Certificate         clientCertificate) 
288                         : this(
289                                 stream, targetHost, false, SecurityProtocolType.Default, 
290                                 new X509CertificateCollection(new X509Certificate[]{clientCertificate}))
291                 {
292                 }
293
294                 public SslClientStream(
295                         Stream                                          stream,
296                         string                                          targetHost, 
297                         X509CertificateCollection clientCertificates) : 
298                         this(
299                                 stream, targetHost, false, SecurityProtocolType.Default, 
300                                 clientCertificates)
301                 {
302                 }
303
304                 public SslClientStream(
305                         Stream                                  stream,
306                         string                                  targetHost,
307                         bool                                    ownsStream,
308                         SecurityProtocolType    securityProtocolType) 
309                         : this(
310                                 stream, targetHost, ownsStream, securityProtocolType,
311                                 new X509CertificateCollection())
312                 {
313                 }
314
315                 public SslClientStream(
316                         Stream                                          stream,
317                         string                                          targetHost,
318                         bool                                            ownsStream,
319                         SecurityProtocolType            securityProtocolType,
320                         X509CertificateCollection       clientCertificates)
321                 {
322                         if (stream == null)
323                         {
324                                 throw new ArgumentNullException("stream is null.");
325                         }
326                         if (!stream.CanRead || !stream.CanWrite)
327                         {
328                                 throw new ArgumentNullException("stream is not both readable and writable.");
329                         }
330                         if (targetHost == null || targetHost.Length == 0)
331                         {
332                                 throw new ArgumentNullException("targetHost is null or an empty string.");
333                         }
334
335                         this.context = new ClientContext(
336                                 this,
337                                 securityProtocolType, 
338                                 targetHost, 
339                                 clientCertificates);
340
341                         this.inputBuffer        = new MemoryStream();
342                         this.innerStream        = stream;
343                         this.ownsStream         = ownsStream;
344                         this.negotiate                  = new object ();
345                         this.read                       = new object ();
346                         this.write                      = new object ();
347                         this.protocol           = new ClientRecordProtocol(innerStream, context);
348                 }
349
350                 #endregion
351
352                 #region Finalizer
353
354                 ~SslClientStream()
355                 {
356                         this.Dispose(false);
357                 }
358
359                 #endregion
360
361         #region IDisposable Methods
362
363                 void IDisposable.Dispose()
364                 {
365                         this.Dispose(true);
366                         GC.SuppressFinalize(this);
367                 }
368
369                 protected virtual void Dispose(bool disposing)
370                 {
371                         if (!this.disposed)
372                         {
373                                 if (disposing)
374                                 {
375                                         if (this.innerStream != null)
376                                         {
377                                                 if (this.context.HandshakeState == HandshakeState.Finished &&
378                                                         !this.context.ConnectionEnd)
379                                                 {
380                                                         // Write close notify                                                   
381                                                         this.protocol.SendAlert(AlertDescription.CloseNotify);
382                                                 }
383
384                                                 if (this.ownsStream)
385                                                 {
386                                                         // Close inner stream
387                                                         this.innerStream.Close();
388                                                 }
389                                         }
390                                         this.ownsStream                         = false;
391                                         this.innerStream                        = null;
392                                         this.ClientCertSelection        = null;
393                                         this.ServerCertValidation       = null;
394                                         this.PrivateKeySelection        = null;
395                                 }
396
397                                 this.disposed = true;
398                         }
399                 }
400
401                 #endregion
402
403                 #region Methods
404
405                 public override IAsyncResult BeginRead(
406                         byte[]                  buffer,
407                         int                             offset,
408                         int                             count,
409                         AsyncCallback   callback,
410                         object                  state)
411                 {\r
412                         this.checkDisposed ();
413                         
414                         if (buffer == null)
415                         {
416                                 throw new ArgumentNullException("buffer is a null reference.");
417                         }
418                         if (offset < 0)
419                         {
420                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
421                         }
422                         if (offset > buffer.Length)
423                         {
424                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
425                         }
426                         if (count < 0)
427                         {
428                                 throw new ArgumentOutOfRangeException("count is less than 0.");
429                         }
430                         if (count > (buffer.Length - offset))
431                         {
432                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
433                         }
434
435                         if (this.context.HandshakeState == HandshakeState.None)
436                         {
437                                 // Note: Async code may have problem if they can't ensure that
438                                 // the Negotiate phase isn't done during a read operation.
439                                 // System.Net.HttpWebRequest protects itself from that problem
440                                 lock (this.negotiate)
441                                 {
442                                         if (this.context.HandshakeState == HandshakeState.None)
443                                         {
444                                                 this.NegotiateHandshake();
445                                         }
446                                 }
447                         }
448
449                         IAsyncResult asyncResult = null;
450
451                         lock (this.read)
452                         {
453                                 try
454                                 {
455                                         // If actual buffer is full readed reset it
456                                         if (this.inputBuffer.Position == this.inputBuffer.Length &&
457                                                 this.inputBuffer.Length > 0)
458                                         {
459                                                 this.resetBuffer();
460                                         }
461
462                                         if (!this.context.ConnectionEnd)
463                                         {
464                                                 if ((this.inputBuffer.Length == this.inputBuffer.Position) && (count > 0))\r
465                                                 {
466                                                         // bigger than max record length for SSL/TLS\r
467                                                         byte[] recbuf = new byte[16384]; \r
468
469                                                         // this will read data from the network until we have (at least) one
470                                                         // record to send back to the caller\r
471                                                         this.innerStream.BeginRead (recbuf, 0, recbuf.Length, 
472                                                                 new AsyncCallback (NetworkReadCallback), recbuf);\r
473 \r
474                                                         if (!recordEvent.WaitOne (300000, false)) // 5 minutes\r
475                                                         {
476                                                                 // FAILSAFE
477                                                                 DebugHelper.WriteLine ("TIMEOUT length {0}, position {1}, count {2} - {3}\n{4}", 
478                                                                         this.inputBuffer.Length, this.inputBuffer.Position, count, GetHashCode (),
479                                                                         Environment.StackTrace);\r
480                                                                 throw new TlsException (AlertDescription.InternalError);\r
481                                                         }
482                                                 }\r
483                                         }
484
485                                         // return the record(s) to the caller
486                                         rd = new ReadDelegate (this.inputBuffer.Read);
487                                         asyncResult = rd.BeginInvoke (buffer, offset, count, callback, state);
488                                 }
489                                 catch (TlsException ex)
490                                 {
491                                         this.protocol.SendAlert(ex.Alert);
492                                         this.Close();
493
494                                         throw new IOException("The authentication or decryption has failed.");
495                                 }
496                                 catch (Exception)
497                                 {
498                                         throw new IOException("IO exception during read.");
499                                 }
500
501                         }
502
503                         return asyncResult;
504                 }\r
505 \r
506                 private ManualResetEvent recordEvent = new ManualResetEvent (false);\r
507                 private MemoryStream recordStream = new MemoryStream ();\r
508
509                 // read encrypted data until we have enough to decrypt (at least) one
510                 // record and return are the records (may be more than one) we have\r
511                 private void NetworkReadCallback (IAsyncResult result)\r
512                 {\r
513                         if (this.disposed)
514                                 return;
515
516                         byte[] recbuf = (byte[])result.AsyncState;\r
517                         int n = innerStream.EndRead (result);
518                         if (n > 0)\r
519                         {\r
520                                 // add the just received data to the waiting data\r
521                                 recordStream.Write (recbuf, 0, n);\r
522                         }\r
523 \r
524                         bool dataToReturn = false;\r
525                         long pos = recordStream.Position;
526 \r
527                         recordStream.Position = 0;\r
528                         byte[] record = null;
529
530                         // don't try to decode record unless we have at least 5 bytes\r
531                         // i.e. type (1), protocol (2) and length (2)\r
532                         if (recordStream.Length >= 5)\r
533                         {\r
534                                 record = this.protocol.ReceiveRecord (recordStream);\r
535                         }\r
536
537                         // a record of 0 length is valid (and there may be more record after it)
538                         while (record != null)\r
539                         {\r
540                                 // we probably received more stuff after the record, and we must keep it!\r
541                                 long remainder = recordStream.Length - recordStream.Position;\r
542                                 byte[] outofrecord = null;\r
543                                 if (remainder > 0)\r
544                                 {\r
545                                         outofrecord = new byte[remainder];\r
546                                         recordStream.Read (outofrecord, 0, outofrecord.Length);\r
547                                 }\r
548 \r
549                                 long position = this.inputBuffer.Position;\r
550
551                                 if (record.Length > 0)
552                                 {\r
553                                         // Write new data to the inputBuffer\r
554                                         this.inputBuffer.Seek (0, SeekOrigin.End);\r
555                                         this.inputBuffer.Write (record, 0, record.Length);\r
556 \r
557                                         // Restore buffer position\r
558                                         this.inputBuffer.Seek (position, SeekOrigin.Begin);
559                                         dataToReturn = true;
560                                 }\r
561 \r
562                                 recordStream.SetLength (0);\r
563                                 record = null;\r
564 \r
565                                 if (remainder > 0)\r
566                                 {\r
567                                         recordStream.Write (outofrecord, 0, outofrecord.Length);\r
568                                         // type (1), protocol (2) and length (2)\r
569                                         if (recordStream.Length >= 5)\r
570                                         {\r
571                                                 // try to see if another record is available\r
572                                                 recordStream.Position = 0;\r
573                                                 record = this.protocol.ReceiveRecord (recordStream);\r
574                                                 if (record == null)\r
575                                                         pos = recordStream.Length;\r
576                                         }\r
577                                         else\r
578                                                 pos = remainder;\r
579                                 }\r
580                                 else\r
581                                         pos = 0;\r
582                         }\r
583
584                         if (!dataToReturn && (n > 0))\r
585                         {
586                                 // there is no record to return to caller and (possibly) more data waiting\r
587                                 // so continue reading from network (and appending to stream)\r
588                                 recordStream.Position = recordStream.Length;\r
589                                 this.innerStream.BeginRead (recbuf, 0, recbuf.Length, 
590                                         new AsyncCallback (NetworkReadCallback), recbuf);\r
591                         }\r
592                         else\r
593                         {
594                                 // we have record(s) to return -or- no more available to read from network
595                                 // reset position for further reading\r
596                                 recordStream.Position = pos;\r
597                                 recordEvent.Set ();\r
598                         }\r
599                 }
600
601                 public override IAsyncResult BeginWrite(
602                         byte[]                  buffer,
603                         int                             offset,
604                         int                             count,
605                         AsyncCallback   callback,
606                         object                  state)
607                 {
608                         this.checkDisposed();
609
610                         if (buffer == null)
611                         {
612                                 throw new ArgumentNullException("buffer is a null reference.");
613                         }
614                         if (offset < 0)
615                         {
616                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
617                         }
618                         if (offset > buffer.Length)
619                         {
620                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
621                         }
622                         if (count < 0)
623                         {
624                                 throw new ArgumentOutOfRangeException("count is less than 0.");
625                         }
626                         if (count > (buffer.Length - offset))
627                         {
628                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
629                         }
630
631                         if (this.context.HandshakeState == HandshakeState.None)
632                         {
633                                 lock (this.negotiate)
634                                 {
635                                         if (this.context.HandshakeState == HandshakeState.None)
636                                         {
637                                                 this.NegotiateHandshake();
638                                         }
639                                 }
640                         }
641
642                         IAsyncResult asyncResult;
643
644                         lock (this.write)
645                         {
646                                 try
647                                 {
648                                         // Send the buffer as a TLS record
649                                         
650                                         byte[] record = this.protocol.EncodeRecord(
651                                                 ContentType.ApplicationData, buffer, offset, count);
652                                 
653                                         asyncResult = this.innerStream.BeginWrite(
654                                                 record, 0, record.Length, callback, state);
655                                 }
656                                 catch (TlsException ex)
657                                 {
658                                         this.protocol.SendAlert(ex.Alert);
659                                         this.Close();
660
661                                         throw new IOException("The authentication or decryption has failed.");
662                                 }
663                                 catch (Exception)
664                                 {
665                                         throw new IOException("IO exception during Write.");
666                                 }
667                         }
668
669                         return asyncResult;
670                 }
671
672                 public override int EndRead(IAsyncResult asyncResult)
673                 {
674                         this.checkDisposed();
675
676                         if (asyncResult == null)
677                         {
678                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
679                         }\r
680
681                         recordEvent.Reset ();
682                         return this.rd.EndInvoke (asyncResult);
683                 }
684
685                 public override void EndWrite(IAsyncResult asyncResult)
686                 {
687                         this.checkDisposed();
688
689                         if (asyncResult == null)
690                         {
691                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
692                         }
693
694                         this.innerStream.EndWrite (asyncResult);
695                 }
696
697                 public override void Close()
698                 {
699                         ((IDisposable)this).Dispose();
700                 }
701
702                 public override void Flush()
703                 {
704                         this.checkDisposed();
705
706                         this.innerStream.Flush();
707                 }
708
709                 public int Read(byte[] buffer)
710                 {
711                         return this.Read(buffer, 0, buffer.Length);
712                 }
713
714                 public override int Read(byte[] buffer, int offset, int count)
715                 {
716                         IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
717
718                         return this.EndRead(res);
719                 }
720
721                 public override long Seek(long offset, SeekOrigin origin)
722                 {
723                         throw new NotSupportedException();
724                 }
725                 
726                 public override void SetLength(long value)
727                 {
728                         throw new NotSupportedException();
729                 }
730
731                 public void Write(byte[] buffer)
732                 {
733                         this.Write(buffer, 0, buffer.Length);
734                 }
735
736                 public override void Write(byte[] buffer, int offset, int count)
737                 {
738                         IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
739
740                         this.EndWrite(res);
741                 }
742
743                 #endregion
744
745                 #region Misc Methods
746
747                 private void resetBuffer()
748                 {
749                         this.inputBuffer.SetLength(0);
750                         this.inputBuffer.Position = 0;
751                 }
752
753                 private void checkDisposed()
754                 {
755                         if (this.disposed)
756                         {
757                                 throw new ObjectDisposedException("The SslClientStream is closed.");
758                         }
759                 }
760
761                 #endregion
762
763                 #region Handsake Methods
764
765                 /*
766                         Client                                                                                  Server
767
768                         ClientHello                 -------->
769                                                                                                                         ServerHello
770                                                                                                                         Certificate*
771                                                                                                                         ServerKeyExchange*
772                                                                                                                         CertificateRequest*
773                                                                                 <--------                       ServerHelloDone
774                         Certificate*
775                         ClientKeyExchange
776                         CertificateVerify*
777                         [ChangeCipherSpec]
778                         Finished                    -------->
779                                                                                                                         [ChangeCipherSpec]
780                                                                                 <--------           Finished
781                         Application Data            <------->                   Application Data
782
783                                         Fig. 1 - Message flow for a full handshake              
784                 */
785
786                 internal void NegotiateHandshake()
787                 {
788                         try
789                         {
790                                 if (this.context.HandshakeState != HandshakeState.None)
791                                 {
792                                         this.context.Clear();
793                                 }
794
795                                 // Obtain supported cipher suites
796                                 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
797
798                                 // Send client hello
799                                 this.protocol.SendRecord(HandshakeType.ClientHello);
800
801                                 // Read server response
802                                 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
803                                 {
804                                         // Read next record\r
805                                         this.protocol.ReceiveRecord (this.innerStream);
806                                 }
807
808                                 // Send client certificate if requested
809                                 if (this.context.ServerSettings.CertificateRequest)
810                                 {
811                                         this.protocol.SendRecord(HandshakeType.Certificate);
812                                 }
813
814                                 // Send Client Key Exchange
815                                 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
816
817                                 // Now initialize session cipher with the generated keys
818                                 this.context.Cipher.InitializeCipher();
819
820                                 // Send certificate verify if requested
821                                 if (this.context.ServerSettings.CertificateRequest)
822                                 {
823                                         this.protocol.SendRecord(HandshakeType.CertificateVerify);
824                                 }
825
826                                 // Send Cipher Spec protocol
827                                 this.protocol.SendChangeCipherSpec();                   
828                         
829                                 // Read record until server finished is received
830                                 while (this.context.HandshakeState != HandshakeState.Finished)
831                                 {
832                                         // If all goes well this will process messages:
833                                         //              Change Cipher Spec
834                                         //              Server finished\r
835                                         this.protocol.ReceiveRecord (this.innerStream);
836                                 }
837
838                                 // Clear Key Info
839                                 this.context.ClearKeyInfo();
840                         }
841                         catch (TlsException ex)
842                         {
843                                 this.protocol.SendAlert(ex.Alert);
844                                 this.Close();
845
846                                 throw new IOException("The authentication or decryption has failed.");
847                         }
848                         catch (Exception)
849                         {
850                                 this.protocol.SendAlert(AlertDescription.InternalError);
851                                 this.Close();
852
853                                 throw new IOException("The authentication or decryption has failed.");
854                         }
855                 }
856
857                 #endregion
858
859                 #region Event Methods
860
861                 internal virtual bool RaiseServerCertificateValidation(
862                         X509Certificate certificate, 
863                         int[]                   certificateErrors)
864                 {
865                         if (this.ServerCertValidation != null)
866                         {
867                                 return this.ServerCertValidation(certificate, certificateErrors);
868                         }
869
870                         return (certificateErrors != null && certificateErrors.Length == 0);
871                 }
872
873                 internal X509Certificate RaiseClientCertificateSelection(
874                         X509CertificateCollection       clientCertificates, 
875                         X509Certificate                         serverCertificate, 
876                         string                                          targetHost, 
877                         X509CertificateCollection       serverRequestedCertificates)
878                 {
879                         if (this.ClientCertSelection != null)
880                         {
881                                 return this.ClientCertSelection(
882                                         clientCertificates,
883                                         serverCertificate,
884                                         targetHost,
885                                         serverRequestedCertificates);
886                         }
887
888                         return null;
889                 }
890
891                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
892                         X509Certificate certificate, 
893                         string                  targetHost)
894                 {
895                         if (this.PrivateKeySelection != null)
896                         {
897                                 return this.PrivateKeySelection(certificate, targetHost);
898                         }
899
900                         return null;
901                 }
902
903                 #endregion
904         }
905 }