2005-06-14 Sebastien Pouliot <sebastien@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                 {
513                         if (this.disposed) {
514                                 recordEvent.Set ();\r
515                                 return;
516                         }\r
517
518                         byte[] recbuf = (byte[])result.AsyncState;\r
519                         int n = innerStream.EndRead (result);
520                         if (n > 0)\r
521                         {\r
522                                 // add the just received data to the waiting data\r
523                                 recordStream.Write (recbuf, 0, n);\r
524                         }\r
525 \r
526                         bool dataToReturn = false;\r
527                         long pos = recordStream.Position;
528 \r
529                         recordStream.Position = 0;\r
530                         byte[] record = null;
531
532                         // don't try to decode record unless we have at least 5 bytes\r
533                         // i.e. type (1), protocol (2) and length (2)\r
534                         if (recordStream.Length >= 5)\r
535                         {\r
536                                 record = this.protocol.ReceiveRecord (recordStream);\r
537                         }\r
538
539                         // a record of 0 length is valid (and there may be more record after it)
540                         while (record != null)\r
541                         {\r
542                                 // we probably received more stuff after the record, and we must keep it!\r
543                                 long remainder = recordStream.Length - recordStream.Position;\r
544                                 byte[] outofrecord = null;\r
545                                 if (remainder > 0)\r
546                                 {\r
547                                         outofrecord = new byte[remainder];\r
548                                         recordStream.Read (outofrecord, 0, outofrecord.Length);\r
549                                 }\r
550 \r
551                                 long position = this.inputBuffer.Position;\r
552
553                                 if (record.Length > 0)
554                                 {\r
555                                         // Write new data to the inputBuffer\r
556                                         this.inputBuffer.Seek (0, SeekOrigin.End);\r
557                                         this.inputBuffer.Write (record, 0, record.Length);\r
558 \r
559                                         // Restore buffer position\r
560                                         this.inputBuffer.Seek (position, SeekOrigin.Begin);
561                                         dataToReturn = true;
562                                 }\r
563 \r
564                                 recordStream.SetLength (0);\r
565                                 record = null;\r
566 \r
567                                 if (remainder > 0)\r
568                                 {\r
569                                         recordStream.Write (outofrecord, 0, outofrecord.Length);\r
570                                         // type (1), protocol (2) and length (2)\r
571                                         if (recordStream.Length >= 5)\r
572                                         {\r
573                                                 // try to see if another record is available\r
574                                                 recordStream.Position = 0;\r
575                                                 record = this.protocol.ReceiveRecord (recordStream);\r
576                                                 if (record == null)\r
577                                                         pos = recordStream.Length;\r
578                                         }\r
579                                         else\r
580                                                 pos = remainder;\r
581                                 }\r
582                                 else\r
583                                         pos = 0;\r
584                         }\r
585
586                         if (!dataToReturn && (n > 0))\r
587                         {
588                                 // there is no record to return to caller and (possibly) more data waiting\r
589                                 // so continue reading from network (and appending to stream)\r
590                                 recordStream.Position = recordStream.Length;\r
591                                 this.innerStream.BeginRead (recbuf, 0, recbuf.Length, 
592                                         new AsyncCallback (NetworkReadCallback), recbuf);\r
593                         }\r
594                         else\r
595                         {
596                                 // we have record(s) to return -or- no more available to read from network
597                                 // reset position for further reading\r
598                                 recordStream.Position = pos;\r
599                                 recordEvent.Set ();\r
600                         }\r
601                 }
602
603                 public override IAsyncResult BeginWrite(
604                         byte[]                  buffer,
605                         int                             offset,
606                         int                             count,
607                         AsyncCallback   callback,
608                         object                  state)
609                 {
610                         this.checkDisposed();
611
612                         if (buffer == null)
613                         {
614                                 throw new ArgumentNullException("buffer is a null reference.");
615                         }
616                         if (offset < 0)
617                         {
618                                 throw new ArgumentOutOfRangeException("offset is less than 0.");
619                         }
620                         if (offset > buffer.Length)
621                         {
622                                 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
623                         }
624                         if (count < 0)
625                         {
626                                 throw new ArgumentOutOfRangeException("count is less than 0.");
627                         }
628                         if (count > (buffer.Length - offset))
629                         {
630                                 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
631                         }
632
633                         if (this.context.HandshakeState == HandshakeState.None)
634                         {
635                                 lock (this.negotiate)
636                                 {
637                                         if (this.context.HandshakeState == HandshakeState.None)
638                                         {
639                                                 this.NegotiateHandshake();
640                                         }
641                                 }
642                         }
643
644                         IAsyncResult asyncResult;
645
646                         lock (this.write)
647                         {
648                                 try
649                                 {
650                                         // Send the buffer as a TLS record
651                                         
652                                         byte[] record = this.protocol.EncodeRecord(
653                                                 ContentType.ApplicationData, buffer, offset, count);
654                                 
655                                         asyncResult = this.innerStream.BeginWrite(
656                                                 record, 0, record.Length, callback, state);
657                                 }
658                                 catch (TlsException ex)
659                                 {
660                                         this.protocol.SendAlert(ex.Alert);
661                                         this.Close();
662
663                                         throw new IOException("The authentication or decryption has failed.");
664                                 }
665                                 catch (Exception)
666                                 {
667                                         throw new IOException("IO exception during Write.");
668                                 }
669                         }
670
671                         return asyncResult;
672                 }
673
674                 public override int EndRead(IAsyncResult asyncResult)
675                 {
676                         this.checkDisposed();
677
678                         if (asyncResult == null)
679                         {
680                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
681                         }\r
682
683                         recordEvent.Reset ();
684                         return this.rd.EndInvoke (asyncResult);
685                 }
686
687                 public override void EndWrite(IAsyncResult asyncResult)
688                 {
689                         this.checkDisposed();
690
691                         if (asyncResult == null)
692                         {
693                                 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
694                         }
695
696                         this.innerStream.EndWrite (asyncResult);
697                 }
698
699                 public override void Close()
700                 {
701                         ((IDisposable)this).Dispose();
702                 }
703
704                 public override void Flush()
705                 {
706                         this.checkDisposed();
707
708                         this.innerStream.Flush();
709                 }
710
711                 public int Read(byte[] buffer)
712                 {
713                         return this.Read(buffer, 0, buffer.Length);
714                 }
715
716                 public override int Read(byte[] buffer, int offset, int count)
717                 {
718                         IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
719
720                         return this.EndRead(res);
721                 }
722
723                 public override long Seek(long offset, SeekOrigin origin)
724                 {
725                         throw new NotSupportedException();
726                 }
727                 
728                 public override void SetLength(long value)
729                 {
730                         throw new NotSupportedException();
731                 }
732
733                 public void Write(byte[] buffer)
734                 {
735                         this.Write(buffer, 0, buffer.Length);
736                 }
737
738                 public override void Write(byte[] buffer, int offset, int count)
739                 {
740                         IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
741
742                         this.EndWrite(res);
743                 }
744
745                 #endregion
746
747                 #region Misc Methods
748
749                 private void resetBuffer()
750                 {
751                         this.inputBuffer.SetLength(0);
752                         this.inputBuffer.Position = 0;
753                 }
754
755                 private void checkDisposed()
756                 {
757                         if (this.disposed)
758                         {
759                                 throw new ObjectDisposedException("The SslClientStream is closed.");
760                         }
761                 }
762
763                 #endregion
764
765                 #region Handsake Methods
766
767                 /*
768                         Client                                                                                  Server
769
770                         ClientHello                 -------->
771                                                                                                                         ServerHello
772                                                                                                                         Certificate*
773                                                                                                                         ServerKeyExchange*
774                                                                                                                         CertificateRequest*
775                                                                                 <--------                       ServerHelloDone
776                         Certificate*
777                         ClientKeyExchange
778                         CertificateVerify*
779                         [ChangeCipherSpec]
780                         Finished                    -------->
781                                                                                                                         [ChangeCipherSpec]
782                                                                                 <--------           Finished
783                         Application Data            <------->                   Application Data
784
785                                         Fig. 1 - Message flow for a full handshake              
786                 */
787
788                 internal void NegotiateHandshake()
789                 {
790                         try
791                         {
792                                 if (this.context.HandshakeState != HandshakeState.None)
793                                 {
794                                         this.context.Clear();
795                                 }
796
797                                 // Obtain supported cipher suites
798                                 this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
799
800                                 // Send client hello
801                                 this.protocol.SendRecord(HandshakeType.ClientHello);
802
803                                 // Read server response
804                                 while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
805                                 {
806                                         // Read next record\r
807                                         this.protocol.ReceiveRecord (this.innerStream);
808                                 }
809
810                                 // Send client certificate if requested
811                                 // even if the server ask for it it _may_ still be optional
812                                 bool clientCertificate = this.context.ServerSettings.CertificateRequest;
813
814                                 // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
815                                 // the current design doesn't allow a very cute way to handle 
816                                 // SSL3 alert warning for NoCertificate (41).
817                                 if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
818                                 {
819                                         clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
820                                                 (this.context.ClientSettings.Certificates.Count > 0));
821                                         // this works well with OpenSSL (but only for SSL3)
822                                 }
823
824                                 if (clientCertificate)
825                                 {
826                                         this.protocol.SendRecord(HandshakeType.Certificate);
827                                 }
828
829                                 // Send Client Key Exchange
830                                 this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
831
832                                 // Now initialize session cipher with the generated keys
833                                 this.context.Cipher.InitializeCipher();
834
835                                 // Send certificate verify if requested (optional)
836                                 if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
837                                 {
838                                         this.protocol.SendRecord(HandshakeType.CertificateVerify);
839                                 }
840
841                                 // Send Cipher Spec protocol
842                                 this.protocol.SendChangeCipherSpec();                   
843                         
844                                 // Read record until server finished is received
845                                 while (this.context.HandshakeState != HandshakeState.Finished)
846                                 {
847                                         // If all goes well this will process messages:
848                                         //              Change Cipher Spec
849                                         //              Server finished\r
850                                         this.protocol.ReceiveRecord (this.innerStream);
851                                 }
852
853                                 // Clear Key Info
854                                 this.context.ClearKeyInfo();
855                         }
856                         catch (TlsException ex)
857                         {
858                                 this.protocol.SendAlert(ex.Alert);
859                                 this.Close();
860
861                                 throw new IOException("The authentication or decryption has failed.");
862                         }
863                         catch (Exception)
864                         {
865                                 this.protocol.SendAlert(AlertDescription.InternalError);
866                                 this.Close();
867
868                                 throw new IOException("The authentication or decryption has failed.");
869                         }
870                 }
871
872                 #endregion
873
874                 #region Event Methods
875
876                 internal virtual bool RaiseServerCertificateValidation(
877                         X509Certificate certificate, 
878                         int[]                   certificateErrors)
879                 {
880                         if (this.ServerCertValidation != null)
881                         {
882                                 return this.ServerCertValidation(certificate, certificateErrors);
883                         }
884
885                         return (certificateErrors != null && certificateErrors.Length == 0);
886                 }
887
888                 internal X509Certificate RaiseClientCertificateSelection(
889                         X509CertificateCollection       clientCertificates, 
890                         X509Certificate                         serverCertificate, 
891                         string                                          targetHost, 
892                         X509CertificateCollection       serverRequestedCertificates)
893                 {
894                         if (this.ClientCertSelection != null)
895                         {
896                                 return this.ClientCertSelection(
897                                         clientCertificates,
898                                         serverCertificate,
899                                         targetHost,
900                                         serverRequestedCertificates);
901                         }
902
903                         return null;
904                 }
905
906                 internal AsymmetricAlgorithm RaisePrivateKeySelection(
907                         X509Certificate certificate, 
908                         string                  targetHost)
909                 {
910                         if (this.PrivateKeySelection != null)
911                         {
912                                 return this.PrivateKeySelection(certificate, targetHost);
913                         }
914
915                         return null;
916                 }
917
918                 #endregion
919         }
920 }