-/* Transport Security Layer (TLS)\r
- * Copyright (c) 2003 Carlos Guzmán Álvarez\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
-\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
- public delegate bool CertificateValidationCallback(\r
- X509Certificate certificate, int[] certificateErrors);\r
- \r
- public delegate X509Certificate CertificateSelectionCallback(\r
- X509CertificateCollection clientCertificates, \r
- X509Certificate serverCertificate, \r
- string targetHost, \r
- X509CertificateCollection serverRequestedCertificates);\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
- \r
- #endregion\r
-\r
- #region FIELDS\r
-\r
- private CertificateValidationCallback serverCertValidationDelegate;\r
- private CertificateSelectionCallback clientCertSelectionDelegate;\r
- private Stream innerStream;\r
- private BufferedStream inputBuffer;\r
- private TlsContext context;\r
- private bool ownsStream;\r
- private bool disposed;\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
-\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 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 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 DESTRUCTOR\r
-\r
- ~SslClientStream()\r
- {\r
- this.Dispose(false);\r
- }\r
-\r
- #endregion\r
-\r
- #region IDISPOSABLE\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 (!disposed)\r
- {\r
- if (disposing)\r
- {\r
- if (this.innerStream != null)\r
- {\r
- // Write close notify\r
- TlsCloseNotifyAlert alert = new TlsCloseNotifyAlert(this.context);\r
- this.SendAlert(alert);\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
- disposed = true;\r
- }\r
- }\r
-\r
- #endregion\r
-\r
- #region CONSTRUCTORS\r
- \r
- public SslClientStream(Stream stream, string targetHost, bool ownsStream) : \r
- this(stream, targetHost, \r
- ownsStream, SecurityProtocolType.Default, null)\r
- {\r
- }\r
- \r
- public SslClientStream(\r
- Stream stream, string targetHost, X509Certificate clientCertificate) : \r
- this(\r
- stream, targetHost, \r
- false, SecurityProtocolType.Default, \r
- new X509CertificateCollection(new X509Certificate[]{clientCertificate}))\r
- {\r
- }\r
-\r
- public SslClientStream(\r
- Stream stream,\r
- string targetHost, X509CertificateCollection clientCertificates) : \r
- this(stream, targetHost, false, \r
- SecurityProtocolType.Default, clientCertificates)\r
- {\r
- }\r
-\r
- public SslClientStream(\r
- Stream stream,\r
- string targetHost,\r
- bool ownsStream,\r
- SecurityProtocolType securityProtocolType) : \r
- this(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
- 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
- }\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
- throw new NotSupportedException();\r
- }\r
-\r
- public override IAsyncResult BeginWrite(\r
- byte[] buffer,\r
- int offset,\r
- int count,\r
- AsyncCallback callback,\r
- object state)\r
- {\r
- throw new NotSupportedException();\r
- }\r
-\r
- public override int EndRead(IAsyncResult asyncResult)\r
- {\r
- throw new NotSupportedException();\r
- }\r
-\r
- public override void EndWrite(IAsyncResult asyncResult)\r
- {\r
- throw new NotSupportedException();\r
- }\r
-\r
- public override void Close()\r
- {\r
- ((IDisposable)this).Dispose();\r
- }\r
-\r
- public override void Flush()\r
- {\r
- if (this.disposed)\r
- {\r
- throw new ObjectDisposedException("The NetworkStream is closed.");\r
- }\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 size)\r
- {\r
- if (!this.context.HandshakeFinished)\r
- {\r
- // Start handshake negotiation\r
- this.doHandshake();\r
- }\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 (size < 0)\r
- {\r
- throw new ArgumentOutOfRangeException("size is less than 0.");\r
- }\r
- if (size > (buffer.Length - offset))\r
- {\r
- throw new ArgumentOutOfRangeException("size is less than the length of buffer minus the value of the offset parameter.");\r
- }\r
- if (this.disposed)\r
- {\r
- throw new ObjectDisposedException("The NetworkStream is closed.");\r
- }\r
- \r
- try\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) < size)\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.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
-\r
- #warning "Think on how to solve this"\r
- /*\r
- if (base.Available == 0)\r
- {\r
- break;\r
- }\r
- */\r
- }\r
-\r
- return this.inputBuffer.Read(buffer, offset, size);\r
- }\r
- catch (TlsException ex)\r
- {\r
- throw ex;\r
- }\r
- catch (Exception ex)\r
- {\r
- throw new IOException("IO exception during read.", ex);\r
- }\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 size)\r
- {\r
- if (!this.context.HandshakeFinished)\r
- {\r
- // Start handshake negotiation\r
- this.doHandshake();\r
- }\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 (size < 0)\r
- {\r
- throw new ArgumentOutOfRangeException("size is less than 0.");\r
- }\r
- if (size > (buffer.Length - offset))\r
- {\r
- throw new ArgumentOutOfRangeException("size is less than the length of buffer minus the value of the offset parameter.");\r
- }\r
- if (disposed)\r
- {\r
- throw new ObjectDisposedException("The NetworkStream is closed.");\r
- }\r
-\r
- try\r
- {\r
- // Send the buffer as a TLS record\r
- byte[] recordData = new byte[size];\r
- System.Array.Copy(buffer, offset, recordData, 0, size);\r
-\r
- this.sendRecord(TlsContentType.ApplicationData, recordData);\r
- }\r
- catch (TlsException ex)\r
- {\r
- throw ex;\r
- }\r
- catch (Exception ex)\r
- {\r
- throw new IOException("IO exception during Write.", ex);\r
- }\r
- }\r
-\r
- #endregion\r
-\r
- #region TLS_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
- TlsContentType contentType = (TlsContentType)innerStream.ReadByte();\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 TLS_SEND_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.UpdateSession();\r
-\r
- // Reset message contents\r
- alert.Reset();\r
- }\r
-\r
- private void sendRecord(TlsHandshakeType type)\r
- {\r
- TlsHandshakeMessage msg = createClientHandshakeMessage(type);\r
- \r
- // Write record\r
- this.sendRecord(msg.ContentType, msg.EncodeMessage());\r
-\r
- // Update session\r
- msg.UpdateSession();\r
-\r
- // Reset message contents\r
- msg.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(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[][] fragments = this.fragmentData(recordData);\r
- for (int i = 0; i < fragments.Length; i++)\r
- {\r
- byte[] fragment = fragments[i];\r
-\r
- if (this.context.IsActual)\r
- {\r
- // Encrypt fragment\r
- fragment = this.encryptRecordFragment(contentType, fragment);\r
- }\r
-\r
- // Write tls message\r
- TlsStream record = new TlsStream();\r
- record.Write((byte)contentType);\r
- record.Write((short)this.context.Protocol);\r
- record.Write((short)fragment.Length);\r
- record.Write(fragment);\r
-\r
- // Write record\r
- this.innerStream.Write(record.ToArray(), 0, (int)record.Length);\r
-\r
- // Reset record data\r
- record.Reset();\r
- }\r
- }\r
-\r
- private byte[][] fragmentData(byte[] messageData)\r
- {\r
- ArrayList d = new ArrayList();\r
- \r
- int position = 0;\r
-\r
- while (position < messageData.Length)\r
- {\r
- short fragmentLength = 0;\r
- byte[] fragmentData;\r
- if ((messageData.Length - position) > TlsContext.MAX_FRAGMENT_SIZE)\r
- {\r
- fragmentLength = TlsContext.MAX_FRAGMENT_SIZE;\r
- }\r
- else\r
- {\r
- fragmentLength = (short)(messageData.Length - position);\r
- }\r
- fragmentData = new byte[fragmentLength];\r
-\r
- System.Array.Copy(messageData, position, fragmentData, 0, fragmentLength);\r
-\r
- d.Add(fragmentData);\r
-\r
- position += fragmentLength;\r
- }\r
-\r
- byte[][] result = new byte[d.Count][];\r
- for (int i = 0; i < d.Count; i++)\r
- {\r
- result[i] = (byte[])d[i];\r
- }\r
-\r
- return result;\r
- }\r
-\r
- #endregion\r
-\r
- #region TLS_CRYPTO_METHODS\r
-\r
- private byte[] encryptRecordFragment(TlsContentType contentType, 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(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 MESSAGE_PROCESSING\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.UpdateSession();\r
- }\r
- }\r
-\r
- private void processAlert(TlsAlertLevel alertLevel, 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
- #endregion\r
-\r
- #region HANDSHAKE_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(TlsAlertLevel level, 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, int[] certificateErrors)\r
- {\r
- if (this.ServerCertValidation != null)\r
- {\r
- return this.ServerCertValidation(certificate, certificateErrors);\r
- }\r
-\r
- return false;\r
- }\r
-\r
- internal bool RaiseClientCertificateSelection(\r
- X509CertificateCollection clientCertificates, \r
- X509Certificate serverCertificate, \r
- string targetHost, \r
- X509CertificateCollection serverRequestedCertificates)\r
- {\r
- #warning "Add implementation"\r
-\r
- return true;\r
- }\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.Handshake;
+
+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 certificate,
+ string targetHost);
+
+ #endregion
+
+ public class SslClientStream : SslStreamBase
+ {
+ #region Internal Events
+
+ internal event CertificateValidationCallback ServerCertValidation;
+ internal event CertificateSelectionCallback ClientCertSelection;
+ internal event PrivateKeySelectionCallback PrivateKeySelection;
+
+ #endregion
+
+ #region Properties
+
+ // required by HttpsClientStream for proxy support
+ internal Stream InputBuffer
+ {
+ get { return base.inputBuffer; }
+ }
+
+ public X509CertificateCollection ClientCertificates
+ {
+ get { return this.context.ClientSettings.Certificates; }
+ }
+
+ public X509Certificate SelectedClientCertificate
+ {
+ get { return this.context.ClientSettings.ClientCertificate; }
+ }
+
+ #endregion
+
+ #region Callback Properties
+
+ public CertificateValidationCallback ServerCertValidationDelegate
+ {
+ get { return this.ServerCertValidation; }
+ set { this.ServerCertValidation = value; }
+ }
+
+ public CertificateSelectionCallback ClientCertSelectionDelegate
+ {
+ get { return this.ClientCertSelection; }
+ set { this.ClientCertSelection = value; }
+ }
+
+ public PrivateKeySelectionCallback PrivateKeyCertSelectionDelegate
+ {
+ get { return this.PrivateKeySelection; }
+ set { this.PrivateKeySelection = value; }
+ }
+
+ #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):
+ base(stream, ownsStream)
+ {
+ if (targetHost == null || targetHost.Length == 0)
+ {
+ throw new ArgumentNullException("targetHost is null or an empty string.");
+ }
+
+ this.context = new ClientContext(
+ this,
+ securityProtocolType,
+ targetHost,
+ clientCertificates);
+
+ this.protocol = new ClientRecordProtocol(innerStream, (ClientContext)this.context);
+ }
+
+ #endregion
+
+ #region Finalizer
+
+ ~SslClientStream()
+ {
+ base.Dispose(false);
+ }
+
+ #endregion
+
+ #region IDisposable Methods
+
+ protected override void Dispose(bool disposing)
+ {
+ base.Dispose(disposing);
+
+ if (disposing)
+ {
+ this.ServerCertValidation = null;
+ this.ClientCertSelection = null;
+ this.PrivateKeySelection = null;
+ }
+ }
+
+ #endregion
+
+ #region Handshake 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
+ */
+
+ internal override IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state)
+ {
+ try
+ {
+ if (this.context.HandshakeState != HandshakeState.None)
+ {
+ this.context.Clear();
+ }
+
+ // Obtain supported cipher suites
+ this.context.SupportedCiphers = CipherSuiteFactory.GetSupportedCiphers(this.context.SecurityProtocol);
+
+ // Set handshake state
+ this.context.HandshakeState = HandshakeState.Started;
+
+ // 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)
+ {
+ this.protocol.SendAlert(AlertDescription.InternalError);
+
+ throw new IOException("The authentication or decryption has failed.", ex);
+ }
+ }
+
+ private void SafeReceiveRecord (Stream s)
+ {
+ byte[] record = this.protocol.ReceiveRecord (s);
+ if ((record == null) || (record.Length == 0)) {
+ throw new TlsException (
+ AlertDescription.HandshakeFailiure,
+ "The server stopped the handshake.");
+ }
+ }
+
+ internal override void OnNegotiateHandshakeCallback(IAsyncResult asyncResult)
+ {
+ this.protocol.EndSendRecord(asyncResult);
+
+ // Read server response
+ while (this.context.LastHandshakeMsg != HandshakeType.ServerHelloDone)
+ {
+ // Read next record
+ SafeReceiveRecord (this.innerStream);
+
+ // special case for abbreviated handshake where no ServerHelloDone is sent from the server
+ if (this.context.AbbreviatedHandshake && (this.context.LastHandshakeMsg == HandshakeType.ServerHello))
+ break;
+ }
+
+ // the handshake is much easier if we can reuse a preivous session settings
+ if (this.context.AbbreviatedHandshake)
+ {
+ ClientSessionCache.SetContextFromCache (this.context);
+ this.context.Cipher.ComputeKeys ();
+ this.context.Cipher.InitializeCipher ();
+
+ // Send Cipher Spec protocol
+ this.protocol.SendChangeCipherSpec ();
+
+ // 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);
+ }
+
+ // Send Finished message
+ this.protocol.SendRecord (HandshakeType.Finished);
+ }
+ else
+ {
+ // Send client certificate if requested
+ // even if the server ask for it it _may_ still be optional
+ bool clientCertificate = this.context.ServerSettings.CertificateRequest;
+
+ // 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)
+ {
+ clientCertificate = ((this.context.ClientSettings.Certificates != null) &&
+ (this.context.ClientSettings.Certificates.Count > 0));
+ // this works well with OpenSSL (but only for SSL3)
+ }
+
+ if (clientCertificate)
+ {
+ this.protocol.SendRecord(HandshakeType.Certificate);
+ }
+
+ // Send Client Key Exchange
+ this.protocol.SendRecord(HandshakeType.ClientKeyExchange);
+
+ // Now initialize session cipher with the generated keys
+ this.context.Cipher.InitializeCipher();
+
+ // Send certificate verify if requested (optional)
+ if (clientCertificate && (this.context.ClientSettings.ClientCertificate != null))
+ {
+ this.protocol.SendRecord(HandshakeType.CertificateVerify);
+ }
+
+ // Send Cipher Spec protocol
+ this.protocol.SendChangeCipherSpec ();
+ // Send Finished message
+ this.protocol.SendRecord (HandshakeType.Finished);
+
+ // 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);
+ }
+ }
+
+ // Reset Handshake messages information
+ this.context.HandshakeMessages.Reset ();
+
+ // Clear Key Info
+ this.context.ClearKeyInfo();
+
+ }
+
+ #endregion
+
+ #region Event Methods
+
+ internal override X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates, X509Certificate serverCertificate, string targetHost, X509CertificateCollection serverRequestedCertificates)
+ {
+ if (this.ClientCertSelection != null)
+ {
+ return this.ClientCertSelection(
+ clientCertificates,
+ serverCertificate,
+ targetHost,
+ serverRequestedCertificates);
+ }
+
+ return null;
+ }
+
+ internal override bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors)
+ {
+ if (this.ServerCertValidation != null)
+ {
+ return this.ServerCertValidation(certificate, errors);
+ }
+
+ return (errors != null && errors.Length == 0);
+ }
+
+ internal virtual bool RaiseServerCertificateValidation(
+ X509Certificate certificate,
+ int[] certificateErrors)
+ {
+ return base.RaiseRemoteCertificateValidation(certificate, certificateErrors);
+ }
+
+ internal X509Certificate RaiseClientCertificateSelection(
+ X509CertificateCollection clientCertificates,
+ X509Certificate serverCertificate,
+ string targetHost,
+ X509CertificateCollection serverRequestedCertificates)
+ {
+ return base.RaiseLocalCertificateSelection(clientCertificates, serverCertificate, targetHost, serverRequestedCertificates);
+ }
+
+ internal override AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost)
+ {
+ if (this.PrivateKeySelection != null)
+ {
+ return this.PrivateKeySelection(certificate, targetHost);
+ }
+
+ return null;
+ }
+
+ internal AsymmetricAlgorithm RaisePrivateKeySelection(
+ X509Certificate certificate,
+ string targetHost)
+ {
+ return base.RaiseLocalPrivateKeySelection(certificate, targetHost);
+ }
+
+ #endregion
+ }
+}