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