589510685a63193d51b8308798be26820c61cf76
[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                         if (this.context.ReceivedConnectionEnd)
441                         {
442                                 throw new TlsException(
443                                         AlertDescription.InternalError,
444                                         "The session is finished and it's no longer valid.");
445                         }
446
447                         record_processing.Reset ();
448                         byte[] recordTypeBuffer = new byte[1];
449
450                         int bytesRead = record.Read(recordTypeBuffer, 0, recordTypeBuffer.Length);
451
452                         //We're at the end of the stream. Time to bail.
453                         if (bytesRead == 0)
454                         {
455                                 return null;
456                         }
457
458                         // Try to read the Record Content Type
459                         int type = recordTypeBuffer[0];
460
461                         // Set last handshake message received to None
462                         this.context.LastHandshakeMsg = HandshakeType.ClientHello;
463
464                         ContentType     contentType     = (ContentType)type;
465                         byte[] buffer = this.ReadRecordBuffer(type, record);
466                         if (buffer == null)
467                         {
468                                 // record incomplete (at the moment)
469                                 return null;
470                         }
471
472                         // Decrypt message contents if needed
473                         if (contentType == ContentType.Alert && buffer.Length == 2)
474                         {
475                         }
476                         else if ((this.Context.Read != null) && (this.Context.Read.Cipher != null))
477                         {
478                                 buffer = this.decryptRecordFragment (contentType, buffer);
479                                 DebugHelper.WriteLine ("Decrypted record data", buffer);
480                         }
481
482                         // Process record
483                         switch (contentType)
484                         {
485                         case ContentType.Alert:
486                                 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
487                                 if (record.CanSeek) 
488                                 {
489                                         // don't reprocess that memory block
490                                         record.SetLength (0); 
491                                 }
492                                 buffer = null;
493                                 break;
494
495                         case ContentType.ChangeCipherSpec:
496                                 this.ProcessChangeCipherSpec();
497                                 break;
498
499                         case ContentType.ApplicationData:
500                                 break;
501
502                         case ContentType.Handshake:
503                                 TlsStream message = new TlsStream (buffer);
504                                 while (!message.EOF)
505                                 {
506                                         this.ProcessHandshakeMessage(message);
507                                 }
508                                 break;
509
510                         case (ContentType)0x80:
511                                 this.context.HandshakeMessages.Write (buffer);
512                                 break;
513
514                         default:
515                                 throw new TlsException(
516                                         AlertDescription.UnexpectedMessage,
517                                         "Unknown record received from server.");
518                         }
519
520                         record_processing.Set ();
521                         return buffer;
522                 }
523
524                 private byte[] ReadRecordBuffer (int contentType, Stream record)
525                 {
526                         switch (contentType)
527                         {
528                                 case 0x80:
529                                         return this.ReadClientHelloV2(record);
530
531                                 default:
532                                         if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
533                                         {
534                                                 throw new TlsException(AlertDescription.DecodeError);
535                                         }
536                                         return this.ReadStandardRecordBuffer(record);
537                         }
538                 }
539
540                 private byte[] ReadClientHelloV2 (Stream record)
541                 {
542                         int msgLength = record.ReadByte ();
543                         // process further only if the whole record is available
544                         if (record.CanSeek && (msgLength + 1 > record.Length)) 
545                         {
546                                 return null;
547                         }
548
549                         byte[] message = new byte[msgLength];
550                         record.Read (message, 0, msgLength);
551
552                         int msgType             = message [0];
553                         if (msgType != 1)
554                         {
555                                 throw new TlsException(AlertDescription.DecodeError);
556                         }
557                         int protocol = (message [1] << 8 | message [2]);
558                         int cipherSpecLength = (message [3] << 8 | message [4]);
559                         int sessionIdLength = (message [5] << 8 | message [6]);
560                         int challengeLength = (message [7] << 8 | message [8]);
561                         int length = (challengeLength > 32) ? 32 : challengeLength;
562
563                         // Read CipherSpecs
564                         byte[] cipherSpecV2 = new byte[cipherSpecLength];
565                         Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
566
567                         // Read session ID
568                         byte[] sessionId = new byte[sessionIdLength];
569                         Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
570
571                         // Read challenge ID
572                         byte[] challenge = new byte[challengeLength];
573                         Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
574                 
575                         if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
576                         {
577                                 throw new TlsException(AlertDescription.DecodeError);
578                         }
579
580                         // Updated the Session ID
581                         if (sessionId.Length > 0)
582                         {
583                                 this.context.SessionId = sessionId;
584                         }
585
586                         // Update the protocol version
587                         this.Context.ChangeProtocol((short)protocol);
588
589                         // Select the Cipher suite
590                         this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
591
592                         // Updated the Client Random
593                         this.context.ClientRandom = new byte [32]; // Always 32
594                         // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
595                         // 2. right justify (0) challenge in ClientRandom if less than 32
596                         Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
597
598                         // Set 
599                         this.context.LastHandshakeMsg = HandshakeType.ClientHello;
600                         this.context.ProtocolNegotiated = true;
601
602                         return message;
603                 }
604
605                 private byte[] ReadStandardRecordBuffer (Stream record)
606                 {
607                         byte[] header = new byte[4];
608                         if (record.Read (header, 0, 4) != 4)
609                                 throw new TlsException ("buffer underrun");
610                         
611                         short protocol = (short)((header [0] << 8) | header [1]);
612                         short length = (short)((header [2] << 8) | header [3]);
613
614                         // process further only if the whole record is available
615                         // note: the first 5 bytes aren't part of the length
616                         if (record.CanSeek && (length + 5 > record.Length)) 
617                         {
618                                 return null;
619                         }
620                         
621                         // Read Record data
622                         int     totalReceived = 0;
623                         byte[]  buffer          = new byte[length];
624                         while (totalReceived != length)
625                         {
626                                 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
627
628                                 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
629                                 if (0 == justReceived)
630                                 {
631                                         throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
632                                 }
633
634                                 totalReceived += justReceived;
635                         }
636
637                         // Check that the message has a valid protocol version
638                         if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
639                         {
640                                 throw new TlsException(
641                                         AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
642                         }
643
644                         DebugHelper.WriteLine("Record data", buffer);
645
646                         return buffer;
647                 }
648
649                 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
650                 {
651                         switch (alertLevel)
652                         {
653                                 case AlertLevel.Fatal:
654                                         throw new TlsException(alertLevel, alertDesc);
655
656                                 case AlertLevel.Warning:
657                                 default:
658                                 switch (alertDesc)
659                                 {
660                                         case AlertDescription.CloseNotify:
661                                                 this.context.ReceivedConnectionEnd = true;
662                                                 break;
663                                 }
664                                 break;
665                         }
666                 }
667
668                 #endregion
669
670                 #region Send Alert Methods
671
672                 internal void SendAlert(ref Exception ex)
673                 {
674                         var tlsEx = ex as TlsException;
675                         var alert = tlsEx != null ? tlsEx.Alert : new Alert(AlertDescription.InternalError);
676
677                         try {
678                                 SendAlert(alert);
679                         } catch (Exception alertEx) {
680                                 ex = new IOException (string.Format ("Error while sending TLS Alert ({0}:{1}): {2}", alert.Level, alert.Description, ex), ex);
681                         }
682                 }
683
684                 public void SendAlert(AlertDescription description)
685                 {
686                         this.SendAlert(new Alert(description));
687                 }
688
689                 public void SendAlert(AlertLevel level, AlertDescription description)
690                 {
691                         this.SendAlert(new Alert(level, description));
692                 }
693
694                 public void SendAlert(Alert alert)
695                 {
696                         AlertLevel level;
697                         AlertDescription description;
698                         bool close;
699
700                         if (alert == null) {
701                                 DebugHelper.WriteLine(">>>> Write Alert NULL");
702                                 level = AlertLevel.Fatal;
703                                 description = AlertDescription.InternalError;
704                                 close = true;
705                         } else {
706                                 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
707                                 level = alert.Level;
708                                 description = alert.Description;
709                                 close = alert.IsCloseNotify;
710                         }
711
712                         // Write record
713                         this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
714
715                         if (close) {
716                                 this.context.SentConnectionEnd = true;
717                         }
718                 }
719
720                 #endregion
721
722                 #region Send Record Methods
723
724                 public void SendChangeCipherSpec()
725                 {
726                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
727
728                         // Send Change Cipher Spec message with the current cipher
729                         // or as plain text if this is the initial negotiation
730                         this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
731
732                         Context ctx = this.context;
733
734                         // Reset sequence numbers
735                         ctx.WriteSequenceNumber = 0;
736
737                         // all further data sent will be encrypted with the negotiated
738                         // security parameters (now the current parameters)
739                         if (ctx is ClientContext) {
740                                 ctx.StartSwitchingSecurityParameters (true);
741                         } else {
742                                 ctx.EndSwitchingSecurityParameters (false);
743                         }
744                 }
745
746                 public void SendChangeCipherSpec(Stream recordStream)
747                 {
748                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
749
750                         byte[] record = this.EncodeRecord (ContentType.ChangeCipherSpec, new byte[] { 1 });
751
752                         // Send Change Cipher Spec message with the current cipher
753                         // or as plain text if this is the initial negotiation
754                         recordStream.Write(record, 0, record.Length);
755
756                         Context ctx = this.context;
757
758                         // Reset sequence numbers
759                         ctx.WriteSequenceNumber = 0;
760
761                         // all further data sent will be encrypted with the negotiated
762                         // security parameters (now the current parameters)
763                         if (ctx is ClientContext) {
764                                 ctx.StartSwitchingSecurityParameters (true);
765                         } else {
766                                 ctx.EndSwitchingSecurityParameters (false);
767                         }
768                 }
769
770                 public IAsyncResult BeginSendChangeCipherSpec(AsyncCallback callback, object state)
771                 {
772                         DebugHelper.WriteLine (">>>> Write Change Cipher Spec");
773
774                         // Send Change Cipher Spec message with the current cipher
775                         // or as plain text if this is the initial negotiation
776                         return this.BeginSendRecord (ContentType.ChangeCipherSpec, new byte[] { 1 }, callback, state);
777                 }
778
779                 public void EndSendChangeCipherSpec (IAsyncResult asyncResult)
780                 {
781                         this.EndSendRecord (asyncResult);
782
783                         Context ctx = this.context;
784
785                         // Reset sequence numbers
786                         ctx.WriteSequenceNumber = 0;
787
788                         // all further data sent will be encrypted with the negotiated
789                         // security parameters (now the current parameters)
790                         if (ctx is ClientContext) {
791                                 ctx.StartSwitchingSecurityParameters (true);
792                         } else {
793                                 ctx.EndSwitchingSecurityParameters (false);
794                         }
795                 }
796
797                 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
798                 {
799                         HandshakeMessage msg = this.GetMessage(handshakeType);
800
801                         msg.Process();
802
803                         DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
804
805                         SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
806
807                         this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
808
809                         return internalResult;
810                 }
811
812                 private void InternalSendRecordCallback(IAsyncResult ar)
813                 {
814                         SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
815                         
816                         try
817                         {
818                                 this.EndSendRecord(ar);
819
820                                 // Update session
821                                 internalResult.Message.Update();
822
823                                 // Reset message contents
824                                 internalResult.Message.Reset();
825
826                                 internalResult.SetComplete();
827                         }
828                         catch (Exception ex)
829                         {
830                                 internalResult.SetComplete(ex);
831                         }
832                 }
833
834                 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
835                 {
836                         if (this.context.SentConnectionEnd)
837                         {
838                                 throw new TlsException(
839                                         AlertDescription.InternalError,
840                                         "The session is finished and it's no longer valid.");
841                         }
842
843                         byte[] record = this.EncodeRecord(contentType, recordData);
844
845                         return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
846                 }
847
848                 public void EndSendRecord(IAsyncResult asyncResult)
849                 {
850                         if (asyncResult is SendRecordAsyncResult)
851                         {
852                                 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
853                                 if (!internalResult.IsCompleted)
854                                         internalResult.AsyncWaitHandle.WaitOne();
855                                 if (internalResult.CompletedWithError)
856                                         throw internalResult.AsyncException;
857                         }
858                         else
859                         {
860                                 this.innerStream.EndWrite(asyncResult);
861                         }
862                 }
863
864                 public void SendRecord(ContentType contentType, byte[] recordData)
865                 {
866                         IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
867
868                         this.EndSendRecord(ar);
869                 }
870
871                 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
872                 {
873                         return this.EncodeRecord(
874                                 contentType,
875                                 recordData,
876                                 0,
877                                 recordData.Length);
878                 }
879
880                 public byte[] EncodeRecord(
881                         ContentType     contentType, 
882                         byte[]          recordData,
883                         int                     offset,
884                         int                     count)
885                 {
886                         if (this.context.SentConnectionEnd)
887                         {
888                                 throw new TlsException(
889                                         AlertDescription.InternalError,
890                                         "The session is finished and it's no longer valid.");
891                         }
892
893                         TlsStream record = new TlsStream();
894
895                         int     position = offset;
896
897                         while (position < ( offset + count ))
898                         {
899                                 short   fragmentLength = 0;
900                                 byte[]  fragment;
901
902                                 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
903                                 {
904                                         fragmentLength = Context.MAX_FRAGMENT_SIZE;
905                                 }
906                                 else
907                                 {
908                                         fragmentLength = (short)(count + offset - position);
909                                 }
910
911                                 // Fill the fragment data
912                                 fragment = new byte[fragmentLength];
913                                 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
914
915                                 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
916                                 {
917                                         // Encrypt fragment
918                                         fragment = this.encryptRecordFragment (contentType, fragment);
919                                 }
920
921                                 // Write tls message
922                                 record.Write((byte)contentType);
923                                 record.Write(this.context.Protocol);
924                                 record.Write((short)fragment.Length);
925                                 record.Write(fragment);
926
927                                 DebugHelper.WriteLine("Record data", fragment);
928
929                                 // Update buffer position
930                                 position += fragmentLength;
931                         }
932
933                         return record.ToArray();
934                 }
935
936                 public byte[] EncodeHandshakeRecord(HandshakeType handshakeType)
937                 {
938                         HandshakeMessage msg = this.GetMessage(handshakeType);
939
940                         msg.Process();
941
942                         var bytes = this.EncodeRecord (msg.ContentType, msg.EncodeMessage ());
943
944                         msg.Update();
945
946                         msg.Reset();
947
948                         return bytes;
949                 }
950                                 
951                 #endregion
952
953                 #region Cryptography Methods
954
955                 private byte[] encryptRecordFragment(
956                         ContentType     contentType, 
957                         byte[]          fragment)
958                 {
959                         byte[] mac      = null;
960
961                         // Calculate message MAC
962                         if (this.Context is ClientContext)
963                         {
964                                 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
965                         }       
966                         else
967                         {
968                                 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
969                         }
970
971                         DebugHelper.WriteLine(">>>> Record MAC", mac);
972
973                         // Encrypt the message
974                         byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
975
976                         // Update sequence number
977                         this.context.WriteSequenceNumber++;
978
979                         return ecr;
980                 }
981
982                 private byte[] decryptRecordFragment(
983                         ContentType     contentType, 
984                         byte[]          fragment)
985                 {
986                         byte[]  dcrFragment             = null;
987                         byte[]  dcrMAC                  = null;
988
989                         try
990                         {
991                                 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
992                         }
993                         catch
994                         {
995                                 if (this.context is ServerContext)
996                                 {
997                                         this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
998                                 }
999                                 throw;
1000                         }
1001                         
1002                         // Generate record MAC
1003                         byte[] mac = null;
1004
1005                         if (this.Context is ClientContext)
1006                         {
1007                                 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
1008                         }
1009                         else
1010                         {
1011                                 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
1012                         }
1013
1014                         DebugHelper.WriteLine(">>>> Record MAC", mac);
1015
1016                         // Check record MAC
1017                         if (!Compare (mac, dcrMAC))
1018                         {
1019                                 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
1020                         }
1021
1022                         // Update sequence number
1023                         this.context.ReadSequenceNumber++;
1024
1025                         return dcrFragment;
1026                 }
1027
1028                 private bool Compare (byte[] array1, byte[] array2)
1029                 {
1030                         if (array1 == null)
1031                                 return (array2 == null);
1032                         if (array2 == null)
1033                                 return false;
1034                         if (array1.Length != array2.Length)
1035                                 return false;
1036                         for (int i = 0; i < array1.Length; i++) {
1037                                 if (array1[i] != array2[i])
1038                                         return false;
1039                         }
1040                         return true;
1041                 }
1042
1043                 #endregion
1044
1045                 #region CipherSpecV2 processing
1046
1047                 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
1048                 {
1049                         TlsStream codes = new TlsStream(buffer);
1050
1051                         string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
1052
1053                         while (codes.Position < codes.Length)
1054                         {
1055                                 byte check = codes.ReadByte();
1056
1057                                 if (check == 0)
1058                                 {
1059                                         // SSL/TLS cipher spec
1060                                         short code = codes.ReadInt16(); 
1061                                         int index = this.Context.SupportedCiphers.IndexOf(code);
1062                                         if (index != -1)
1063                                         {
1064                                                 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
1065                                                 break;
1066                                         }
1067                                 }
1068                                 else
1069                                 {
1070                                         byte[] tmp = new byte[2];
1071                                         codes.Read(tmp, 0, tmp.Length);
1072
1073                                         int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
1074                                         CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
1075
1076                                         if (cipher != null)
1077                                         {
1078                                                 this.Context.Negotiating.Cipher = cipher;
1079                                                 break;
1080                                         }
1081                                 }
1082                         }
1083
1084                         if (this.Context.Negotiating == null)
1085                         {
1086                                 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
1087                         }
1088                 }
1089
1090                 private CipherSuite MapV2CipherCode(string prefix, int code)
1091                 {
1092                         try
1093                         {
1094                                 switch (code)
1095                                 {
1096                                         case 65664:
1097                                                 // TLS_RC4_128_WITH_MD5
1098                                                 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
1099                                         
1100                                         case 131200:
1101                                                 // TLS_RC4_128_EXPORT40_WITH_MD5
1102                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
1103                                         
1104                                         case 196736:
1105                                                 // TLS_RC2_CBC_128_CBC_WITH_MD5
1106                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
1107                                         
1108                                         case 262272:
1109                                                 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
1110                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
1111                                         
1112                                         case 327808:
1113                                                 // TLS_IDEA_128_CBC_WITH_MD5
1114                                                 return null;
1115                                         
1116                                         case 393280:
1117                                                 // TLS_DES_64_CBC_WITH_MD5
1118                                                 return null;
1119
1120                                         case 458944:
1121                                                 // TLS_DES_192_EDE3_CBC_WITH_MD5
1122                                                 return null;
1123
1124                                         default:
1125                                                 return null;
1126                                 }
1127                         }
1128                         catch
1129                         {
1130                                 return null;
1131                         }
1132                 }
1133
1134                 #endregion
1135         }
1136 }