New tests.
[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.ReceivedConnectionEnd)
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.ReceivedConnectionEnd = 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                                 this.context.SentConnectionEnd = true;
629                         }
630                 }
631
632                 #endregion
633
634                 #region Send Record Methods
635
636                 public void SendChangeCipherSpec()
637                 {
638                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
639
640                         // Send Change Cipher Spec message with the current cipher
641                         // or as plain text if this is the initial negotiation
642                         this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
643
644                         Context ctx = this.context;
645
646                         // Reset sequence numbers
647                         ctx.WriteSequenceNumber = 0;
648
649                         // all further data sent will be encrypted with the negotiated
650                         // security parameters (now the current parameters)
651                         if (ctx is ClientContext) {
652                                 ctx.StartSwitchingSecurityParameters (true);
653                         } else {
654                                 ctx.EndSwitchingSecurityParameters (false);
655                         }
656                 }
657
658                 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
659                 {
660                         HandshakeMessage msg = this.GetMessage(handshakeType);
661
662                         msg.Process();
663
664                         DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
665
666                         SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
667
668                         this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
669
670                         return internalResult;
671                 }
672
673                 private void InternalSendRecordCallback(IAsyncResult ar)
674                 {
675                         SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
676                         
677                         try
678                         {
679                                 this.EndSendRecord(ar);
680
681                                 // Update session
682                                 internalResult.Message.Update();
683
684                                 // Reset message contents
685                                 internalResult.Message.Reset();
686
687                                 internalResult.SetComplete();
688                         }
689                         catch (Exception ex)
690                         {
691                                 internalResult.SetComplete(ex);
692                         }
693                 }
694
695                 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
696                 {
697                         if (this.context.SentConnectionEnd)
698                         {
699                                 throw new TlsException(
700                                         AlertDescription.InternalError,
701                                         "The session is finished and it's no longer valid.");
702                         }
703
704                         byte[] record = this.EncodeRecord(contentType, recordData);
705
706                         return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
707                 }
708
709                 public void EndSendRecord(IAsyncResult asyncResult)
710                 {
711                         if (asyncResult is SendRecordAsyncResult)
712                         {
713                                 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
714                                 if (!internalResult.IsCompleted)
715                                         internalResult.AsyncWaitHandle.WaitOne();
716                                 if (internalResult.CompletedWithError)
717                                         throw internalResult.AsyncException;
718                         }
719                         else
720                         {
721                                 this.innerStream.EndWrite(asyncResult);
722                         }
723                 }
724
725                 public void SendRecord(ContentType contentType, byte[] recordData)
726                 {
727                         IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
728
729                         this.EndSendRecord(ar);
730                 }
731
732                 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
733                 {
734                         return this.EncodeRecord(
735                                 contentType,
736                                 recordData,
737                                 0,
738                                 recordData.Length);
739                 }
740
741                 public byte[] EncodeRecord(
742                         ContentType     contentType, 
743                         byte[]          recordData,
744                         int                     offset,
745                         int                     count)
746                 {
747                         if (this.context.SentConnectionEnd)
748                         {
749                                 throw new TlsException(
750                                         AlertDescription.InternalError,
751                                         "The session is finished and it's no longer valid.");
752                         }
753
754                         TlsStream record = new TlsStream();
755
756                         int     position = offset;
757
758                         while (position < ( offset + count ))
759                         {
760                                 short   fragmentLength = 0;
761                                 byte[]  fragment;
762
763                                 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
764                                 {
765                                         fragmentLength = Context.MAX_FRAGMENT_SIZE;
766                                 }
767                                 else
768                                 {
769                                         fragmentLength = (short)(count + offset - position);
770                                 }
771
772                                 // Fill the fragment data
773                                 fragment = new byte[fragmentLength];
774                                 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
775
776                                 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
777                                 {
778                                         // Encrypt fragment
779                                         fragment = this.encryptRecordFragment (contentType, fragment);
780                                 }
781
782                                 // Write tls message
783                                 record.Write((byte)contentType);
784                                 record.Write(this.context.Protocol);
785                                 record.Write((short)fragment.Length);
786                                 record.Write(fragment);
787
788                                 DebugHelper.WriteLine("Record data", fragment);
789
790                                 // Update buffer position
791                                 position += fragmentLength;
792                         }
793
794                         return record.ToArray();
795                 }
796                 
797                 #endregion
798
799                 #region Cryptography Methods
800
801                 private byte[] encryptRecordFragment(
802                         ContentType     contentType, 
803                         byte[]          fragment)
804                 {
805                         byte[] mac      = null;
806
807                         // Calculate message MAC
808                         if (this.Context is ClientContext)
809                         {
810                                 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
811                         }       
812                         else
813                         {
814                                 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
815                         }
816
817                         DebugHelper.WriteLine(">>>> Record MAC", mac);
818
819                         // Encrypt the message
820                         byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
821
822                         // Update sequence number
823                         this.context.WriteSequenceNumber++;
824
825                         return ecr;
826                 }
827
828                 private byte[] decryptRecordFragment(
829                         ContentType     contentType, 
830                         byte[]          fragment)
831                 {
832                         byte[]  dcrFragment             = null;
833                         byte[]  dcrMAC                  = null;
834
835                         try
836                         {
837                                 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
838                         }
839                         catch
840                         {
841                                 if (this.context is ServerContext)
842                                 {
843                                         this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
844                                 }
845
846                                 throw;
847                         }
848                         
849                         // Generate record MAC
850                         byte[] mac = null;
851
852                         if (this.Context is ClientContext)
853                         {
854                                 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
855                         }
856                         else
857                         {
858                                 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
859                         }
860
861                         DebugHelper.WriteLine(">>>> Record MAC", mac);
862
863                         // Check record MAC
864                         if (!Compare (mac, dcrMAC))
865                         {
866                                 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
867                         }
868
869                         // Update sequence number
870                         this.context.ReadSequenceNumber++;
871
872                         return dcrFragment;
873                 }
874
875                 private bool Compare (byte[] array1, byte[] array2)
876                 {
877                         if (array1 == null)
878                                 return (array2 == null);
879                         if (array2 == null)
880                                 return false;
881                         if (array1.Length != array2.Length)
882                                 return false;
883                         for (int i = 0; i < array1.Length; i++) {
884                                 if (array1[i] != array2[i])
885                                         return false;
886                         }
887                         return true;
888                 }
889
890                 #endregion
891
892                 #region CipherSpecV2 processing
893
894                 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
895                 {
896                         TlsStream codes = new TlsStream(buffer);
897
898                         string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
899
900                         while (codes.Position < codes.Length)
901                         {
902                                 byte check = codes.ReadByte();
903
904                                 if (check == 0)
905                                 {
906                                         // SSL/TLS cipher spec
907                                         short code = codes.ReadInt16(); 
908                                         int index = this.Context.SupportedCiphers.IndexOf(code);
909                                         if (index != -1)
910                                         {
911                                                 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
912                                                 break;
913                                         }
914                                 }
915                                 else
916                                 {
917                                         byte[] tmp = new byte[2];
918                                         codes.Read(tmp, 0, tmp.Length);
919
920                                         int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
921                                         CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
922
923                                         if (cipher != null)
924                                         {
925                                                 this.Context.Negotiating.Cipher = cipher;
926                                                 break;
927                                         }
928                                 }
929                         }
930
931                         if (this.Context.Negotiating == null)
932                         {
933                                 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
934                         }
935                 }
936
937                 private CipherSuite MapV2CipherCode(string prefix, int code)
938                 {
939                         try
940                         {
941                                 switch (code)
942                                 {
943                                         case 65664:
944                                                 // TLS_RC4_128_WITH_MD5
945                                                 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
946                                         
947                                         case 131200:
948                                                 // TLS_RC4_128_EXPORT40_WITH_MD5
949                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
950                                         
951                                         case 196736:
952                                                 // TLS_RC2_CBC_128_CBC_WITH_MD5
953                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
954                                         
955                                         case 262272:
956                                                 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
957                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
958                                         
959                                         case 327808:
960                                                 // TLS_IDEA_128_CBC_WITH_MD5
961                                                 return null;
962                                         
963                                         case 393280:
964                                                 // TLS_DES_64_CBC_WITH_MD5
965                                                 return null;
966
967                                         case 458944:
968                                                 // TLS_DES_192_EDE3_CBC_WITH_MD5
969                                                 return null;
970
971                                         default:
972                                                 return null;
973                                 }
974                         }
975                         catch
976                         {
977                                 return null;
978                         }
979                 }
980
981                 #endregion
982         }
983 }