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 new TlsException(
85 AlertDescription.InternalError,
86 "The session is finished and it's no longer valid.");
89 // Try to read the Record Content Type
90 int type = this.innerStream.ReadByte();
92 // There are no more data for read
98 ContentType contentType = (ContentType)type;
99 short protocol = this.readShort();
100 short length = this.readShort();
104 byte[] buffer = new byte[length];
105 while (received != length)
107 received += this.innerStream.Read(
108 buffer, received, buffer.Length - received);
111 DebugHelper.WriteLine(
112 ">>>> Read record ({0}|{1})",
113 this.context.DecodeProtocolCode(protocol),
115 DebugHelper.WriteLine("Record data", buffer);
117 TlsStream message = new TlsStream(buffer);
119 // Check that the message has a valid protocol version
120 if (protocol != this.context.Protocol &&
121 this.context.ProtocolNegotiated)
123 throw new TlsException(
124 AlertDescription.ProtocolVersion,
125 "Invalid protocol version on message received from server");
128 // Decrypt message contents if needed
129 if (contentType == ContentType.Alert && length == 2)
134 if (this.context.IsActual &&
135 contentType != ContentType.ChangeCipherSpec)
137 message = this.decryptRecordFragment(
141 DebugHelper.WriteLine("Decrypted record data", message.ToArray());
145 // Set last handshake message received to None
146 this.context.LastHandshakeMsg = HandshakeType.None;
149 byte[] result = message.ToArray();
153 case ContentType.Alert:
155 (AlertLevel)message.ReadByte(),
156 (AlertDescription)message.ReadByte());
159 case ContentType.ChangeCipherSpec:
160 this.ProcessChangeCipherSpec();
163 case ContentType.ApplicationData:
166 case ContentType.Handshake:
169 this.ProcessHandshakeMessage(message);
172 // Update handshakes of current messages
173 this.context.HandshakeMessages.Write(message.ToArray());
177 throw new TlsException(
178 AlertDescription.UnexpectedMessage,
179 "Unknown record received from server.");
185 private short readShort()
187 byte[] b = new byte[2];
188 this.innerStream.Read(b, 0, b.Length);
190 short val = BitConverter.ToInt16(b, 0);
192 return System.Net.IPAddress.HostToNetworkOrder(val);
195 private void processAlert(
196 AlertLevel alertLevel,
197 AlertDescription alertDesc)
201 case AlertLevel.Fatal:
202 throw new TlsException(alertLevel, alertDesc);
204 case AlertLevel.Warning:
208 case AlertDescription.CloseNotify:
209 this.context.ConnectionEnd = true;
218 #region Send Alert Methods
220 public void SendAlert(AlertDescription description)
222 this.SendAlert(new Alert(description));
225 public void SendAlert(
227 AlertDescription description)
229 this.SendAlert(new Alert(level, description));
232 public void SendAlert(Alert alert)
234 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
239 new byte[]{(byte)alert.Level, (byte)alert.Description});
241 if (alert.IsCloseNotify)
243 this.context.ConnectionEnd = true;
249 #region Send Record Methods
251 public void SendChangeCipherSpec()
253 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
255 // Send Change Cipher Spec message as a plain message
256 this.context.IsActual = false;
258 // Send Change Cipher Spec message
259 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
261 // Reset sequence numbers
262 this.context.WriteSequenceNumber = 0;
264 // Make the pending state to be the current state
265 this.context.IsActual = true;
267 // Send Finished message
268 this.SendRecord(HandshakeType.Finished);
271 public void SendRecord(ContentType contentType, byte[] recordData)
273 if (this.context.ConnectionEnd)
275 throw new TlsException(
276 AlertDescription.InternalError,
277 "The session is finished and it's no longer valid.");
280 byte[] record = this.EncodeRecord(contentType, recordData);
282 this.innerStream.Write(record, 0, record.Length);
285 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
287 return this.EncodeRecord(
294 public byte[] EncodeRecord(
295 ContentType contentType,
300 if (this.context.ConnectionEnd)
302 throw new TlsException(
303 AlertDescription.InternalError,
304 "The session is finished and it's no longer valid.");
307 TlsStream record = new TlsStream();
309 int position = offset;
311 while (position < ( offset + count ))
313 short fragmentLength = 0;
316 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
318 fragmentLength = Context.MAX_FRAGMENT_SIZE;
322 fragmentLength = (short)(count - position);
325 // Fill the fragment data
326 fragment = new byte[fragmentLength];
327 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
329 if (this.context.IsActual)
332 fragment = this.encryptRecordFragment(contentType, fragment);
336 record.Write((byte)contentType);
337 record.Write(this.context.Protocol);
338 record.Write((short)fragment.Length);
339 record.Write(fragment);
341 DebugHelper.WriteLine("Record data", fragment);
343 // Update buffer position
344 position += fragmentLength;
347 return record.ToArray();
352 #region Cryptography Methods
354 private byte[] encryptRecordFragment(
355 ContentType contentType,
360 // Calculate message MAC
361 if (this.Context is ClientContext)
363 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
367 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
370 DebugHelper.WriteLine(">>>> Record MAC", mac);
372 // Encrypt the message
373 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
375 // Set new Client Cipher IV
376 if (this.context.Cipher.CipherMode == CipherMode.CBC)
378 byte[] iv = new byte[this.context.Cipher.IvSize];
379 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
381 this.context.Cipher.UpdateClientCipherIV(iv);
384 // Update sequence number
385 this.context.WriteSequenceNumber++;
390 private TlsStream decryptRecordFragment(
391 ContentType contentType,
394 byte[] dcrFragment = null;
395 byte[] dcrMAC = null;
396 bool badRecordMac = false;
400 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
404 if (this.context is ServerContext)
406 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
412 // Generate record MAC
415 if (this.Context is ClientContext)
417 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
421 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
424 DebugHelper.WriteLine(">>>> Record MAC", mac);
427 if (mac.Length != dcrMAC.Length)
433 for (int i = 0; i < mac.Length; i++)
435 if (mac[i] != dcrMAC[i])
445 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
448 // Update sequence number
449 this.context.ReadSequenceNumber++;
451 return new TlsStream(dcrFragment);