* support-test-*.cs: Rename from test-*-p2.cs.
[mono.git] / mcs / class / Mono.Security / Mono.Security.Protocol.Tls / RecordProtocol.cs
1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3
4 //
5 // Permission is hereby granted, free of charge, to any person obtaining
6 // a copy of this software and associated documentation files (the
7 // "Software"), to deal in the Software without restriction, including
8 // without limitation the rights to use, copy, modify, merge, publish,
9 // distribute, sublicense, and/or sell copies of the Software, and to
10 // permit persons to whom the Software is furnished to do so, subject to
11 // the following conditions:
12 // 
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
15 // 
16 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
23 //
24
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Security.Cryptography;
29 using System.Security.Cryptography.X509Certificates;
30 using System.Threading;
31
32 using Mono.Security.Protocol.Tls.Handshake;
33
34 namespace Mono.Security.Protocol.Tls
35 {
36         internal abstract class RecordProtocol
37         {
38                 #region Fields
39
40                 protected Stream        innerStream;
41                 protected Context       context;
42
43                 #endregion
44
45                 #region Properties
46
47                 public Context Context
48                 {
49                         get { return this.context; }
50                         set { this.context = value; }
51                 }
52
53                 #endregion
54
55                 #region Constructors
56
57                 public RecordProtocol(Stream innerStream, Context context)
58                 {
59                         this.innerStream                        = innerStream;
60                         this.context                            = context;
61                         this.context.RecordProtocol = this;
62                 }
63
64                 #endregion
65
66                 #region Abstract Methods
67
68                 public virtual void SendRecord(HandshakeType type)
69                 {
70
71                         IAsyncResult ar = this.BeginSendRecord(type, null, null);
72
73                         this.EndSendRecord(ar);
74
75                 }
76
77                 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
78                 protected abstract void ProcessChangeCipherSpec();
79
80                 public virtual HandshakeMessage GetMessage(HandshakeType type)
81                 {
82                         throw new NotSupportedException();
83                 }
84
85                 #endregion
86
87                 #region Receive Record Async Result
88                 private class ReceiveRecordAsyncResult : IAsyncResult
89                 {
90                         private AsyncCallback _userCallback;
91                         private object _userState;
92                         private Exception _asyncException;
93                         private ManualResetEvent _complete;
94                         private byte[] _resultingBuffer;
95                         private Stream _record;
96
97                         private byte[] _initialBuffer;
98
99                         public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
100                         {
101                                 _userCallback = userCallback;
102                                 _userState = userState;
103                                 _complete = new ManualResetEvent(false);
104                                 _initialBuffer = initialBuffer;
105                                 _record = record;
106                         }
107
108                         public Stream Record
109                         {
110                                 get { return _record; }
111                         }
112
113                         public byte[] ResultingBuffer
114                         {
115                                 get { return _resultingBuffer; }
116                         }
117
118                         public byte[] InitialBuffer
119                         {
120                                 get { return _initialBuffer; }
121                         }
122
123                         public object AsyncState
124                         {
125                                 get { return _userState; }
126                         }
127
128                         public Exception AsyncException
129                         {
130                                 get { return _asyncException; }
131                         }
132
133                         public bool CompletedWithError
134                         {
135                                 get { return null != _asyncException; }
136                         }
137
138                         public WaitHandle AsyncWaitHandle
139                         {
140                                 get { return _complete; }
141                         }
142
143                         public bool CompletedSynchronously
144                         {
145                                 get { return false; }
146                         }
147
148                         public bool IsCompleted
149                         {
150                                 get { return _complete.WaitOne(0, false); }
151                         }
152
153                         private void SetComplete(Exception ex, byte[] resultingBuffer)
154                         {
155                                 if (this.IsCompleted)
156                                         return;
157
158                                 lock (this)
159                                 {
160                                         if (this.IsCompleted)
161                                                 return;
162
163                                         _asyncException = ex;
164                                         _resultingBuffer = resultingBuffer;
165                                         _complete.Set();
166                                 }
167
168                                 if (null != _userCallback)
169                                         _userCallback (this);
170
171                         }
172
173                         public void SetComplete(Exception ex)
174                         {
175                                 SetComplete(ex, null);
176                         }
177
178                         public void SetComplete(byte[] resultingBuffer)
179                         {
180                                 SetComplete(null, resultingBuffer);
181                         }
182
183                         public void SetComplete()
184                         {
185                                 SetComplete(null, null);
186                         }
187                 }
188                 #endregion
189
190                 #region Receive Record Async Result
191                 private class SendRecordAsyncResult : IAsyncResult
192                 {
193                         private AsyncCallback _userCallback;
194                         private object _userState;
195                         private Exception _asyncException;
196                         private ManualResetEvent _complete;
197                         private HandshakeMessage _message;
198
199                         public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
200                         {
201                                 _userCallback = userCallback;
202                                 _userState = userState;
203                                 _complete = new ManualResetEvent(false);
204                                 _message = message;
205                         }
206
207                         public HandshakeMessage Message
208                         {
209                                 get { return _message; }
210                         }
211
212                         public object AsyncState
213                         {
214                                 get { return _userState; }
215                         }
216
217                         public Exception AsyncException
218                         {
219                                 get { return _asyncException; }
220                         }
221
222                         public bool CompletedWithError
223                         {
224                                 get { return null != _asyncException; }
225                         }
226
227                         public WaitHandle AsyncWaitHandle
228                         {
229                                 get { return _complete; }
230                         }
231
232                         public bool CompletedSynchronously
233                         {
234                                 get { return false; }
235                         }
236
237                         public bool IsCompleted
238                         {
239                                 get { return _complete.WaitOne(0, false); }
240                         }
241
242                         public void SetComplete(Exception ex)
243                         {
244                                 if (this.IsCompleted)
245                                         return;
246
247                                 lock (this)
248                                 {
249                                         if (this.IsCompleted)
250                                                 return;
251
252                                         _asyncException = ex;
253                                         _complete.Set();
254                                 }
255
256                                 if (null != _userCallback)
257                                         _userCallback (this);
258
259                         }
260
261                         public void SetComplete()
262                         {
263                                 SetComplete(null);
264                         }
265                 }
266                 #endregion
267
268                 #region Reveive Record Methods
269
270                 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
271                 {
272                         if (this.context.ConnectionEnd)
273                         {
274                                 throw new TlsException(
275                                         AlertDescription.InternalError,
276                                         "The session is finished and it's no longer valid.");
277                         }
278
279                         byte[] recordTypeBuffer = new byte[1];
280
281                         ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
282
283                         record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);
284
285                         return internalResult;
286                 }
287
288                 private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
289                 {
290                         ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
291                         Stream record = internalResult.Record;
292
293                         try
294                         {
295                                 
296                                 int bytesRead = internalResult.Record.EndRead(asyncResult);
297
298                                 //We're at the end of the stream. Time to bail.
299                                 if (bytesRead == 0)
300                                 {
301                                         internalResult.SetComplete((byte[])null);
302                                         return;
303                                 }
304
305                                 // Try to read the Record Content Type
306                                 int type = internalResult.InitialBuffer[0];
307
308                                 // Set last handshake message received to None
309                                 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
310
311                                 ContentType     contentType     = (ContentType)type;
312                                 byte[] buffer = this.ReadRecordBuffer(type, record);
313                                 if (buffer == null)
314                                 {
315                                         // record incomplete (at the moment)
316                                         internalResult.SetComplete((byte[])null);
317                                         return;
318                                 }
319
320                                 // Decrypt message contents if needed
321                                 if (contentType == ContentType.Alert && buffer.Length == 2)
322                                 {
323                                 }
324                                 else
325                                 {
326                                         if (this.context.IsActual && contentType != ContentType.ChangeCipherSpec)
327                                         {
328                                                 buffer = this.decryptRecordFragment(contentType, buffer);
329                                                 DebugHelper.WriteLine("Decrypted record data", buffer);
330                                         }
331                                 }
332
333                                 // Process record
334                                 switch (contentType)
335                                 {
336                                         case ContentType.Alert:
337                                                 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
338                                                 if (record.CanSeek) 
339                                                 {
340                                                         // don't reprocess that memory block
341                                                         record.SetLength (0); 
342                                                 }
343                                                 buffer = null;
344                                                 break;
345
346                                         case ContentType.ChangeCipherSpec:
347                                                 this.ProcessChangeCipherSpec();
348                                                 break;
349
350                                         case ContentType.ApplicationData:
351                                                 break;
352
353                                         case ContentType.Handshake:
354                                                 TlsStream message = new TlsStream (buffer);
355                                                 while (!message.EOF)
356                                                 {
357                                                         this.ProcessHandshakeMessage(message);
358                                                 }
359                                                 break;
360
361         // FIXME / MCS bug - http://bugzilla.ximian.com/show_bug.cgi?id=67711
362         //                              case (ContentType)0x80:
363         //                                      this.context.HandshakeMessages.Write (result);
364         //                                      break;
365
366                                         default:
367                                                 if (contentType != (ContentType)0x80)
368                                                 {
369                                                         throw new TlsException(
370                                                                 AlertDescription.UnexpectedMessage,
371                                                                 "Unknown record received from server.");
372                                                 }
373                                                 this.context.HandshakeMessages.Write (buffer);
374                                                 break;
375                                 }
376
377                                 internalResult.SetComplete(buffer);
378                         }
379                         catch (Exception ex)
380                         {
381                                 internalResult.SetComplete(ex);
382                         }
383
384                 }
385
386                 public byte[] EndReceiveRecord(IAsyncResult asyncResult)
387                 {
388                         ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;
389
390                         if (null == internalResult)
391                                 throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");
392
393                         internalResult.AsyncWaitHandle.WaitOne();
394
395                         if (internalResult.CompletedWithError)
396                                 throw internalResult.AsyncException;
397                         else
398                                 return internalResult.ResultingBuffer;
399                 }
400
401                 public byte[] ReceiveRecord(Stream record)
402                 {
403
404                         IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
405                         return this.EndReceiveRecord(ar);
406
407                 }
408
409                 private byte[] ReadRecordBuffer (int contentType, Stream record)
410                 {
411                         switch (contentType)
412                         {
413                                 case 0x80:
414                                         return this.ReadClientHelloV2(record);
415
416                                 default:
417                                         if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
418                                         {
419                                                 throw new TlsException(AlertDescription.DecodeError);
420                                         }
421                                         return this.ReadStandardRecordBuffer(record);
422                         }
423                 }
424
425                 private byte[] ReadClientHelloV2 (Stream record)
426                 {
427                         int msgLength = record.ReadByte ();
428                         // process further only if the whole record is available
429                         if (record.CanSeek && (msgLength + 1 > record.Length)) 
430                         {
431                                 return null;
432                         }
433
434                         byte[] message = new byte[msgLength];
435                         record.Read (message, 0, msgLength);
436
437                         int msgType             = message [0];
438                         if (msgType != 1)
439                         {
440                                 throw new TlsException(AlertDescription.DecodeError);
441                         }
442                         int protocol = (message [1] << 8 | message [2]);
443                         int cipherSpecLength = (message [3] << 8 | message [4]);
444                         int sessionIdLength = (message [5] << 8 | message [6]);
445                         int challengeLength = (message [7] << 8 | message [8]);
446                         int length = (challengeLength > 32) ? 32 : challengeLength;
447
448                         // Read CipherSpecs
449                         byte[] cipherSpecV2 = new byte[cipherSpecLength];
450                         Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
451
452                         // Read session ID
453                         byte[] sessionId = new byte[sessionIdLength];
454                         Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
455
456                         // Read challenge ID
457                         byte[] challenge = new byte[challengeLength];
458                         Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
459                 
460                         if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
461                         {
462                                 throw new TlsException(AlertDescription.DecodeError);
463                         }
464
465                         // Updated the Session ID
466                         if (sessionId.Length > 0)
467                         {
468                                 this.context.SessionId = sessionId;
469                         }
470
471                         // Update the protocol version
472                         this.Context.ChangeProtocol((short)protocol);
473
474                         // Select the Cipher suite
475                         this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
476
477                         // Updated the Client Random
478                         this.context.ClientRandom = new byte [32]; // Always 32
479                         // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
480                         // 2. right justify (0) challenge in ClientRandom if less than 32
481                         Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
482
483                         // Set 
484                         this.context.LastHandshakeMsg = HandshakeType.ClientHello;
485                         this.context.ProtocolNegotiated = true;
486
487                         return message;
488                 }
489
490                 private byte[] ReadStandardRecordBuffer (Stream record)
491                 {
492                         short protocol  = this.ReadShort(record);
493                         short length    = this.ReadShort(record);
494
495                         // process further only if the whole record is available
496                         // note: the first 5 bytes aren't part of the length
497                         if (record.CanSeek && (length + 5 > record.Length)) 
498                         {
499                                 return null;
500                         }
501                         
502                         // Read Record data
503                         int     totalReceived = 0;
504                         byte[]  buffer          = new byte[length];
505                         while (totalReceived != length)
506                         {
507                                 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
508
509                                 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
510                                 if (0 == justReceived)
511                                 {
512                                         throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
513                                 }
514
515                                 totalReceived += justReceived;
516                         }
517
518                         // Check that the message has a valid protocol version
519                         if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
520                         {
521                                 throw new TlsException(
522                                         AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
523                         }
524
525                         DebugHelper.WriteLine("Record data", buffer);
526
527                         return buffer;
528                 }
529
530                 private short ReadShort(Stream record)
531                 {
532                         byte[] b = new byte[2];
533                         record.Read(b, 0, b.Length);
534
535                         short val = BitConverter.ToInt16(b, 0);
536
537                         return System.Net.IPAddress.HostToNetworkOrder(val);
538                 }
539
540                 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
541                 {
542                         switch (alertLevel)
543                         {
544                                 case AlertLevel.Fatal:
545                                         throw new TlsException(alertLevel, alertDesc);
546
547                                 case AlertLevel.Warning:
548                                 default:
549                                 switch (alertDesc)
550                                 {
551                                         case AlertDescription.CloseNotify:
552                                                 this.context.ConnectionEnd = true;
553                                                 break;
554                                 }
555                                 break;
556                         }
557                 }
558
559                 #endregion
560
561                 #region Send Alert Methods
562
563                 public void SendAlert(AlertDescription description)
564                 {
565                         this.SendAlert(new Alert(description));
566                 }
567
568                 public void SendAlert(
569                         AlertLevel                      level, 
570                         AlertDescription        description)
571                 {
572                         this.SendAlert(new Alert(level, description));
573                 }
574
575                 public void SendAlert(Alert alert)
576                 {
577                         DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
578
579                         // Write record
580                         this.SendRecord(
581                                 ContentType.Alert, 
582                                 new byte[]{(byte)alert.Level, (byte)alert.Description});
583
584                         if (alert.IsCloseNotify)
585                         {
586                                 this.context.ConnectionEnd = true;
587                         }
588                 }
589
590                 #endregion
591
592                 #region Send Record Methods
593
594                 public void SendChangeCipherSpec()
595                 {
596                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
597
598                         // Send Change Cipher Spec message as a plain message
599                         this.context.IsActual = false;
600
601                         // Send Change Cipher Spec message
602                         this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
603
604                         // Reset sequence numbers
605                         this.context.WriteSequenceNumber = 0;
606
607                         // Make the pending state to be the current state
608                         this.context.IsActual = true;
609
610                         // Send Finished message
611                         this.SendRecord(HandshakeType.Finished);                        
612                 }
613
614                 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
615                 {
616                         HandshakeMessage msg = this.GetMessage(handshakeType);
617
618                         msg.Process();
619
620                         DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
621
622                         SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
623
624                         this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
625
626                         return internalResult;
627                 }
628
629                 private void InternalSendRecordCallback(IAsyncResult ar)
630                 {
631                         SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
632                         
633                         try
634                         {
635                                 this.EndSendRecord(ar);
636
637                                 // Update session
638                                 internalResult.Message.Update();
639
640                                 // Reset message contents
641                                 internalResult.Message.Reset();
642
643                                 internalResult.SetComplete();
644                         }
645                         catch (Exception ex)
646                         {
647                                 internalResult.SetComplete(ex);
648                         }
649                 }
650
651                 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
652                 {
653                         if (this.context.ConnectionEnd)
654                         {
655                                 throw new TlsException(
656                                         AlertDescription.InternalError,
657                                         "The session is finished and it's no longer valid.");
658                         }
659
660                         byte[] record = this.EncodeRecord(contentType, recordData);
661
662                         return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
663                 }
664
665                 public void EndSendRecord(IAsyncResult asyncResult)
666                 {
667                         if (asyncResult is SendRecordAsyncResult)
668                         {
669                                 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
670                                 internalResult.AsyncWaitHandle.WaitOne();
671                                 if (internalResult.CompletedWithError)
672                                         throw internalResult.AsyncException;
673                         }
674                         else
675                         {
676                                 this.innerStream.EndWrite(asyncResult);
677                         }
678                 }
679
680                 public void SendRecord(ContentType contentType, byte[] recordData)
681                 {
682                         IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
683
684                         this.EndSendRecord(ar);
685                 }
686
687                 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
688                 {
689                         return this.EncodeRecord(
690                                 contentType,
691                                 recordData,
692                                 0,
693                                 recordData.Length);
694                 }
695
696                 public byte[] EncodeRecord(
697                         ContentType     contentType, 
698                         byte[]          recordData,
699                         int                     offset,
700                         int                     count)
701                 {
702                         if (this.context.ConnectionEnd)
703                         {
704                                 throw new TlsException(
705                                         AlertDescription.InternalError,
706                                         "The session is finished and it's no longer valid.");
707                         }
708
709                         TlsStream record = new TlsStream();
710
711                         int     position = offset;
712
713                         while (position < ( offset + count ))
714                         {
715                                 short   fragmentLength = 0;
716                                 byte[]  fragment;
717
718                                 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
719                                 {
720                                         fragmentLength = Context.MAX_FRAGMENT_SIZE;
721                                 }
722                                 else
723                                 {
724                                         fragmentLength = (short)(count - position);
725                                 }
726
727                                 // Fill the fragment data
728                                 fragment = new byte[fragmentLength];
729                                 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
730
731                                 if (this.context.IsActual)
732                                 {
733                                         // Encrypt fragment
734                                         fragment = this.encryptRecordFragment(contentType, fragment);
735                                 }
736
737                                 // Write tls message
738                                 record.Write((byte)contentType);
739                                 record.Write(this.context.Protocol);
740                                 record.Write((short)fragment.Length);
741                                 record.Write(fragment);
742
743                                 DebugHelper.WriteLine("Record data", fragment);
744
745                                 // Update buffer position
746                                 position += fragmentLength;
747                         }
748
749                         return record.ToArray();
750                 }
751                 
752                 #endregion
753
754                 #region Cryptography Methods
755
756                 private byte[] encryptRecordFragment(
757                         ContentType     contentType, 
758                         byte[]          fragment)
759                 {
760                         byte[] mac      = null;
761
762                         // Calculate message MAC
763                         if (this.Context is ClientContext)
764                         {
765                                 mac     = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
766                         }       
767                         else
768                         {
769                                 mac     = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
770                         }
771
772                         DebugHelper.WriteLine(">>>> Record MAC", mac);
773
774                         // Encrypt the message
775                         byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
776
777                         // Set new Client Cipher IV
778                         if (this.context.Cipher.CipherMode == CipherMode.CBC)
779                         {
780                                 byte[] iv = new byte[this.context.Cipher.IvSize];
781                                 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
782
783                                 this.context.Cipher.UpdateClientCipherIV(iv);
784                         }
785
786                         // Update sequence number
787                         this.context.WriteSequenceNumber++;
788
789                         return ecr;
790                 }
791
792                 private byte[] decryptRecordFragment(
793                         ContentType     contentType, 
794                         byte[]          fragment)
795                 {
796                         byte[]  dcrFragment             = null;
797                         byte[]  dcrMAC                  = null;
798                         bool    badRecordMac    = false;
799
800                         try
801                         {
802                                 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
803                         }
804                         catch
805                         {
806                                 if (this.context is ServerContext)
807                                 {
808                                         this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
809                                 }
810
811                                 throw;
812                         }
813                         
814                         // Generate record MAC
815                         byte[] mac = null;
816
817                         if (this.Context is ClientContext)
818                         {
819                                 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
820                         }
821                         else
822                         {
823                                 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
824                         }
825
826                         DebugHelper.WriteLine(">>>> Record MAC", mac);
827
828                         // Check record MAC
829                         if (mac.Length != dcrMAC.Length)
830                         {
831                                 badRecordMac = true;
832                         }
833                         else
834                         {
835                                 for (int i = 0; i < mac.Length; i++)
836                                 {
837                                         if (mac[i] != dcrMAC[i])
838                                         {
839                                                 badRecordMac = true;
840                                                 break;
841                                         }
842                                 }
843                         }
844
845                         if (badRecordMac)
846                         {
847                                 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
848                         }
849
850                         // Update sequence number
851                         this.context.ReadSequenceNumber++;
852
853                         return dcrFragment;
854                 }
855
856                 #endregion
857
858                 #region CipherSpecV2 processing
859
860                 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
861                 {
862                         TlsStream codes = new TlsStream(buffer);
863
864                         string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
865
866                         while (codes.Position < codes.Length)
867                         {
868                                 byte check = codes.ReadByte();
869
870                                 if (check == 0)
871                                 {
872                                         // SSL/TLS cipher spec
873                                         short code = codes.ReadInt16(); 
874                                         int index = this.Context.SupportedCiphers.IndexOf(code);
875                                         if (index != -1)
876                                         {
877                                                 this.Context.Cipher     = this.Context.SupportedCiphers[index];
878                                                 break;
879                                         }
880                                 }
881                                 else
882                                 {
883                                         byte[] tmp = new byte[2];
884                                         codes.Read(tmp, 0, tmp.Length);
885
886                                         int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
887                                         CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
888
889                                         if (cipher != null)
890                                         {
891                                                 this.Context.Cipher = cipher;
892                                                 break;
893                                         }
894                                 }
895                         }
896
897                         if (this.Context.Cipher == null)
898                         {
899                                 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
900                         }
901                 }
902
903                 private CipherSuite MapV2CipherCode(string prefix, int code)
904                 {
905                         try
906                         {
907                                 switch (code)
908                                 {
909                                         case 65664:
910                                                 // TLS_RC4_128_WITH_MD5
911                                                 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
912                                         
913                                         case 131200:
914                                                 // TLS_RC4_128_EXPORT40_WITH_MD5
915                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
916                                         
917                                         case 196736:
918                                                 // TLS_RC2_CBC_128_CBC_WITH_MD5
919                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
920                                         
921                                         case 262272:
922                                                 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
923                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
924                                         
925                                         case 327808:
926                                                 // TLS_IDEA_128_CBC_WITH_MD5
927                                                 return null;
928                                         
929                                         case 393280:
930                                                 // TLS_DES_64_CBC_WITH_MD5
931                                                 return null;
932
933                                         case 458944:
934                                                 // TLS_DES_192_EDE3_CBC_WITH_MD5
935                                                 return null;
936
937                                         default:
938                                                 return null;
939                                 }
940                         }
941                         catch
942                         {
943                                 return null;
944                         }
945                 }
946
947                 #endregion
948         }
949 }