1 /* Transport Security Layer (TLS)
2 * Copyright (c) 2003-2004 Carlos Guzman Alvarez
4 * Permission is hereby granted, free of charge, to any person
5 * obtaining a copy of this software and associated documentation
6 * files (the "Software"), to deal in the Software without restriction,
7 * including without limitation the rights to use, copy, modify, merge,
8 * publish, distribute, sublicense, and/or sell copies of the Software,
9 * and to permit persons to whom the Software is furnished to do so,
10 * subject to the following conditions:
12 * The above copyright notice and this permission notice shall be included
13 * in all copies or substantial portions of the Software.
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
22 * DEALINGS IN THE SOFTWARE.
27 using System.Security.Cryptography;
28 using System.Security.Cryptography.X509Certificates;
30 using Mono.Security.Protocol.Tls.Alerts;
31 using Mono.Security.Protocol.Tls.Handshake;
33 namespace Mono.Security.Protocol.Tls
35 internal abstract class RecordProtocol
39 protected Stream innerStream;
40 protected TlsContext context;
46 public Stream InnerStream
48 get { return this.innerStream; }
49 set { this.innerStream = value; }
52 public TlsContext Context
54 get { return this.context; }
55 set { this.context = value; }
62 public RecordProtocol(Stream innerStream, TlsContext context)
64 this.innerStream = innerStream;
65 this.context = context;
70 #region Abstract Methods
72 public abstract void SendRecord(TlsHandshakeType type);
73 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
77 #region Reveive Record Methods
79 public byte[] ReceiveRecord()
81 if (this.context.ConnectionEnd)
83 throw this.context.CreateException("The session is finished and it's no longer valid.");
86 // Try to read the Record Content Type
87 int type = this.innerStream.ReadByte();
89 // There are no more data for read
95 TlsContentType contentType = (TlsContentType)type;
96 short protocol = this.readShort();
97 short length = this.readShort();
101 byte[] buffer = new byte[length];
102 while (received != length)
104 received += this.innerStream.Read(
105 buffer, received, buffer.Length - received);
108 TlsStream message = new TlsStream(buffer);
110 // Check that the message has a valid protocol version
111 if (protocol != this.context.Protocol)
113 throw this.context.CreateException("Invalid protocol version on message received from server");
116 // Decrypt message contents if needed
117 if (contentType == TlsContentType.Alert && length == 2)
122 if (this.context.IsActual &&
123 contentType != TlsContentType.ChangeCipherSpec)
125 message = this.decryptRecordFragment(
131 byte[] result = message.ToArray();
136 case TlsContentType.Alert:
138 (TlsAlertLevel)message.ReadByte(),
139 (TlsAlertDescription)message.ReadByte());
142 case TlsContentType.ChangeCipherSpec:
143 // Reset sequence numbers
144 this.context.ReadSequenceNumber = 0;
147 case TlsContentType.ApplicationData:
150 case TlsContentType.Handshake:
153 this.ProcessHandshakeMessage(message);
156 // Update handshakes of current messages
157 this.context.HandshakeMessages.Write(message.ToArray());
161 throw this.context.CreateException("Unknown record received from server.");
167 private short readShort()
169 byte[] b = new byte[2];
170 this.innerStream.Read(b, 0, b.Length);
172 short val = BitConverter.ToInt16(b, 0);
174 return System.Net.IPAddress.HostToNetworkOrder(val);
177 private void processAlert(
178 TlsAlertLevel alertLevel,
179 TlsAlertDescription alertDesc)
183 case TlsAlertLevel.Fatal:
184 throw this.context.CreateException(alertLevel, alertDesc);
186 case TlsAlertLevel.Warning:
190 case TlsAlertDescription.CloseNotify:
191 this.context.ConnectionEnd = true;
200 #region Send Record Methods
202 public void SendAlert(TlsAlert alert)
205 this.SendRecord(TlsContentType.Alert, alert.ToArray());
210 // Reset message contents
214 public void SendChangeCipherSpec()
216 // Send Change Cipher Spec message
217 this.SendRecord(TlsContentType.ChangeCipherSpec, new byte[] {1});
219 // Reset sequence numbers
220 this.context.WriteSequenceNumber = 0;
222 // Make the pending state to be the current state
223 this.context.IsActual = true;
225 // Send Finished message
226 this.SendRecord(TlsHandshakeType.Finished);
229 public void SendRecord(TlsContentType contentType, byte[] recordData)
231 if (this.context.ConnectionEnd)
233 throw this.context.CreateException("The session is finished and it's no longer valid.");
236 byte[] record = this.EncodeRecord(contentType, recordData);
238 this.innerStream.Write(record, 0, record.Length);
241 public byte[] EncodeRecord(TlsContentType contentType, byte[] recordData)
243 return this.EncodeRecord(
250 public byte[] EncodeRecord(
251 TlsContentType contentType,
256 if (this.context.ConnectionEnd)
258 throw this.context.CreateException("The session is finished and it's no longer valid.");
261 TlsStream record = new TlsStream();
263 int position = offset;
265 while (position < ( offset + count ))
267 short fragmentLength = 0;
270 if ((count - position) > TlsContext.MAX_FRAGMENT_SIZE)
272 fragmentLength = TlsContext.MAX_FRAGMENT_SIZE;
276 fragmentLength = (short)(count - position);
279 // Fill the fragment data
280 fragment = new byte[fragmentLength];
281 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
283 if (this.context.IsActual)
286 fragment = this.encryptRecordFragment(contentType, fragment);
290 record.Write((byte)contentType);
291 record.Write(this.context.Protocol);
292 record.Write((short)fragment.Length);
293 record.Write(fragment);
295 // Update buffer position
296 position += fragmentLength;
299 return record.ToArray();
304 #region Cryptography Methods
306 private byte[] encryptRecordFragment(
307 TlsContentType contentType,
310 // Calculate message MAC
311 byte[] mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
313 // Encrypt the message
314 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
317 if (this.context.Cipher.CipherMode == CipherMode.CBC)
319 byte[] iv = new byte[this.context.Cipher.IvSize];
320 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
321 this.context.Cipher.UpdateClientCipherIV(iv);
324 // Update sequence number
325 this.context.WriteSequenceNumber++;
330 private TlsStream decryptRecordFragment(
331 TlsContentType contentType,
334 byte[] dcrFragment = null;
335 byte[] dcrMAC = null;
338 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
341 if (this.context.Cipher.CipherMode == CipherMode.CBC)
343 byte[] iv = new byte[this.context.Cipher.IvSize];
344 System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
345 this.context.Cipher.UpdateServerCipherIV(iv);
349 byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
351 // Check that the mac is correct
352 if (mac.Length != dcrMAC.Length)
354 throw new TlsException("Invalid MAC received from server.");
356 for (int i = 0; i < mac.Length; i++)
358 if (mac[i] != dcrMAC[i])
360 throw new TlsException("Invalid MAC received from server.");
364 // Update sequence number
365 this.context.ReadSequenceNumber++;
367 return new TlsStream(dcrFragment);