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 &&
112 this.context.HelloDone)
114 throw this.context.CreateException("Invalid protocol version on message received from server");
117 // Decrypt message contents if needed
118 if (contentType == TlsContentType.Alert && length == 2)
123 if (this.context.IsActual &&
124 contentType != TlsContentType.ChangeCipherSpec)
126 message = this.decryptRecordFragment(
132 byte[] result = message.ToArray();
137 case TlsContentType.Alert:
139 (TlsAlertLevel)message.ReadByte(),
140 (TlsAlertDescription)message.ReadByte());
143 case TlsContentType.ChangeCipherSpec:
144 // Reset sequence numbers
145 this.context.ReadSequenceNumber = 0;
148 case TlsContentType.ApplicationData:
151 case TlsContentType.Handshake:
154 this.ProcessHandshakeMessage(message);
157 // Update handshakes of current messages
158 this.context.HandshakeMessages.Write(message.ToArray());
162 throw this.context.CreateException("Unknown record received from server.");
168 private short readShort()
170 byte[] b = new byte[2];
171 this.innerStream.Read(b, 0, b.Length);
173 short val = BitConverter.ToInt16(b, 0);
175 return System.Net.IPAddress.HostToNetworkOrder(val);
178 private void processAlert(
179 TlsAlertLevel alertLevel,
180 TlsAlertDescription alertDesc)
184 case TlsAlertLevel.Fatal:
185 throw this.context.CreateException(alertLevel, alertDesc);
187 case TlsAlertLevel.Warning:
191 case TlsAlertDescription.CloseNotify:
192 this.context.ConnectionEnd = true;
201 #region Send Record Methods
203 public void SendAlert(TlsAlert alert)
206 this.SendRecord(TlsContentType.Alert, alert.ToArray());
211 // Reset message contents
215 public void SendChangeCipherSpec()
217 // Send Change Cipher Spec message
218 this.SendRecord(TlsContentType.ChangeCipherSpec, new byte[] {1});
220 // Reset sequence numbers
221 this.context.WriteSequenceNumber = 0;
223 // Make the pending state to be the current state
224 this.context.IsActual = true;
226 // Send Finished message
227 this.SendRecord(TlsHandshakeType.Finished);
230 public void SendRecord(TlsContentType contentType, byte[] recordData)
232 if (this.context.ConnectionEnd)
234 throw this.context.CreateException("The session is finished and it's no longer valid.");
237 byte[] record = this.EncodeRecord(contentType, recordData);
239 this.innerStream.Write(record, 0, record.Length);
242 public byte[] EncodeRecord(TlsContentType contentType, byte[] recordData)
244 return this.EncodeRecord(
251 public byte[] EncodeRecord(
252 TlsContentType contentType,
257 if (this.context.ConnectionEnd)
259 throw this.context.CreateException("The session is finished and it's no longer valid.");
262 TlsStream record = new TlsStream();
264 int position = offset;
266 while (position < ( offset + count ))
268 short fragmentLength = 0;
271 if ((count - position) > TlsContext.MAX_FRAGMENT_SIZE)
273 fragmentLength = TlsContext.MAX_FRAGMENT_SIZE;
277 fragmentLength = (short)(count - position);
280 // Fill the fragment data
281 fragment = new byte[fragmentLength];
282 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
284 if (this.context.IsActual)
287 fragment = this.encryptRecordFragment(contentType, fragment);
291 record.Write((byte)contentType);
292 record.Write(this.context.Protocol);
293 record.Write((short)fragment.Length);
294 record.Write(fragment);
296 // Update buffer position
297 position += fragmentLength;
300 return record.ToArray();
305 #region Cryptography Methods
307 private byte[] encryptRecordFragment(
308 TlsContentType contentType,
311 // Calculate message MAC
312 byte[] mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
314 // Encrypt the message
315 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
318 if (this.context.Cipher.CipherMode == CipherMode.CBC)
320 byte[] iv = new byte[this.context.Cipher.IvSize];
321 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
322 this.context.Cipher.UpdateClientCipherIV(iv);
325 // Update sequence number
326 this.context.WriteSequenceNumber++;
331 private TlsStream decryptRecordFragment(
332 TlsContentType contentType,
335 byte[] dcrFragment = null;
336 byte[] dcrMAC = null;
339 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
342 if (this.context.Cipher.CipherMode == CipherMode.CBC)
344 byte[] iv = new byte[this.context.Cipher.IvSize];
345 System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
346 this.context.Cipher.UpdateServerCipherIV(iv);
350 byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
352 // Check that the mac is correct
353 if (mac.Length != dcrMAC.Length)
355 throw new TlsException("Invalid MAC received from server.");
357 for (int i = 0; i < mac.Length; i++)
359 if (mac[i] != dcrMAC[i])
361 throw new TlsException("Invalid MAC received from server.");
365 // Update sequence number
366 this.context.ReadSequenceNumber++;
368 return new TlsStream(dcrFragment);