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