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