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