New test.
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / SslClientStream.cs
index b2f4acaeb6f179dd230154ca3251fd00eec2c66e..03d37d79ba2474339461c06d6b2a0acac31c26f8 100644 (file)
@@ -1,26 +1,26 @@
-/* Transport Security Layer (TLS)
- * Copyright (c) 2003-2004 Carlos Guzman Alvarez
- * 
- * Permission is hereby granted, free of charge, to any person 
- * obtaining a copy of this software and associated documentation 
- * files (the "Software"), to deal in the Software without restriction, 
- * including without limitation the rights to use, copy, modify, merge, 
- * publish, distribute, sublicense, and/or sell copies of the Software, 
- * and to permit persons to whom the Software is furnished to do so, 
- * subject to the following conditions:
- * 
- * The above copyright notice and this permission notice shall be included 
- * in all copies or substantial portions of the Software.
- * 
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
- * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
- * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
- * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 
- * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 
- * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
- * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 
- * DEALINGS IN THE SOFTWARE.
- */
+// Transport Security Layer (TLS)
+// Copyright (c) 2003-2004 Carlos Guzman Alvarez
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
 
 using System;
 using System.Collections;
@@ -31,9 +31,7 @@ using System.Security.Cryptography;
 using System.Security.Cryptography.X509Certificates;
 using System.Threading;
 
-using Mono.Security.Protocol.Tls.Alerts;
 using Mono.Security.Protocol.Tls.Handshake;
-using Mono.Security.Protocol.Tls.Handshake.Client;
 
 namespace Mono.Security.Protocol.Tls
 {
@@ -42,27 +40,21 @@ namespace Mono.Security.Protocol.Tls
        public delegate bool CertificateValidationCallback(
                X509Certificate certificate, 
                int[]                   certificateErrors);
-       
+
        public delegate X509Certificate CertificateSelectionCallback(
                X509CertificateCollection       clientCertificates, 
                X509Certificate                         serverCertificate, 
                string                                          targetHost, 
                X509CertificateCollection       serverRequestedCertificates);
-       
+
        public delegate AsymmetricAlgorithm PrivateKeySelectionCallback(
-               X509Certificate clientCertificate, 
+               X509Certificate certificate, 
                string                  targetHost);
 
        #endregion
 
-       public class SslClientStream : Stream, IDisposable
+       public class SslClientStream : SslStreamBase
        {
-               #region Events
-
-               public event TlsWarningAlertEventHandler WarningAlert;
-
-               #endregion
-
                #region Internal Events
                
                internal event CertificateValidationCallback    ServerCertValidation;
@@ -71,215 +63,46 @@ namespace Mono.Security.Protocol.Tls
                
                #endregion
 
-               #region Fields
-
-               private CertificateValidationCallback   serverCertValidationDelegate;
-               private CertificateSelectionCallback    clientCertSelectionDelegate;
-               private PrivateKeySelectionCallback             privateKeySelectionDelegate;
-               private Stream                                                  innerStream;
-               private BufferedStream                                  inputBuffer;
-               private TlsContext                                              context;
-               private bool                                                    ownsStream;
-               private bool                                                    disposed;
-               private string                                                  read;
-               private string                                                  write;
-
-               #endregion
-
                #region Properties
 
-               public override bool CanRead
-               {
-                       get { return this.innerStream.CanRead; }
-               }
-
-               public override bool CanSeek
-               {
-                       get { return false; }
-               }
-
-               public override bool CanWrite
-               {
-                       get { return this.innerStream.CanWrite; }
-               }
-
-               public override long Length
-               {
-                       get { throw new NotSupportedException(); }
-               }
-
-               public override long Position
-               {
-                       get { throw new NotSupportedException(); }
-                       set { throw new NotSupportedException(); }
-               }
-
-               #endregion
-
-               #region Security Properties
-
-               public bool CheckCertRevocationStatus 
+               // required by HttpsClientStream for proxy support
+               internal Stream InputBuffer 
                {
-                       get { throw new NotImplementedException(); }
-                       set { throw new NotImplementedException(); }
+                       get { return base.inputBuffer; }
                }
 
-               public CipherAlgorithmType CipherAlgorithm 
+               public X509CertificateCollection ClientCertificates
                {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.Cipher.CipherAlgorithmType;
-                               }
-
-                               return CipherAlgorithmType.None;
-                       }
+                       get { return this.context.ClientSettings.Certificates; }
                }
-               
-               public int CipherStrength 
-               {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.Cipher.EffectiveKeyBits;
-                               }
 
-                               return 0;
-                       }
-               }
-               
-               public X509CertificateCollection ClientCertificates 
-               {
-                       get { return this.context.ClientSettings.Certificates;}
-               }
-               
-               public HashAlgorithmType HashAlgorithm 
-               {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.Cipher.HashAlgorithmType; 
-                               }
-
-                               return HashAlgorithmType.None;
-                       }
-               }
-               
-               public int HashStrength
-               {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.Cipher.HashSize * 8; 
-                               }
-
-                               return 0;
-                       }
-               }
-               
-               public int KeyExchangeStrength 
-               {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.ServerSettings.Certificates[0].RSA.KeySize;
-                               }
-
-                               return 0;
-                       }
-               }
-               
-               public ExchangeAlgorithmType KeyExchangeAlgorithm 
-               {
-                       get 
-                       { 
-                               if (this.context.HandshakeFinished)
-                               {
-                                       return this.context.Cipher.ExchangeAlgorithmType; 
-                               }
-
-                               return ExchangeAlgorithmType.None;
-                       }
-               }
-               
-               public SecurityProtocolType SecurityProtocol 
-               {
-                       get { return this.context.SecurityProtocol; }
-               }
-               
-               public X509Certificate SelectedClientCertificate 
+               public X509Certificate SelectedClientCertificate
                {
                        get { return this.context.ClientSettings.ClientCertificate; }
                }
 
-               public X509Certificate ServerCertificate 
-               {
-                       get 
-                       { 
-                               if (!this.context.HandshakeFinished)
-                               {
-                                       if (this.context.ServerSettings.Certificates != null &&
-                                               this.context.ServerSettings.Certificates.Count > 0)
-                                       {
-                                               return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
-                                       }
-                               }
-
-                               return null;
-                       }
-               } 
-
                #endregion
 
                #region Callback Properties
 
                public CertificateValidationCallback ServerCertValidationDelegate
                {
-                       get { return this.serverCertValidationDelegate; }
-                       set 
-                       { 
-                               if (this.ServerCertValidation != null)
-                               {
-                                       this.ServerCertValidation -= this.serverCertValidationDelegate;
-                               }
-                               this.serverCertValidationDelegate       = value;
-                               this.ServerCertValidation                       += this.serverCertValidationDelegate;
-                       }
+                       get { return this.ServerCertValidation; }
+                       set { this.ServerCertValidation = value; }                      
                }
 
                public CertificateSelectionCallback ClientCertSelectionDelegate 
                {
-                       get { return this.clientCertSelectionDelegate; }
-                       set 
-                       { 
-                               if (this.ClientCertSelection != null)
-                               {
-                                       this.ClientCertSelection -= this.clientCertSelectionDelegate;
-                               }
-                               this.clientCertSelectionDelegate        = value;
-                               this.ClientCertSelection                        += this.clientCertSelectionDelegate;
-                       }
+                       get { return this.ClientCertSelection; }
+                       set { this.ClientCertSelection = value; }
                }
 
-               public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate 
+               public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
                {
-                       get { return this.privateKeySelectionDelegate; }
-                       set 
-                       { 
-                               if (this.PrivateKeySelection != null)
-                               {
-                                       this.PrivateKeySelection -= this.privateKeySelectionDelegate;
-                               }
-                               this.privateKeySelectionDelegate        = value;
-                               this.PrivateKeySelection                        += this.privateKeySelectionDelegate;
-                       }
+                       get { return this.PrivateKeySelection; }
+                       set { this.PrivateKeySelection = value; }
                }
-
+               
                #endregion
 
                #region Constructors
@@ -330,31 +153,21 @@ namespace Mono.Security.Protocol.Tls
                        string                                          targetHost,
                        bool                                            ownsStream,
                        SecurityProtocolType            securityProtocolType,
-                       X509CertificateCollection       clientCertificates)
+                       X509CertificateCollection       clientCertificates):
+                       base(stream, ownsStream)
                {
-                       if (stream == null)
-                       {
-                               throw new ArgumentNullException("stream is null.");
-                       }
-                       if (!stream.CanRead || !stream.CanWrite)
-                       {
-                               throw new ArgumentNullException("stream is not both readable and writable.");
-                       }
                        if (targetHost == null || targetHost.Length == 0)
                        {
                                throw new ArgumentNullException("targetHost is null or an empty string.");
                        }
 
-                       this.context            = new TlsContext(
+                       this.context = new ClientContext(
                                this,
                                securityProtocolType, 
                                targetHost, 
                                clientCertificates);
-                       this.inputBuffer        = new BufferedStream(new MemoryStream());
-                       this.innerStream        = stream;
-                       this.ownsStream         = ownsStream;
-                       this.read                       = String.Empty;
-                       this.write                      = String.Empty;
+
+                       this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context);
                }
 
                #endregion
@@ -363,818 +176,218 @@ namespace Mono.Security.Protocol.Tls
 
                ~SslClientStream()
                {
-                       this.Dispose(false);
+                       base.Dispose(false);
                }
 
                #endregion
 
-        #region IDisposable Methods
+               #region IDisposable Methods
 
-               void IDisposable.Dispose()
+               protected override void Dispose(bool disposing)
                {
-                       this.Dispose(true);
-                       GC.SuppressFinalize(this);
-               }
+                       base.Dispose(disposing);
 
-               protected virtual void Dispose(bool disposing)
-               {
-                       if (!this.disposed)
+                       if (disposing)
                        {
-                               if (disposing)
-                               {
-                                       if (this.innerStream != null)
-                                       {
-                                               if (this.context.HandshakeFinished)
-                                               {
-                                                       // Write close notify
-                                                       TlsCloseNotifyAlert alert = new TlsCloseNotifyAlert(this.context);
-                                                       this.SendAlert(alert);
-                                               }
-
-                                               if (this.ownsStream)
-                                               {
-                                                       // Close inner stream
-                                                       this.innerStream.Close();
-                                               }
-                                       }
-                                       this.ownsStream         = false;
-                                       this.innerStream        = null;
-                                       if (this.ClientCertSelection != null)
-                                       {
-                                               this.ClientCertSelection -= this.clientCertSelectionDelegate;
-                                       }
-                                       if (this.ServerCertValidation != null)
-                                       {
-                                               this.ServerCertValidation -= this.serverCertValidationDelegate;
-                                       }
-                                       this.serverCertValidationDelegate       = null;
-                                       this.clientCertSelectionDelegate        = null;
-                               }
-
-                               this.disposed = true;
+                               this.ServerCertValidation = null;
+                               this.ClientCertSelection = null;
+                               this.PrivateKeySelection = null;
                        }
                }
 
                #endregion
 
-               #region Methods
-
-               public override IAsyncResult BeginRead(
-                       byte[]                  buffer,
-                       int                             offset,
-                       int                             count,
-                       AsyncCallback   callback,
-                       object                  state)
-               {
-                       this.checkDisposed();
-                       
-                       if (buffer == null)
-                       {
-                               throw new ArgumentNullException("buffer is a null reference.");
-                       }
-                       if (offset < 0)
-                       {
-                               throw new ArgumentOutOfRangeException("offset is less than 0.");
-                       }
-                       if (offset > buffer.Length)
-                       {
-                               throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
-                       }
-                       if (count < 0)
-                       {
-                               throw new ArgumentOutOfRangeException("count is less than 0.");
-                       }
-                       if (count > (buffer.Length - offset))
-                       {
-                               throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
-                       }
+               #region Handshake Methods
 
-                       if (!this.context.HandshakeFinished)
-                       {
-                               this.doHandshake();     // Handshake negotiation
-                       }
+               /*
+                       Client                                                                                  Server
 
-                       if (!Monitor.TryEnter(this.read))
-                       {
-                               throw new InvalidOperationException("A read operation is already in progress.");
-                       }
+                       ClientHello                 -------->
+                                                                                                                       ServerHello
+                                                                                                                       Certificate*
+                                                                                                                       ServerKeyExchange*
+                                                                                                                       CertificateRequest*
+                                                                               <--------                       ServerHelloDone
+                       Certificate*
+                       ClientKeyExchange
+                       CertificateVerify*
+                       [ChangeCipherSpec]
+                       Finished                    -------->
+                                                                                                                       [ChangeCipherSpec]
+                                                                               <--------           Finished
+                       Application Data            <------->                   Application Data
 
-                       IAsyncResult asyncResult;
+                                       Fig. 1 - Message flow for a full handshake              
+               */
 
+               internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state)
+               {
                        try
                        {
-                               System.Threading.Monitor.Enter(this.read);
-
-                               // If actual buffer is full readed reset it
-                               if (this.inputBuffer.Position == this.inputBuffer.Length &&
-                                       this.inputBuffer.Length > 0)
+                               if (this.context.HandshakeState != HandshakeState.None)
                                {
-                                       this.resetBuffer();
+                                       this.context.Clear();
                                }
 
-                               // Check if we have space in the middle buffer
-                               // if not Read next TLS record and update the inputBuffer
-                               while ((this.inputBuffer.Length - this.inputBuffer.Position) < count)
-                               {
-                                       // Read next record and write it into the inputBuffer
-                                       long    position        = this.inputBuffer.Position;                                    
-                                       byte[]  record          = this.receiveRecord();
-                                       
-                                       if (record != null && record.Length > 0)
-                                       {
-                                               // Write new data to the inputBuffer
-                                               this.inputBuffer.Seek(0, SeekOrigin.End);
-                                               this.inputBuffer.Write(record, 0, record.Length);
-
-                                               // Restore buffer position
-                                               this.inputBuffer.Seek(position, SeekOrigin.Begin);
-                                       }
-                                       else
-                                       {
-                                               if (record == null)
-                                               {
-                                                       break;
-                                               }
-                                       }
-
-                                       // TODO: Review if we need to check the Length
-                                       // property of the innerStream for other types
-                                       // of streams, to check that there are data available
-                                       // for read
-                                       if (this.innerStream is NetworkStream &&
-                                               !((NetworkStream)this.innerStream).DataAvailable)
-                                       {
-                                               break;
-                                       }
-                               }
+                               // Obtain supported cipher suites
+                               this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
 
-                               asyncResult = this.inputBuffer.BeginRead(
-                                       buffer, offset, count, callback, state);
-                       }
-                       catch (TlsException ex)
-                       {
-                               throw new IOException("The authentication or decryption has failed.", ex);
-                       }
-                       catch (Exception ex)
-                       {
-                               throw new IOException("IO exception during read.", ex);
-                       }
-                       finally
-                       {
-                               System.Threading.Monitor.Exit(this.read);
-                       }
+                               // Set handshake state
+                               this.context.HandshakeState = HandshakeState.Started;
 
-                       return asyncResult;
-               }
-
-               public override IAsyncResult BeginWrite(
-                       byte[]                  buffer,
-                       int                             offset,
-                       int                             count,
-                       AsyncCallback   callback,
-                       object                  state)
-               {
-                       this.checkDisposed();
-
-                       if (buffer == null)
-                       {
-                               throw new ArgumentNullException("buffer is a null reference.");
-                       }
-                       if (offset < 0)
-                       {
-                               throw new ArgumentOutOfRangeException("offset is less than 0.");
-                       }
-                       if (offset > buffer.Length)
-                       {
-                               throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
-                       }
-                       if (count < 0)
-                       {
-                               throw new ArgumentOutOfRangeException("count is less than 0.");
-                       }
-                       if (count > (buffer.Length - offset))
-                       {
-                               throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
-                       }
-
-                       if (!this.context.HandshakeFinished)
-                       {
-                               // Start handshake negotiation
-                               this.doHandshake();
-                       }
-
-                       if (!Monitor.TryEnter(this.write))
-                       {
-                               throw new InvalidOperationException("A write operation is already in progress.");
-                       }
-
-                       IAsyncResult asyncResult;
-
-                       try
-                       {
-                               Monitor.Enter(this.write);
-
-                               // Send the buffer as a TLS record
-                               byte[] record = this.encodeRecord(
-                                       TlsContentType.ApplicationData, buffer, offset, count);
-                               
-                               asyncResult = this.innerStream.BeginWrite(
-                                       record, 0, record.Length, callback, state);
+                               // Send client hello
+                               return this.protocol.BeginSendRecord(HandshakeType.ClientHello, callback, state);
                        }
                        catch (TlsException ex)
                        {
+                               this.protocol.SendAlert(ex.Alert);
+
                                throw new IOException("The authentication or decryption has failed.", ex);
                        }
                        catch (Exception ex)
                        {
-                               throw new IOException("IO exception during Write.", ex);
-                       }
-                       finally
-                       {
-                               Monitor.Exit(this.write);
-                       }                       
+                               this.protocol.SendAlert(AlertDescription.InternalError);
 
-                       return asyncResult;
-               }
-
-               public override int EndRead(IAsyncResult asyncResult)
-               {
-                       this.checkDisposed();
-
-                       if (asyncResult == null)
-                       {
-                               throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
+                               throw new IOException("The authentication or decryption has failed.", ex);
                        }
-
-                       return this.inputBuffer.EndRead(asyncResult);
                }
 
-               public override void EndWrite(IAsyncResult asyncResult)
+               private void SafeReceiveRecord (Stream s)
                {
-                       this.checkDisposed();
-
-                       if (asyncResult == null)
-                       {
-                               throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
+                       byte[] record = this.protocol.ReceiveRecord (s);
+                       if ((record == null) || (record.Length == 0)) {
+                               throw new TlsException (
+                                       AlertDescription.HandshakeFailiure,
+                                       "The server stopped the handshake.");
                        }
-
-                       this.innerStream.EndWrite (asyncResult);
-               }
-
-               public override void Close()
-               {
-                       ((IDisposable)this).Dispose();
                }
 
-               public override void Flush()
+               internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult)
                {
-                       this.checkDisposed();
+                       this.protocol.EndSendRecord(asyncResult);
 
-                       this.innerStream.Flush();
-               }
-
-               public int Read(byte[] buffer)
-               {
-                       return this.Read(buffer, 0, buffer.Length);
-               }
-
-               public override int Read(byte[] buffer, int offset, int count)
-               {
-                       IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
-
-                       return this.EndRead(res);
-               }
-
-               public override long Seek(long offset, SeekOrigin origin)
-               {
-                       throw new NotSupportedException();
-               }
-               
-               public override void SetLength(long value)
-               {
-                       throw new NotSupportedException();
-               }
-
-               public void Write(byte[] buffer)
-               {
-                       this.Write(buffer, 0, buffer.Length);
-               }
-
-               public override void Write(byte[] buffer, int offset, int count)
-               {
-                       IAsyncResult res = this.BeginWrite (buffer, offset, count, null, null);
-
-                       this.EndWrite(res);
-               }
-
-               #endregion
-
-               #region Reveive Record Methods
-
-               private byte[] receiveRecord()
-               {
-                       if (this.context.ConnectionEnd)
+                       // Read server response
+                       while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone) 
                        {
-                               throw this.context.CreateException("The session is finished and it's no longer valid.");
-                       }
-                       
-                       // Try to read the Record Content Type
-                       int type = this.innerStream.ReadByte();
+                               // Read next record
+                               SafeReceiveRecord (this.innerStream);
 
-                       // There are no more data for read
-                       if (type == -1)
-                       {
-                               return null;
+                               // special case for abbreviated handshake where no ServerHelloDone is sent from the server
+                               if (this.context.AbbreviatedHandshake && (this.context.LastHandshakeMsg == HandshakeType.ServerHello))
+                                       break;
                        }
 
-                       TlsContentType  contentType     = (TlsContentType)type;
-                       short                   protocol        = this.ReadShort();
-                       short                   length          = this.ReadShort();
-                       
-                       // Read Record data
-                       int             received        = 0;
-                       byte[]  buffer          = new byte[length];
-                       while (received != length)
+                       // the handshake is much easier if we can reuse a previous session settings
+                       if (this.context.AbbreviatedHandshake) 
                        {
-                               received += this.innerStream.Read(
-                                       buffer, received, buffer.Length - received);
-                       }
+                               ClientSessionCache.SetContextFromCache (this.context);
+                               this.context.Negotiating.Cipher.ComputeKeys ();
+                               this.context.Negotiating.Cipher.InitializeCipher ();
 
-                       TlsStream message = new TlsStream(buffer);
-               
-                       // Check that the message has a valid protocol version
-                       if (protocol != this.context.Protocol)
-                       {
-                               throw this.context.CreateException("Invalid protocol version on message received from server");
-                       }
+                               // Send Cipher Spec protocol
+                               this.protocol.SendChangeCipherSpec ();
 
-                       // Decrypt message contents if needed
-                       if (contentType == TlsContentType.Alert && length == 2)
-                       {
-                       }
-                       else
-                       {
-                               if (this.context.IsActual &&
-                                       contentType != TlsContentType.ChangeCipherSpec)
+                               // Read record until server finished is received
+                               while (this.context.HandshakeState != HandshakeState.Finished) 
                                {
-                                       message = this.decryptRecordFragment(
-                                               contentType, 
-                                               message.ToArray());
+                                       // If all goes well this will process messages:
+                                       //              Change Cipher Spec
+                                       //              Server finished
+                                       SafeReceiveRecord (this.innerStream);
                                }
-                       }
-
-                       byte[] result = message.ToArray();
-
-                       // Process record
-                       switch (contentType)
-                       {
-                               case TlsContentType.Alert:
-                                       this.processAlert((TlsAlertLevel)message.ReadByte(),
-                                               (TlsAlertDescription)message.ReadByte());
-                                       break;
-
-                               case TlsContentType.ChangeCipherSpec:
-                                       // Reset sequence numbers
-                                       this.context.ReadSequenceNumber = 0;
-                                       break;
-
-                               case TlsContentType.ApplicationData:
-                                       break;
-
-                               case TlsContentType.Handshake:
-                                       while (!message.EOF)
-                                       {
-                                               this.processHandshakeMessage(message);
-                                       }
-                                       // Update handshakes of current messages
-                                       this.context.HandshakeMessages.Write(message.ToArray());
-                                       break;
-
-                               default:
-                                       throw this.context.CreateException("Unknown record received from server.");
-                       }
-
-                       return result;
-               }
-
-               #endregion
-
-               #region Send Record Methods
-
-               internal void SendAlert(TlsAlert alert)
-               {                       
-                       // Write record
-                       this.sendRecord(TlsContentType.Alert, alert.ToArray());
-
-                       // Update session
-                       alert.Update();
-
-                       // Reset message contents
-                       alert.Reset();
-               }
-
-               private void sendChangeCipherSpec()
-               {
-                       // Send Change Cipher Spec message
-                       this.sendRecord(TlsContentType.ChangeCipherSpec, new byte[] {1});
-
-                       // Reset sequence numbers
-                       this.context.WriteSequenceNumber = 0;
-
-                       // Make the pending state to be the current state
-                       this.context.IsActual = true;
-
-                       // Send Finished message
-                       this.sendRecord(TlsHandshakeType.Finished);                     
-               }
 
-               private void sendRecord(TlsHandshakeType type)
-               {
-                       TlsHandshakeMessage msg = this.createClientHandshakeMessage(type);
-                       
-                       // Write record
-                       this.sendRecord(msg.ContentType, msg.EncodeMessage());
-
-                       // Update session
-                       msg.Update();
-
-                       // Reset message contents
-                       msg.Reset();
-               }
-
-               private void sendRecord(TlsContentType contentType, byte[] recordData)
-               {
-                       if (this.context.ConnectionEnd)
-                       {
-                               throw this.context.CreateException("The session is finished and it's no longer valid.");
+                               // Send Finished message
+                               this.protocol.SendRecord (HandshakeType.Finished);
                        }
-
-                       byte[] record = this.encodeRecord(contentType, recordData);
-
-                       this.innerStream.Write(record, 0, record.Length);
-               }
-
-               private byte[] encodeRecord(TlsContentType contentType, byte[] recordData)
-               {
-                       return this.encodeRecord(
-                               contentType,
-                               recordData,
-                               0,
-                               recordData.Length);
-               }
-
-               private byte[] encodeRecord(
-                       TlsContentType  contentType, 
-                       byte[]                  recordData,
-                       int                             offset,
-                       int                             count)
-               {
-                       if (this.context.ConnectionEnd)
-                       {
-                               throw this.context.CreateException("The session is finished and it's no longer valid.");
-                       }
-
-                       TlsStream record = new TlsStream();
-
-                       int     position = offset;
-
-                       while (position < ( offset + count ))
+                       else
                        {
-                               short   fragmentLength = 0;
-                               byte[]  fragment;
+                               // Send client certificate if requested
+                               // even if the server ask for it it _may_ still be optional
+                               bool clientCertificate = this.context.ServerSettings.CertificateRequest;
 
-                               if ((count - position) > TlsContext.MAX_FRAGMENT_SIZE)
-                               {
-                                       fragmentLength = TlsContext.MAX_FRAGMENT_SIZE;
-                               }
-                               else
+                               // NOTE: sadly SSL3 and TLS1 differs in how they handle this and
+                               // the current design doesn't allow a very cute way to handle 
+                               // SSL3 alert warning for NoCertificate (41).
+                               if (this.context.SecurityProtocol == SecurityProtocolType.Ssl3)
                                {
-                                       fragmentLength = (short)(count - position);
+                                       clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
+                                               (this.context.ClientSettings.Certificates.Count > 0));
+                                       // this works well with OpenSSL (but only for SSL3)
                                }
 
-                               // Fill the fragment data
-                               fragment = new byte[fragmentLength];
-                               Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
-
-                               if (this.context.IsActual)
+                               if (clientCertificate)
                                {
-                                       // Encrypt fragment
-                                       fragment = this.encryptRecordFragment(contentType, fragment);
+                                       this.protocol.SendRecord(HandshakeType.Certificate);
                                }
 
-                               // Write tls message
-                               record.Write((byte)contentType);
-                               record.Write(this.context.Protocol);
-                               record.Write((short)fragment.Length);
-                               record.Write(fragment);
+                               // Send Client Key Exchange
+                               this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
 
-                               // Update buffer position
-                               position += fragmentLength;
-                       }
+                               // Now initialize session cipher with the generated keys
+                               this.context.Negotiating.Cipher.InitializeCipher();
 
-                       return record.ToArray();
-               }
-               
-               #endregion
-
-               #region Cryptography Methods
-
-               private byte[] encryptRecordFragment(
-                       TlsContentType  contentType, 
-                       byte[]                  fragment)
-               {
-                       // Calculate message MAC
-                       byte[] mac      = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
-
-                       // Encrypt the message
-                       byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
-
-                       // Set new IV
-                       if (this.context.Cipher.CipherMode == CipherMode.CBC)
-                       {
-                               byte[] iv = new byte[this.context.Cipher.IvSize];
-                               System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
-                               this.context.Cipher.UpdateClientCipherIV(iv);
-                       }
-
-                       // Update sequence number
-                       this.context.WriteSequenceNumber++;
-
-                       return ecr;
-               }
-
-               private TlsStream decryptRecordFragment(
-                       TlsContentType  contentType, 
-                       byte[]                  fragment)
-               {
-                       byte[]  dcrFragment     = null;
-                       byte[]  dcrMAC          = null;
-
-                       // Decrypt message
-                       this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
-
-                       // Set new IV
-                       if (this.context.Cipher.CipherMode == CipherMode.CBC)
-                       {
-                               byte[] iv = new byte[this.context.Cipher.IvSize];
-                               System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
-                               this.context.Cipher.UpdateServerCipherIV(iv);
-                       }
-                       
-                       // Check MAC code
-                       byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
-
-                       // Check that the mac is correct
-                       if (mac.Length != dcrMAC.Length)
-                       {
-                               throw new TlsException("Invalid MAC received from server.");
-                       }
-                       for (int i = 0; i < mac.Length; i++)
-                       {
-                               if (mac[i] != dcrMAC[i])
+                               // Send certificate verify if requested (optional)
+                               if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
                                {
-                                       throw new TlsException("Invalid MAC received from server.");
+                                       this.protocol.SendRecord(HandshakeType.CertificateVerify);
                                }
-                       }
-
-                       // Update sequence number
-                       this.context.ReadSequenceNumber++;
-
-                       return new TlsStream(dcrFragment);
-               }
-
-               #endregion
-
-               #region Handshake Processing Methods
-
-               private void processHandshakeMessage(TlsStream handMsg)
-               {
-                       TlsHandshakeType        handshakeType   = (TlsHandshakeType)handMsg.ReadByte();
-                       TlsHandshakeMessage     message                 = null;
 
-                       // Read message length
-                       int length = handMsg.ReadInt24();
+                               // Send Cipher Spec protocol
+                               this.protocol.SendChangeCipherSpec ();
 
-                       // Read message data
-                       byte[] data = new byte[length];
-                       handMsg.Read(data, 0, length);
+                               // Send Finished message
+                               this.protocol.SendRecord (HandshakeType.Finished);
 
-                       // Create and process the server message
-                       message = this.createServerHandshakeMessage(handshakeType, data);
-
-                       // Update session
-                       if (message != null)
-                       {
-                               message.Update();
-                       }
-               }
-
-               private void processAlert(
-                       TlsAlertLevel           alertLevel, 
-                       TlsAlertDescription alertDesc)
-               {
-                       switch (alertLevel)
-                       {
-                               case TlsAlertLevel.Fatal:
-                                       throw this.context.CreateException(alertLevel, alertDesc);                                      
-
-                               case TlsAlertLevel.Warning:
-                               default:
-                               switch (alertDesc)
-                               {
-                                       case TlsAlertDescription.CloseNotify:
-                                               this.context.ConnectionEnd = true;
-                                               break;
-
-                                       default:
-                                               this.RaiseWarningAlert(alertLevel, alertDesc);
-                                               break;
+                               // Read record until server finished is received
+                               while (this.context.HandshakeState != HandshakeState.Finished) {
+                                       // If all goes well this will process messages:
+                                       //              Change Cipher Spec
+                                       //              Server finished
+                                       SafeReceiveRecord (this.innerStream);
                                }
-                               break;
                        }
-               }
-
-               #endregion
-
-               #region Misc Methods
-
-               private void resetBuffer()
-               {
-                       this.inputBuffer.SetLength(0);
-                       this.inputBuffer.Position = 0;
-               }
 
-               private short ReadShort()
-               {
-                       byte[] b = new byte[2];
-                       this.innerStream.Read(b, 0, b.Length);
+                       // Reset Handshake messages information
+                       this.context.HandshakeMessages.Reset ();
 
-                       short val = BitConverter.ToInt16(b, 0);
+                       // Clear Key Info
+                       this.context.ClearKeyInfo();
 
-                       return System.Net.IPAddress.HostToNetworkOrder(val);
-               }
-
-               private void checkDisposed()
-               {
-                       if (this.disposed)
-                       {
-                               throw new ObjectDisposedException("The SslClientStream is closed.");
-                       }
                }
 
                #endregion
 
-               #region Handsake Methods
-
-               /*
-                       Client                                                                                  Server
-
-                       ClientHello                 -------->
-                                                                                                                       ServerHello
-                                                                                                                       Certificate*
-                                                                                                                       ServerKeyExchange*
-                                                                                                                       CertificateRequest*
-                                                                               <--------                       ServerHelloDone
-                       Certificate*
-                       ClientKeyExchange
-                       CertificateVerify*
-                       [ChangeCipherSpec]
-                       Finished                    -------->
-                                                                                                                       [ChangeCipherSpec]
-                                                                               <--------           Finished
-                       Application Data            <------->                   Application Data
-
-                                       Fig. 1 - Message flow for a full handshake              
-               */
+               #region Event Methods
 
-               private void doHandshake()
+               internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
                {
-                       // Obtain supported cipher suite collection
-                       this.context.SupportedCiphers = TlsCipherSuiteFactory.GetSupportedCiphers(context.SecurityProtocol);
-
-                       // Send client hello
-                       this.sendRecord(TlsHandshakeType.ClientHello);
-
-                       // Read server response
-                       while (!this.context.HelloDone)
-                       {
-                               // Read next record
-                               this.receiveRecord();
-                       }
-                       
-                       // Send client certificate if requested
-                       if (this.context.ServerSettings.CertificateRequest)
-                       {
-                               this.sendRecord(TlsHandshakeType.Certificate);
-                       }
-
-                       // Send Client Key Exchange
-                       this.sendRecord(TlsHandshakeType.ClientKeyExchange);
-
-                       // Now initialize session cipher with the generated keys
-                       this.context.Cipher.InitializeCipher();
-
-                       // Send certificate verify if requested
-                       if (this.context.ServerSettings.CertificateRequest)
-                       {
-                               this.sendRecord(TlsHandshakeType.CertificateVerify);
-                       }
-
-                       // Send Cipher Spec protocol
-                       this.sendChangeCipherSpec();                    
-                       
-                       // Read record until server finished is received
-                       while (!this.context.HandshakeFinished)
+                       if (this.ClientCertSelection != null)
                        {
-                               // If all goes well this will process messages:
-                               //              Change Cipher Spec
-                               //              Server finished
-                               this.receiveRecord();
+                               return this.ClientCertSelection(
+                                       clientCertificates,
+                                       serverCertificate,
+                                       targetHost,
+                                       serverRequestedCertificates);
                        }
 
-                       // Clear Key Info
-                       this.context.ClearKeyInfo();
+                       return null;
                }
                
-               private TlsHandshakeMessage createClientHandshakeMessage(TlsHandshakeType type)
+               internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
                {
-                       switch (type)
+                       if (this.ServerCertValidation != null)
                        {
-                               case TlsHandshakeType.ClientHello:
-                                       return new TlsClientHello(this.context);
-
-                               case TlsHandshakeType.Certificate:
-                                       return new TlsClientCertificate(this.context);
-
-                               case TlsHandshakeType.ClientKeyExchange:
-                                       return new TlsClientKeyExchange(this.context);
-
-                               case TlsHandshakeType.CertificateVerify:
-                                       return new TlsClientCertificateVerify(this.context);
-
-                               case TlsHandshakeType.Finished:
-                                       return new TlsClientFinished(this.context);
-
-                               default:
-                                       throw new InvalidOperationException("Unknown client handshake message type: " + type.ToString() );
+                               return this.ServerCertValidation(certificate, errors);
                        }
-               }
-
-               private TlsHandshakeMessage createServerHandshakeMessage(TlsHandshakeType type, byte[] buffer)
-               {
-                       switch (type)
-                       {
-                               case TlsHandshakeType.HelloRequest:
-                                       this.sendRecord(TlsHandshakeType.ClientHello);
-                                       return null;
-
-                               case TlsHandshakeType.ServerHello:
-                                       return new TlsServerHello(this.context, buffer);
-
-                               case TlsHandshakeType.Certificate:
-                                       return new TlsServerCertificate(this.context, buffer);
-
-                               case TlsHandshakeType.ServerKeyExchange:
-                                       return new TlsServerKeyExchange(this.context, buffer);
 
-                               case TlsHandshakeType.CertificateRequest:
-                                       return new TlsServerCertificateRequest(this.context, buffer);
-
-                               case TlsHandshakeType.ServerHelloDone:
-                                       return new TlsServerHelloDone(this.context, buffer);
-
-                               case TlsHandshakeType.Finished:
-                                       return new TlsServerFinished(this.context, buffer);
-
-                               default:
-                                       throw this.context.CreateException("Unknown server handshake message received ({0})", type.ToString());
-                       }
+                       return (errors != null && errors.Length == 0);
                }
 
-               #endregion
-
-               #region Event Methods
-
-               internal void RaiseWarningAlert(
-                       TlsAlertLevel           level, 
-                       TlsAlertDescription description)
-               {
-                       if (WarningAlert != null)
-                       {
-                               WarningAlert(this, new TlsWarningAlertEventArgs(level, description));
-                       }
-               }
-
-               internal bool RaiseServerCertificateValidation(
+               internal virtual bool RaiseServerCertificateValidation(
                        X509Certificate certificate, 
                        int[]                   certificateErrors)
                {
-                       if (this.ServerCertValidation != null)
-                       {
-                               return this.ServerCertValidation(certificate, certificateErrors);
-                       }
-
-                       return false;
+                       return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
                }
 
                internal X509Certificate RaiseClientCertificateSelection(
@@ -1183,32 +396,26 @@ namespace Mono.Security.Protocol.Tls
                        string                                          targetHost, 
                        X509CertificateCollection       serverRequestedCertificates)
                {
-                       if (this.ClientCertSelection != null)
-                       {
-                               return this.ClientCertSelection(
-                                       clientCertificates,
-                                       serverCertificate,
-                                       targetHost,
-                                       serverRequestedCertificates);
-                       }
-
-                       return null;
+                       return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
                }
 
-               internal AsymmetricAlgorithm RaisePrivateKeySelection(
-                       X509Certificate clientCertificate, 
-                       string                  targetHost)
+               internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
                {
                        if (this.PrivateKeySelection != null)
                        {
-                               return this.PrivateKeySelection(
-                                       clientCertificate,
-                                       targetHost);
+                               return this.PrivateKeySelection(certificate, targetHost);
                        }
 
                        return null;
                }
 
+               internal AsymmetricAlgorithm RaisePrivateKeySelection(
+                       X509Certificate certificate,
+                       string targetHost)
+               {
+                       return base.RaiseLocalPrivateKeySelection(certificate, targetHost);
+               }
+
                #endregion
        }
 }