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;
65 this.context.RecordProtocol = this;
70 #region Abstract Methods
72 public abstract void SendRecord(HandshakeType type);
73 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
74 protected abstract void ProcessChangeCipherSpec();
78 #region Reveive Record Methods
80 public byte[] ReceiveRecord()
82 if (this.context.ConnectionEnd)
84 throw this.context.CreateException("The session is finished and it's no longer valid.");
87 // Try to read the Record Content Type
88 int type = this.innerStream.ReadByte();
90 // There are no more data for read
96 ContentType contentType = (ContentType)type;
97 short protocol = this.readShort();
98 short length = this.readShort();
102 byte[] buffer = new byte[length];
103 while (received != length)
105 received += this.innerStream.Read(
106 buffer, received, buffer.Length - received);
109 TlsStream message = new TlsStream(buffer);
111 // Check that the message has a valid protocol version
112 if (protocol != this.context.Protocol &&
113 this.context.ProtocolNegotiated)
115 throw this.context.CreateException("Invalid protocol version on message received from server");
118 // Decrypt message contents if needed
119 if (contentType == ContentType.Alert && length == 2)
124 if (this.context.IsActual &&
125 contentType != ContentType.ChangeCipherSpec)
127 message = this.decryptRecordFragment(
133 // Set last handshake message received to None
134 this.context.LastHandshakeMsg = HandshakeType.None;
137 byte[] result = message.ToArray();
141 case ContentType.Alert:
143 (AlertLevel)message.ReadByte(),
144 (AlertDescription)message.ReadByte());
147 case ContentType.ChangeCipherSpec:
148 this.ProcessChangeCipherSpec();
151 case ContentType.ApplicationData:
154 case ContentType.Handshake:
157 this.ProcessHandshakeMessage(message);
160 // Update handshakes of current messages
161 this.context.HandshakeMessages.Write(message.ToArray());
165 throw this.context.CreateException("Unknown record received from server.");
171 private short readShort()
173 byte[] b = new byte[2];
174 this.innerStream.Read(b, 0, b.Length);
176 short val = BitConverter.ToInt16(b, 0);
178 return System.Net.IPAddress.HostToNetworkOrder(val);
181 private void processAlert(
182 AlertLevel alertLevel,
183 AlertDescription alertDesc)
187 case AlertLevel.Fatal:
188 throw this.context.CreateException(alertLevel, alertDesc);
190 case AlertLevel.Warning:
194 case AlertDescription.CloseNotify:
195 this.context.ConnectionEnd = true;
204 #region Send Alert Methods
206 public void SendAlert(AlertDescription description)
208 this.SendAlert(new Alert(this.Context, description));
211 public void SendAlert(
213 AlertDescription description)
215 this.SendAlert(new Alert(this.Context, level, description));
218 public void SendAlert(Alert alert)
221 this.SendRecord(ContentType.Alert, alert.ToArray());
226 // Reset message contents
232 #region Send Record Methods
234 public void SendChangeCipherSpec()
236 // Send Change Cipher Spec message as a plain message
237 this.context.IsActual = false;
239 // Send Change Cipher Spec message
240 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
242 // Reset sequence numbers
243 this.context.WriteSequenceNumber = 0;
245 // Make the pending state to be the current state
246 this.context.IsActual = true;
248 // Send Finished message
249 this.SendRecord(HandshakeType.Finished);
252 public void SendRecord(ContentType contentType, byte[] recordData)
254 if (this.context.ConnectionEnd)
256 throw this.context.CreateException("The session is finished and it's no longer valid.");
259 byte[] record = this.EncodeRecord(contentType, recordData);
261 this.innerStream.Write(record, 0, record.Length);
264 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
266 return this.EncodeRecord(
273 public byte[] EncodeRecord(
274 ContentType contentType,
279 if (this.context.ConnectionEnd)
281 throw this.context.CreateException("The session is finished and it's no longer valid.");
284 TlsStream record = new TlsStream();
286 int position = offset;
288 while (position < ( offset + count ))
290 short fragmentLength = 0;
293 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
295 fragmentLength = Context.MAX_FRAGMENT_SIZE;
299 fragmentLength = (short)(count - position);
302 // Fill the fragment data
303 fragment = new byte[fragmentLength];
304 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
306 if (this.context.IsActual)
309 fragment = this.encryptRecordFragment(contentType, fragment);
313 record.Write((byte)contentType);
314 record.Write(this.context.Protocol);
315 record.Write((short)fragment.Length);
316 record.Write(fragment);
318 // Update buffer position
319 position += fragmentLength;
322 return record.ToArray();
327 #region Cryptography Methods
329 private byte[] encryptRecordFragment(
330 ContentType contentType,
335 // Calculate message MAC
336 if (this.Context is ClientContext)
338 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
342 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
345 // Encrypt the message
346 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
348 // Set new Client Cipher IV
349 if (this.context.Cipher.CipherMode == CipherMode.CBC)
351 byte[] iv = new byte[this.context.Cipher.IvSize];
352 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
354 this.context.Cipher.UpdateClientCipherIV(iv);
357 // Update sequence number
358 this.context.WriteSequenceNumber++;
363 private TlsStream decryptRecordFragment(
364 ContentType contentType,
367 byte[] dcrFragment = null;
368 byte[] dcrMAC = null;
369 bool badRecordMac = false;
373 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
377 if (this.context is ServerContext)
379 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
385 // Generate record MAC
388 if (this.Context is ClientContext)
390 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
394 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
398 if (mac.Length != dcrMAC.Length)
404 for (int i = 0; i < mac.Length; i++)
406 if (mac[i] != dcrMAC[i])
416 if (this.context is ServerContext)
418 this.Context.RecordProtocol.SendAlert(AlertDescription.BadRecordMAC);
421 throw new TlsException("Bad record MAC");
424 // Update sequence number
425 this.context.ReadSequenceNumber++;
427 return new TlsStream(dcrFragment);