-/* 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.IO;\r
-using System.Security.Cryptography;\r
-using System.Security.Cryptography.X509Certificates;\r
-\r
-using Mono.Security.Protocol.Tls.Handshake;\r
-\r
-namespace Mono.Security.Protocol.Tls\r
-{\r
- internal abstract class RecordProtocol\r
- {\r
- #region Fields\r
-\r
- protected Stream innerStream;\r
- protected Context context;\r
-\r
- #endregion\r
-\r
- #region Properties\r
-\r
- public Stream InnerStream\r
- {\r
- get { return this.innerStream; }\r
- set { this.innerStream = value; }\r
- }\r
-\r
- public Context Context\r
- {\r
- get { return this.context; }\r
- set { this.context = value; }\r
- }\r
-\r
- #endregion\r
-\r
- #region Constructors\r
-\r
- public RecordProtocol(Stream innerStream, Context context)\r
- {\r
- this.innerStream = innerStream;\r
- this.context = context;\r
- }\r
-\r
- #endregion\r
-\r
- #region Abstract Methods\r
-\r
- public abstract void SendRecord(HandshakeType type);\r
- protected abstract void ProcessHandshakeMessage(TlsStream handMsg);\r
- \r
- #endregion\r
-\r
- #region Reveive Record Methods\r
-\r
- public 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
- ContentType contentType = (ContentType)type;\r
- short protocol = 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
- this.context.ProtocolNegotiated)\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 == ContentType.Alert && length == 2)\r
- {\r
- }\r
- else\r
- {\r
- if (this.context.IsActual &&\r
- contentType != ContentType.ChangeCipherSpec)\r
- {\r
- message = this.decryptRecordFragment(\r
- contentType, \r
- message.ToArray());\r
- }\r
- }\r
-\r
- // Set last handshake message received to None\r
- this.context.LastHandshakeMsg = HandshakeType.None;\r
- \r
- // Process record\r
- byte[] result = message.ToArray();\r
-\r
- switch (contentType)\r
- {\r
- case ContentType.Alert:\r
- this.processAlert(\r
- (AlertLevel)message.ReadByte(),\r
- (AlertDescription)message.ReadByte());\r
- break;\r
-\r
- case ContentType.ChangeCipherSpec:\r
- // Reset sequence numbers\r
- this.context.ReadSequenceNumber = 0;\r
- break;\r
-\r
- case ContentType.ApplicationData:\r
- break;\r
-\r
- case ContentType.Handshake:\r
- while (!message.EOF)\r
- {\r
- this.ProcessHandshakeMessage(message);\r
- }\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
- 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 processAlert(\r
- AlertLevel alertLevel, \r
- AlertDescription alertDesc)\r
- {\r
- switch (alertLevel)\r
- {\r
- case AlertLevel.Fatal:\r
- throw this.context.CreateException(alertLevel, alertDesc); \r
-\r
- case AlertLevel.Warning:\r
- default:\r
- switch (alertDesc)\r
- {\r
- case AlertDescription.CloseNotify:\r
- this.context.ConnectionEnd = true;\r
- break;\r
- }\r
- break;\r
- }\r
- }\r
-\r
- #endregion\r
-\r
- #region Send Alert Methods\r
-\r
- public void SendAlert(AlertDescription description)\r
- {\r
- this.SendAlert(new Alert(this.Context, description));\r
- }\r
-\r
- public void SendAlert(\r
- AlertLevel level, \r
- AlertDescription description)\r
- {\r
- this.SendAlert(new Alert(this.Context, level, description));\r
- }\r
-\r
- public void SendAlert(Alert alert)\r
- { \r
- // Write record\r
- this.SendRecord(ContentType.Alert, alert.ToArray());\r
-\r
- // Update session\r
- alert.Update();\r
-\r
- // Reset message contents\r
- alert.Reset();\r
- }\r
-\r
- #endregion\r
-\r
- #region Send Record Methods\r
-\r
- public void SendChangeCipherSpec()\r
- {\r
- // Send Change Cipher Spec message\r
- this.SendRecord(ContentType.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(HandshakeType.Finished); \r
- }\r
-\r
- public void SendRecord(ContentType 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
- public byte[] EncodeRecord(ContentType contentType, byte[] recordData)\r
- {\r
- return this.EncodeRecord(\r
- contentType,\r
- recordData,\r
- 0,\r
- recordData.Length);\r
- }\r
-\r
- public byte[] EncodeRecord(\r
- ContentType 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) > Context.MAX_FRAGMENT_SIZE)\r
- {\r
- fragmentLength = Context.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(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
- ContentType 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
- ContentType contentType, \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
-}\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.IO;
+using System.Security.Cryptography;
+using System.Security.Cryptography.X509Certificates;
+
+using Mono.Security.Protocol.Tls.Handshake;
+
+namespace Mono.Security.Protocol.Tls
+{
+ internal abstract class RecordProtocol
+ {
+ #region Fields
+
+ protected Stream innerStream;
+ protected Context context;
+
+ #endregion
+
+ #region Properties
+
+ public Stream InnerStream
+ {
+ get { return this.innerStream; }
+ set { this.innerStream = value; }
+ }
+
+ public Context Context
+ {
+ get { return this.context; }
+ set { this.context = value; }
+ }
+
+ #endregion
+
+ #region Constructors
+
+ public RecordProtocol(Stream innerStream, Context context)
+ {
+ this.innerStream = innerStream;
+ this.context = context;
+ this.context.RecordProtocol = this;
+ }
+
+ #endregion
+
+ #region Abstract Methods
+
+ public abstract void SendRecord(HandshakeType type);
+ protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
+ protected abstract void ProcessChangeCipherSpec();
+
+ #endregion
+
+ #region Reveive Record Methods
+
+ public byte[] ReceiveRecord()
+ {
+ if (this.context.ConnectionEnd)
+ {
+ throw new TlsException(
+ AlertDescription.InternalError,
+ "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;
+ }
+
+ ContentType contentType = (ContentType)type;
+ short protocol = 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);
+ }
+
+ DebugHelper.WriteLine(
+ ">>>> Read record ({0}|{1})",
+ this.context.DecodeProtocolCode(protocol),
+ contentType);
+ DebugHelper.WriteLine("Record data", buffer);
+
+ TlsStream message = new TlsStream(buffer);
+
+ // Check that the message has a valid protocol version
+ if (protocol != this.context.Protocol &&
+ this.context.ProtocolNegotiated)
+ {
+ throw new TlsException(
+ AlertDescription.ProtocolVersion,
+ "Invalid protocol version on message received from server");
+ }
+
+ // Decrypt message contents if needed
+ if (contentType == ContentType.Alert && length == 2)
+ {
+ }
+ else
+ {
+ if (this.context.IsActual &&
+ contentType != ContentType.ChangeCipherSpec)
+ {
+ message = this.decryptRecordFragment(
+ contentType,
+ message.ToArray());
+
+ DebugHelper.WriteLine("Decrypted record data", message.ToArray());
+ }
+ }
+
+ // Set last handshake message received to None
+ this.context.LastHandshakeMsg = HandshakeType.None;
+
+ // Process record
+ byte[] result = message.ToArray();
+
+ switch (contentType)
+ {
+ case ContentType.Alert:
+ this.processAlert(
+ (AlertLevel)message.ReadByte(),
+ (AlertDescription)message.ReadByte());
+ break;
+
+ case ContentType.ChangeCipherSpec:
+ this.ProcessChangeCipherSpec();
+ break;
+
+ case ContentType.ApplicationData:
+ break;
+
+ case ContentType.Handshake:
+ while (!message.EOF)
+ {
+ this.ProcessHandshakeMessage(message);
+ }
+
+ // Update handshakes of current messages
+ this.context.HandshakeMessages.Write(message.ToArray());
+ break;
+
+ default:
+ throw new TlsException(
+ AlertDescription.UnexpectedMessage,
+ "Unknown record received from server.");
+ }
+
+ return result;
+ }
+
+ 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 processAlert(
+ AlertLevel alertLevel,
+ AlertDescription alertDesc)
+ {
+ switch (alertLevel)
+ {
+ case AlertLevel.Fatal:
+ throw new TlsException(alertLevel, alertDesc);
+
+ case AlertLevel.Warning:
+ default:
+ switch (alertDesc)
+ {
+ case AlertDescription.CloseNotify:
+ this.context.ConnectionEnd = true;
+ break;
+ }
+ break;
+ }
+ }
+
+ #endregion
+
+ #region Send Alert Methods
+
+ public void SendAlert(AlertDescription description)
+ {
+ this.SendAlert(new Alert(description));
+ }
+
+ public void SendAlert(
+ AlertLevel level,
+ AlertDescription description)
+ {
+ this.SendAlert(new Alert(level, description));
+ }
+
+ public void SendAlert(Alert alert)
+ {
+ DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
+
+ // Write record
+ this.SendRecord(
+ ContentType.Alert,
+ new byte[]{(byte)alert.Level, (byte)alert.Description});
+
+ if (alert.IsCloseNotify)
+ {
+ this.context.ConnectionEnd = true;
+ }
+ }
+
+ #endregion
+
+ #region Send Record Methods
+
+ public void SendChangeCipherSpec()
+ {
+ DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
+
+ // Send Change Cipher Spec message as a plain message
+ this.context.IsActual = false;
+
+ // Send Change Cipher Spec message
+ this.SendRecord(ContentType.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(HandshakeType.Finished);
+ }
+
+ public void SendRecord(ContentType contentType, byte[] recordData)
+ {
+ if (this.context.ConnectionEnd)
+ {
+ throw new TlsException(
+ AlertDescription.InternalError,
+ "The session is finished and it's no longer valid.");
+ }
+
+ byte[] record = this.EncodeRecord(contentType, recordData);
+
+ this.innerStream.Write(record, 0, record.Length);
+ }
+
+ public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
+ {
+ return this.EncodeRecord(
+ contentType,
+ recordData,
+ 0,
+ recordData.Length);
+ }
+
+ public byte[] EncodeRecord(
+ ContentType contentType,
+ byte[] recordData,
+ int offset,
+ int count)
+ {
+ if (this.context.ConnectionEnd)
+ {
+ throw new TlsException(
+ AlertDescription.InternalError,
+ "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) > Context.MAX_FRAGMENT_SIZE)
+ {
+ fragmentLength = Context.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(this.context.Protocol);
+ record.Write((short)fragment.Length);
+ record.Write(fragment);
+
+ DebugHelper.WriteLine("Record data", fragment);
+
+ // Update buffer position
+ position += fragmentLength;
+ }
+
+ return record.ToArray();
+ }
+
+ #endregion
+
+ #region Cryptography Methods
+
+ private byte[] encryptRecordFragment(
+ ContentType contentType,
+ byte[] fragment)
+ {
+ byte[] mac = null;
+
+ // Calculate message MAC
+ if (this.Context is ClientContext)
+ {
+ mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
+ }
+ else
+ {
+ mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
+ }
+
+ DebugHelper.WriteLine(">>>> Record MAC", mac);
+
+ // Encrypt the message
+ byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
+
+ // Set new Client Cipher IV
+ if (this.context.Cipher.CipherMode == CipherMode.CBC)
+ {
+ byte[] iv = new byte[this.context.Cipher.IvSize];
+ Buffer.BlockCopy(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(
+ ContentType contentType,
+ byte[] fragment)
+ {
+ byte[] dcrFragment = null;
+ byte[] dcrMAC = null;
+ bool badRecordMac = false;
+
+ try
+ {
+ this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
+ }
+ catch
+ {
+ if (this.context is ServerContext)
+ {
+ this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
+ }
+
+ throw;
+ }
+
+ // Generate record MAC
+ byte[] mac = null;
+
+ if (this.Context is ClientContext)
+ {
+ mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
+ }
+ else
+ {
+ mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
+ }
+
+ DebugHelper.WriteLine(">>>> Record MAC", mac);
+
+ // Check record MAC
+ if (mac.Length != dcrMAC.Length)
+ {
+ badRecordMac = true;
+ }
+ else
+ {
+ for (int i = 0; i < mac.Length; i++)
+ {
+ if (mac[i] != dcrMAC[i])
+ {
+ badRecordMac = true;
+ break;
+ }
+ }
+ }
+
+ if (badRecordMac)
+ {
+ throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
+ }
+
+ // Update sequence number
+ this.context.ReadSequenceNumber++;
+
+ return new TlsStream(dcrFragment);
+ }
+
+ #endregion
+ }
+}