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