2004-03-06 Carlos Guzman Alvarez <carlosga@telefonica.net>
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / RecordProtocol.cs
1 /* Transport Security Layer (TLS)\r
2  * Copyright (c) 2003-2004 Carlos Guzman Alvarez\r
3  * \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
11  * \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
14  * \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
23  */\r
24 \r
25 using System;\r
26 using System.IO;\r
27 using System.Security.Cryptography;\r
28 using System.Security.Cryptography.X509Certificates;\r
29 \r
30 using Mono.Security.Protocol.Tls.Handshake;\r
31 \r
32 namespace Mono.Security.Protocol.Tls\r
33 {\r
34         internal abstract class RecordProtocol\r
35         {\r
36                 #region Fields\r
37 \r
38                 protected Stream        innerStream;\r
39                 protected Context       context;\r
40 \r
41                 #endregion\r
42 \r
43                 #region Properties\r
44 \r
45                 public Stream InnerStream\r
46                 {\r
47                         get { return this.innerStream; }\r
48                         set { this.innerStream = value; }\r
49                 }\r
50 \r
51                 public Context Context\r
52                 {\r
53                         get { return this.context; }\r
54                         set { this.context = value; }\r
55                 }\r
56 \r
57                 #endregion\r
58 \r
59                 #region Constructors\r
60 \r
61                 public RecordProtocol(Stream innerStream, Context context)\r
62                 {\r
63                         this.innerStream        = innerStream;\r
64                         this.context            = context;\r
65                 }\r
66 \r
67                 #endregion\r
68 \r
69                 #region Abstract Methods\r
70 \r
71                 public abstract void SendRecord(HandshakeType type);\r
72                 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);\r
73                                 \r
74                 #endregion\r
75 \r
76                 #region Reveive Record Methods\r
77 \r
78                 public byte[] ReceiveRecord()\r
79                 {\r
80                         if (this.context.ConnectionEnd)\r
81                         {\r
82                                 throw this.context.CreateException("The session is finished and it's no longer valid.");\r
83                         }\r
84                         \r
85                         // Try to read the Record Content Type\r
86                         int type = this.innerStream.ReadByte();\r
87 \r
88                         // There are no more data for read\r
89                         if (type == -1)\r
90                         {\r
91                                 return null;\r
92                         }\r
93 \r
94                         ContentType     contentType     = (ContentType)type;\r
95                         short           protocol        = this.readShort();\r
96                         short           length          = this.readShort();\r
97                         \r
98                         // Read Record data\r
99                         int             received        = 0;\r
100                         byte[]  buffer          = new byte[length];\r
101                         while (received != length)\r
102                         {\r
103                                 received += this.innerStream.Read(\r
104                                         buffer, received, buffer.Length - received);\r
105                         }\r
106 \r
107                         TlsStream message = new TlsStream(buffer);\r
108                 \r
109                         // Check that the message has a valid protocol version\r
110                         if (protocol != this.context.Protocol && \r
111                                 this.context.ProtocolNegotiated)\r
112                         {\r
113                                 throw this.context.CreateException("Invalid protocol version on message received from server");\r
114                         }\r
115 \r
116                         // Decrypt message contents if needed\r
117                         if (contentType == ContentType.Alert && length == 2)\r
118                         {\r
119                         }\r
120                         else\r
121                         {\r
122                                 if (this.context.IsActual &&\r
123                                         contentType != ContentType.ChangeCipherSpec)\r
124                                 {\r
125                                         message = this.decryptRecordFragment(\r
126                                                 contentType, \r
127                                                 message.ToArray());\r
128                                 }\r
129                         }\r
130 \r
131                         // Set last handshake message received to None\r
132                         this.context.LastHandshakeMsg = HandshakeType.None;\r
133                         \r
134                         // Process record\r
135                         byte[] result = message.ToArray();\r
136 \r
137                         switch (contentType)\r
138                         {\r
139                                 case ContentType.Alert:\r
140                                         this.processAlert(\r
141                                                 (AlertLevel)message.ReadByte(),\r
142                                                 (AlertDescription)message.ReadByte());\r
143                                         break;\r
144 \r
145                                 case ContentType.ChangeCipherSpec:\r
146                                         // Reset sequence numbers\r
147                                         this.context.ReadSequenceNumber = 0;\r
148                                         break;\r
149 \r
150                                 case ContentType.ApplicationData:\r
151                                         break;\r
152 \r
153                                 case ContentType.Handshake:\r
154                                         while (!message.EOF)\r
155                                         {\r
156                                                 this.ProcessHandshakeMessage(message);\r
157                                         }\r
158 \r
159                                         // Update handshakes of current messages\r
160                                         this.context.HandshakeMessages.Write(message.ToArray());\r
161                                         break;\r
162 \r
163                                 default:\r
164                                         throw this.context.CreateException("Unknown record received from server.");\r
165                         }\r
166 \r
167                         return result;\r
168                 }\r
169 \r
170                 private short readShort()\r
171                 {\r
172                         byte[] b = new byte[2];\r
173                         this.innerStream.Read(b, 0, b.Length);\r
174 \r
175                         short val = BitConverter.ToInt16(b, 0);\r
176 \r
177                         return System.Net.IPAddress.HostToNetworkOrder(val);\r
178                 }\r
179 \r
180                 private void processAlert(\r
181                         AlertLevel                      alertLevel, \r
182                         AlertDescription        alertDesc)\r
183                 {\r
184                         switch (alertLevel)\r
185                         {\r
186                                 case AlertLevel.Fatal:\r
187                                         throw this.context.CreateException(alertLevel, alertDesc);                                      \r
188 \r
189                                 case AlertLevel.Warning:\r
190                                 default:\r
191                                 switch (alertDesc)\r
192                                 {\r
193                                         case AlertDescription.CloseNotify:\r
194                                                 this.context.ConnectionEnd = true;\r
195                                                 break;\r
196                                 }\r
197                                 break;\r
198                         }\r
199                 }\r
200 \r
201                 #endregion\r
202 \r
203                 #region Send Alert Methods\r
204 \r
205                 public void SendAlert(AlertDescription description)\r
206                 {\r
207                         this.SendAlert(new Alert(this.Context, description));\r
208                 }\r
209 \r
210                 public void SendAlert(\r
211                         AlertLevel                      level, \r
212                         AlertDescription        description)\r
213                 {\r
214                         this.SendAlert(new Alert(this.Context, level, description));\r
215                 }\r
216 \r
217                 public void SendAlert(Alert alert)\r
218                 {                       \r
219                         // Write record\r
220                         this.SendRecord(ContentType.Alert, alert.ToArray());\r
221 \r
222                         // Update session\r
223                         alert.Update();\r
224 \r
225                         // Reset message contents\r
226                         alert.Reset();\r
227                 }\r
228 \r
229                 #endregion\r
230 \r
231                 #region Send Record Methods\r
232 \r
233                 public void SendChangeCipherSpec()\r
234                 {\r
235                         // Send Change Cipher Spec message\r
236                         this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});\r
237 \r
238                         // Reset sequence numbers\r
239                         this.context.WriteSequenceNumber = 0;\r
240 \r
241                         // Make the pending state to be the current state\r
242                         this.context.IsActual = true;\r
243 \r
244                         // Send Finished message\r
245                         this.SendRecord(HandshakeType.Finished);                        \r
246                 }\r
247 \r
248                 public void SendRecord(ContentType contentType, byte[] recordData)\r
249                 {\r
250                         if (this.context.ConnectionEnd)\r
251                         {\r
252                                 throw this.context.CreateException("The session is finished and it's no longer valid.");\r
253                         }\r
254 \r
255                         byte[] record = this.EncodeRecord(contentType, recordData);\r
256 \r
257                         this.innerStream.Write(record, 0, record.Length);\r
258                 }\r
259 \r
260                 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)\r
261                 {\r
262                         return this.EncodeRecord(\r
263                                 contentType,\r
264                                 recordData,\r
265                                 0,\r
266                                 recordData.Length);\r
267                 }\r
268 \r
269                 public byte[] EncodeRecord(\r
270                         ContentType     contentType, \r
271                         byte[]          recordData,\r
272                         int                     offset,\r
273                         int                     count)\r
274                 {\r
275                         if (this.context.ConnectionEnd)\r
276                         {\r
277                                 throw this.context.CreateException("The session is finished and it's no longer valid.");\r
278                         }\r
279 \r
280                         TlsStream record = new TlsStream();\r
281 \r
282                         int     position = offset;\r
283 \r
284                         while (position < ( offset + count ))\r
285                         {\r
286                                 short   fragmentLength = 0;\r
287                                 byte[]  fragment;\r
288 \r
289                                 if ((count - position) > Context.MAX_FRAGMENT_SIZE)\r
290                                 {\r
291                                         fragmentLength = Context.MAX_FRAGMENT_SIZE;\r
292                                 }\r
293                                 else\r
294                                 {\r
295                                         fragmentLength = (short)(count - position);\r
296                                 }\r
297 \r
298                                 // Fill the fragment data\r
299                                 fragment = new byte[fragmentLength];\r
300                                 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);\r
301 \r
302                                 if (this.context.IsActual)\r
303                                 {\r
304                                         // Encrypt fragment\r
305                                         fragment = this.encryptRecordFragment(contentType, fragment);\r
306                                 }\r
307 \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
313 \r
314                                 // Update buffer position\r
315                                 position += fragmentLength;\r
316                         }\r
317 \r
318                         return record.ToArray();\r
319                 }\r
320                 \r
321                 #endregion\r
322 \r
323                 #region Cryptography Methods\r
324 \r
325                 private byte[] encryptRecordFragment(\r
326                         ContentType     contentType, \r
327                         byte[]          fragment)\r
328                 {\r
329                         // Calculate message MAC\r
330                         byte[] mac      = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);\r
331 \r
332                         // Encrypt the message\r
333                         byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);\r
334 \r
335                         // Set new IV\r
336                         if (this.context.Cipher.CipherMode == CipherMode.CBC)\r
337                         {\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
341                         }\r
342 \r
343                         // Update sequence number\r
344                         this.context.WriteSequenceNumber++;\r
345 \r
346                         return ecr;\r
347                 }\r
348 \r
349                 private TlsStream decryptRecordFragment(\r
350                         ContentType     contentType, \r
351                         byte[]          fragment)\r
352                 {\r
353                         byte[]  dcrFragment     = null;\r
354                         byte[]  dcrMAC          = null;\r
355 \r
356                         // Decrypt message\r
357                         this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);\r
358 \r
359                         // Set new IV\r
360                         if (this.context.Cipher.CipherMode == CipherMode.CBC)\r
361                         {\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
365                         }\r
366                         \r
367                         // Check MAC code\r
368                         byte[] mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);\r
369 \r
370                         // Check that the mac is correct\r
371                         if (mac.Length != dcrMAC.Length)\r
372                         {\r
373                                 throw new TlsException("Invalid MAC received from server.");\r
374                         }\r
375                         for (int i = 0; i < mac.Length; i++)\r
376                         {\r
377                                 if (mac[i] != dcrMAC[i])\r
378                                 {\r
379                                         throw new TlsException("Invalid MAC received from server.");\r
380                                 }\r
381                         }\r
382 \r
383                         // Update sequence number\r
384                         this.context.ReadSequenceNumber++;\r
385 \r
386                         return new TlsStream(dcrFragment);\r
387                 }\r
388 \r
389                 #endregion\r
390         }\r
391 }\r