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.Handshake;
32 namespace Mono.Security.Protocol.Tls
34 internal abstract class RecordProtocol
38 protected Stream innerStream;
39 protected Context context;
45 public Stream InnerStream
47 get { return this.innerStream; }
48 set { this.innerStream = value; }
51 public Context Context
53 get { return this.context; }
54 set { this.context = value; }
61 public RecordProtocol(Stream innerStream, Context context)
63 this.innerStream = innerStream;
64 this.context = context;
69 #region Abstract Methods
71 public abstract void SendRecord(HandshakeType type);
72 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
73 protected abstract void ProcessChangeCipherSpec();
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 ContentType contentType = (ContentType)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 &&
112 this.context.ProtocolNegotiated)
114 throw this.context.CreateException("Invalid protocol version on message received from server");
117 // Decrypt message contents if needed
118 if (contentType == ContentType.Alert && length == 2)
123 if (this.context.IsActual &&
124 contentType != ContentType.ChangeCipherSpec)
126 message = this.decryptRecordFragment(
132 // Set last handshake message received to None
133 this.context.LastHandshakeMsg = HandshakeType.None;
136 byte[] result = message.ToArray();
140 case ContentType.Alert:
142 (AlertLevel)message.ReadByte(),
143 (AlertDescription)message.ReadByte());
146 case ContentType.ChangeCipherSpec:
147 this.ProcessChangeCipherSpec();
150 case ContentType.ApplicationData:
153 case ContentType.Handshake:
156 this.ProcessHandshakeMessage(message);
159 // Update handshakes of current messages
160 this.context.HandshakeMessages.Write(message.ToArray());
164 throw this.context.CreateException("Unknown record received from server.");
170 private short readShort()
172 byte[] b = new byte[2];
173 this.innerStream.Read(b, 0, b.Length);
175 short val = BitConverter.ToInt16(b, 0);
177 return System.Net.IPAddress.HostToNetworkOrder(val);
180 private void processAlert(
181 AlertLevel alertLevel,
182 AlertDescription alertDesc)
186 case AlertLevel.Fatal:
187 throw this.context.CreateException(alertLevel, alertDesc);
189 case AlertLevel.Warning:
193 case AlertDescription.CloseNotify:
194 this.context.ConnectionEnd = true;
203 #region Send Alert Methods
205 public void SendAlert(AlertDescription description)
207 this.SendAlert(new Alert(this.Context, description));
210 public void SendAlert(
212 AlertDescription description)
214 this.SendAlert(new Alert(this.Context, level, description));
217 public void SendAlert(Alert alert)
220 this.SendRecord(ContentType.Alert, alert.ToArray());
225 // Reset message contents
231 #region Send Record Methods
233 public void SendChangeCipherSpec()
235 // Send Change Cipher Spec message as a plain message
236 this.context.IsActual = false;
238 // Send Change Cipher Spec message
239 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
241 // Reset sequence numbers
242 this.context.WriteSequenceNumber = 0;
244 // Make the pending state to be the current state
245 this.context.IsActual = true;
247 // Send Finished message
248 this.SendRecord(HandshakeType.Finished);
251 public void SendRecord(ContentType contentType, byte[] recordData)
253 if (this.context.ConnectionEnd)
255 throw this.context.CreateException("The session is finished and it's no longer valid.");
258 byte[] record = this.EncodeRecord(contentType, recordData);
260 this.innerStream.Write(record, 0, record.Length);
263 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
265 return this.EncodeRecord(
272 public byte[] EncodeRecord(
273 ContentType contentType,
278 if (this.context.ConnectionEnd)
280 throw this.context.CreateException("The session is finished and it's no longer valid.");
283 TlsStream record = new TlsStream();
285 int position = offset;
287 while (position < ( offset + count ))
289 short fragmentLength = 0;
292 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
294 fragmentLength = Context.MAX_FRAGMENT_SIZE;
298 fragmentLength = (short)(count - position);
301 // Fill the fragment data
302 fragment = new byte[fragmentLength];
303 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
305 if (this.context.IsActual)
308 fragment = this.encryptRecordFragment(contentType, fragment);
312 record.Write((byte)contentType);
313 record.Write(this.context.Protocol);
314 record.Write((short)fragment.Length);
315 record.Write(fragment);
317 // Update buffer position
318 position += fragmentLength;
321 return record.ToArray();
326 #region Cryptography Methods
328 private byte[] encryptRecordFragment(
329 ContentType contentType,
334 // Calculate message MAC
335 if (this.Context is ClientContext)
337 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
341 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
344 // Encrypt the message
345 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
347 // Set new Client Cipher IV
348 if (this.context.Cipher.CipherMode == CipherMode.CBC)
350 byte[] iv = new byte[this.context.Cipher.IvSize];
351 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
353 this.context.Cipher.UpdateClientCipherIV(iv);
356 // Update sequence number
357 this.context.WriteSequenceNumber++;
362 private TlsStream decryptRecordFragment(
363 ContentType contentType,
366 byte[] dcrFragment = null;
367 byte[] dcrMAC = null;
370 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
374 if (this.Context is ClientContext)
376 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
380 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
383 // Check that the mac is correct
384 if (mac.Length != dcrMAC.Length)
386 throw new TlsException("Invalid MAC received from server.");
389 for (int i = 0; i < mac.Length; i++)
391 if (mac[i] != dcrMAC[i])
393 throw new TlsException("Invalid MAC received from server.");
397 // Update sequence number
398 this.context.ReadSequenceNumber++;
400 return new TlsStream(dcrFragment);