1 /* Transport Security Layer (TLS)
\r
2 * Copyright (c) 2003-2004 Carlos Guzman Alvarez
\r
4 * Permission is hereby granted, free of charge, to any person
\r
5 * obtaining a copy of this software and associated documentation
\r
6 * files (the "Software"), to deal in the Software without restriction,
\r
7 * including without limitation the rights to use, copy, modify, merge,
\r
8 * publish, distribute, sublicense, and/or sell copies of the Software,
\r
9 * and to permit persons to whom the Software is furnished to do so,
\r
10 * subject to the following conditions:
\r
12 * The above copyright notice and this permission notice shall be included
\r
13 * in all copies or substantial portions of the Software.
\r
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
\r
16 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
\r
17 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
\r
18 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
\r
19 * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
\r
20 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
\r
21 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
\r
22 * DEALINGS IN THE SOFTWARE.
\r
27 using System.Security.Cryptography;
\r
28 using System.Security.Cryptography.X509Certificates;
\r
30 using Mono.Security.Protocol.Tls.Handshake;
\r
32 namespace Mono.Security.Protocol.Tls
\r
34 internal abstract class RecordProtocol
\r
38 protected Stream innerStream;
\r
39 protected Context context;
\r
45 public Stream InnerStream
\r
47 get { return this.innerStream; }
\r
48 set { this.innerStream = value; }
\r
51 public Context Context
\r
53 get { return this.context; }
\r
54 set { this.context = value; }
\r
59 #region Constructors
\r
61 public RecordProtocol(Stream innerStream, Context context)
\r
63 this.innerStream = innerStream;
\r
64 this.context = context;
\r
69 #region Abstract Methods
\r
71 public abstract void SendRecord(HandshakeType type);
\r
72 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
\r
76 #region Reveive Record Methods
\r
78 public byte[] ReceiveRecord()
\r
80 if (this.context.ConnectionEnd)
\r
82 throw this.context.CreateException("The session is finished and it's no longer valid.");
\r
85 // Try to read the Record Content Type
\r
86 int type = this.innerStream.ReadByte();
\r
88 // There are no more data for read
\r
94 ContentType contentType = (ContentType)type;
\r
95 short protocol = this.readShort();
\r
96 short length = this.readShort();
\r
100 byte[] buffer = new byte[length];
\r
101 while (received != length)
\r
103 received += this.innerStream.Read(
\r
104 buffer, received, buffer.Length - received);
\r
107 TlsStream message = new TlsStream(buffer);
\r
109 // Check that the message has a valid protocol version
\r
110 if (protocol != this.context.Protocol &&
\r
111 this.context.ProtocolNegotiated)
\r
113 throw this.context.CreateException("Invalid protocol version on message received from server");
\r
116 // Decrypt message contents if needed
\r
117 if (contentType == ContentType.Alert && length == 2)
\r
122 if (this.context.IsActual &&
\r
123 contentType != ContentType.ChangeCipherSpec)
\r
125 message = this.decryptRecordFragment(
\r
127 message.ToArray());
\r
131 // Set last handshake message received to None
\r
132 this.context.LastHandshakeMsg = HandshakeType.None;
\r
135 byte[] result = message.ToArray();
\r
137 switch (contentType)
\r
139 case ContentType.Alert:
\r
141 (AlertLevel)message.ReadByte(),
\r
142 (AlertDescription)message.ReadByte());
\r
145 case ContentType.ChangeCipherSpec:
\r
146 // Reset sequence numbers
\r
147 this.context.ReadSequenceNumber = 0;
\r
150 case ContentType.ApplicationData:
\r
153 case ContentType.Handshake:
\r
154 while (!message.EOF)
\r
156 this.ProcessHandshakeMessage(message);
\r
159 // Update handshakes of current messages
\r
160 this.context.HandshakeMessages.Write(message.ToArray());
\r
164 throw this.context.CreateException("Unknown record received from server.");
\r
170 private short readShort()
\r
172 byte[] b = new byte[2];
\r
173 this.innerStream.Read(b, 0, b.Length);
\r
175 short val = BitConverter.ToInt16(b, 0);
\r
177 return System.Net.IPAddress.HostToNetworkOrder(val);
\r
180 private void processAlert(
\r
181 AlertLevel alertLevel,
\r
182 AlertDescription alertDesc)
\r
184 switch (alertLevel)
\r
186 case AlertLevel.Fatal:
\r
187 throw this.context.CreateException(alertLevel, alertDesc);
\r
189 case AlertLevel.Warning:
\r
193 case AlertDescription.CloseNotify:
\r
194 this.context.ConnectionEnd = true;
\r
203 #region Send Alert Methods
\r
205 public void SendAlert(AlertDescription description)
\r
207 this.SendAlert(new Alert(this.Context, description));
\r
210 public void SendAlert(
\r
212 AlertDescription description)
\r
214 this.SendAlert(new Alert(this.Context, level, description));
\r
217 public void SendAlert(Alert alert)
\r
220 this.SendRecord(ContentType.Alert, alert.ToArray());
\r
225 // Reset message contents
\r
231 #region Send Record Methods
\r
233 public void SendChangeCipherSpec()
\r
235 // Send Change Cipher Spec message
\r
236 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
\r
238 // Reset sequence numbers
\r
239 this.context.WriteSequenceNumber = 0;
\r
241 // Make the pending state to be the current state
\r
242 this.context.IsActual = true;
\r
244 // Send Finished message
\r
245 this.SendRecord(HandshakeType.Finished);
\r
248 public void SendRecord(ContentType contentType, byte[] recordData)
\r
250 if (this.context.ConnectionEnd)
\r
252 throw this.context.CreateException("The session is finished and it's no longer valid.");
\r
255 byte[] record = this.EncodeRecord(contentType, recordData);
\r
257 this.innerStream.Write(record, 0, record.Length);
\r
260 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
\r
262 return this.EncodeRecord(
\r
266 recordData.Length);
\r
269 public byte[] EncodeRecord(
\r
270 ContentType contentType,
\r
275 if (this.context.ConnectionEnd)
\r
277 throw this.context.CreateException("The session is finished and it's no longer valid.");
\r
280 TlsStream record = new TlsStream();
\r
282 int position = offset;
\r
284 while (position < ( offset + count ))
\r
286 short fragmentLength = 0;
\r
289 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
\r
291 fragmentLength = Context.MAX_FRAGMENT_SIZE;
\r
295 fragmentLength = (short)(count - position);
\r
298 // Fill the fragment data
\r
299 fragment = new byte[fragmentLength];
\r
300 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
\r
302 if (this.context.IsActual)
\r
304 // Encrypt fragment
\r
305 fragment = this.encryptRecordFragment(contentType, fragment);
\r
308 // Write tls message
\r
309 record.Write((byte)contentType);
\r
310 record.Write(this.context.Protocol);
\r
311 record.Write((short)fragment.Length);
\r
312 record.Write(fragment);
\r
314 // Update buffer position
\r
315 position += fragmentLength;
\r
318 return record.ToArray();
\r
323 #region Cryptography Methods
\r
325 private byte[] encryptRecordFragment(
\r
326 ContentType contentType,
\r
329 // Calculate message MAC
\r
330 byte[] mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
\r
332 // Encrypt the message
\r
333 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
\r
336 if (this.context.Cipher.CipherMode == CipherMode.CBC)
\r
338 byte[] iv = new byte[this.context.Cipher.IvSize];
\r
339 System.Array.Copy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
\r
340 this.context.Cipher.UpdateClientCipherIV(iv);
\r
343 // Update sequence number
\r
344 this.context.WriteSequenceNumber++;
\r
349 private TlsStream decryptRecordFragment(
\r
350 ContentType contentType,
\r
353 byte[] dcrFragment = null;
\r
354 byte[] dcrMAC = null;
\r
357 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
\r
360 if (this.context.Cipher.CipherMode == CipherMode.CBC)
\r
362 byte[] iv = new byte[this.context.Cipher.IvSize];
\r
363 System.Array.Copy(fragment, fragment.Length - iv.Length, iv, 0, iv.Length);
\r
364 this.context.Cipher.UpdateServerCipherIV(iv);
\r
368 byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
\r
370 // Check that the mac is correct
\r
371 if (mac.Length != dcrMAC.Length)
\r
373 throw new TlsException("Invalid MAC received from server.");
\r
375 for (int i = 0; i < mac.Length; i++)
\r
377 if (mac[i] != dcrMAC[i])
\r
379 throw new TlsException("Invalid MAC received from server.");
\r
383 // Update sequence number
\r
384 this.context.ReadSequenceNumber++;
\r
386 return new TlsStream(dcrFragment);
\r