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 Context context;
46 public Stream InnerStream
48 get { return this.innerStream; }
49 set { this.innerStream = value; }
52 public Context Context
54 get { return this.context; }
55 set { this.context = value; }
62 public RecordProtocol(Stream innerStream, Context 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 && this.context.ProtocolNegotiated)
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 Alert Methods
202 public void SendAlert(TlsAlertDescription description)
204 this.SendAlert(new TlsAlert(this.Context, description));
207 public void SendAlert(
209 TlsAlertDescription description)
211 this.SendAlert(new TlsAlert(this.Context, level, description));
214 public void SendAlert(TlsAlert alert)
217 this.SendRecord(TlsContentType.Alert, alert.ToArray());
222 // Reset message contents
228 #region Send Record Methods
230 public void SendChangeCipherSpec()
232 // Send Change Cipher Spec message
233 this.SendRecord(TlsContentType.ChangeCipherSpec, new byte[] {1});
235 // Reset sequence numbers
236 this.context.WriteSequenceNumber = 0;
238 // Make the pending state to be the current state
239 this.context.IsActual = true;
241 // Send Finished message
242 this.SendRecord(TlsHandshakeType.Finished);
245 public void SendRecord(TlsContentType contentType, byte[] recordData)
247 if (this.context.ConnectionEnd)
249 throw this.context.CreateException("The session is finished and it's no longer valid.");
252 byte[] record = this.EncodeRecord(contentType, recordData);
254 this.innerStream.Write(record, 0, record.Length);
257 public byte[] EncodeRecord(TlsContentType contentType, byte[] recordData)
259 return this.EncodeRecord(
266 public byte[] EncodeRecord(
267 TlsContentType contentType,
272 if (this.context.ConnectionEnd)
274 throw this.context.CreateException("The session is finished and it's no longer valid.");
277 TlsStream record = new TlsStream();
279 int position = offset;
281 while (position < ( offset + count ))
283 short fragmentLength = 0;
286 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
288 fragmentLength = Context.MAX_FRAGMENT_SIZE;
292 fragmentLength = (short)(count - position);
295 // Fill the fragment data
296 fragment = new byte[fragmentLength];
297 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
299 if (this.context.IsActual)
302 fragment = this.encryptRecordFragment(contentType, fragment);
306 record.Write((byte)contentType);
307 record.Write(this.context.Protocol);
308 record.Write((short)fragment.Length);
309 record.Write(fragment);
311 // Update buffer position
312 position += fragmentLength;
315 return record.ToArray();
320 #region Cryptography Methods
322 private byte[] encryptRecordFragment(
323 TlsContentType contentType,
326 // Calculate message MAC
327 byte[] mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
329 // Encrypt the message
330 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
333 if (this.context.Cipher.CipherMode == CipherMode.CBC)
335 byte[] iv = new byte[this.context.Cipher.IvSize];
336 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
337 this.context.Cipher.UpdateClientCipherIV(iv);
340 // Update sequence number
341 this.context.WriteSequenceNumber++;
346 private TlsStream decryptRecordFragment(
347 TlsContentType contentType,
350 byte[] dcrFragment = null;
351 byte[] dcrMAC = null;
354 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
357 if (this.context.Cipher.CipherMode == CipherMode.CBC)
359 byte[] iv = new byte[this.context.Cipher.IvSize];
360 System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
361 this.context.Cipher.UpdateServerCipherIV(iv);
365 byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
367 // Check that the mac is correct
368 if (mac.Length != dcrMAC.Length)
370 throw new TlsException("Invalid MAC received from server.");
372 for (int i = 0; i < mac.Length; i++)
374 if (mac[i] != dcrMAC[i])
376 throw new TlsException("Invalid MAC received from server.");
380 // Update sequence number
381 this.context.ReadSequenceNumber++;
383 return new TlsStream(dcrFragment);