Merge pull request #901 from Blewzman/FixAggregateExceptionGetBaseException
[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                 public void SendAlert(AlertDescription description)
673                 {
674                         this.SendAlert(new Alert(description));
675                 }
676
677                 public void SendAlert(
678                         AlertLevel                      level, 
679                         AlertDescription        description)
680                 {
681                         this.SendAlert(new Alert(level, description));
682                 }
683
684                 public void SendAlert(Alert alert)
685                 {
686                         AlertLevel level;
687                         AlertDescription description;
688                         bool close;
689
690                         if (alert == null) {
691                                 DebugHelper.WriteLine(">>>> Write Alert NULL");
692                                 level = AlertLevel.Fatal;
693                                 description = AlertDescription.InternalError;
694                                 close = true;
695                         } else {
696                                 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
697                                 level = alert.Level;
698                                 description = alert.Description;
699                                 close = alert.IsCloseNotify;
700                         }
701
702                         // Write record
703                         this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
704
705                         if (close) {
706                                 this.context.SentConnectionEnd = true;
707                         }
708                 }
709
710                 #endregion
711
712                 #region Send Record Methods
713
714                 public void SendChangeCipherSpec()
715                 {
716                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
717
718                         // Send Change Cipher Spec message with the current cipher
719                         // or as plain text if this is the initial negotiation
720                         this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
721
722                         Context ctx = this.context;
723
724                         // Reset sequence numbers
725                         ctx.WriteSequenceNumber = 0;
726
727                         // all further data sent will be encrypted with the negotiated
728                         // security parameters (now the current parameters)
729                         if (ctx is ClientContext) {
730                                 ctx.StartSwitchingSecurityParameters (true);
731                         } else {
732                                 ctx.EndSwitchingSecurityParameters (false);
733                         }
734                 }
735
736                 public void SendChangeCipherSpec(Stream recordStream)
737                 {
738                         DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
739
740                         byte[] record = this.EncodeRecord (ContentType.ChangeCipherSpec, new byte[] { 1 });
741
742                         // Send Change Cipher Spec message with the current cipher
743                         // or as plain text if this is the initial negotiation
744                         recordStream.Write(record, 0, record.Length);
745
746                         Context ctx = this.context;
747
748                         // Reset sequence numbers
749                         ctx.WriteSequenceNumber = 0;
750
751                         // all further data sent will be encrypted with the negotiated
752                         // security parameters (now the current parameters)
753                         if (ctx is ClientContext) {
754                                 ctx.StartSwitchingSecurityParameters (true);
755                         } else {
756                                 ctx.EndSwitchingSecurityParameters (false);
757                         }
758                 }
759
760                 public IAsyncResult BeginSendChangeCipherSpec(AsyncCallback callback, object state)
761                 {
762                         DebugHelper.WriteLine (">>>> Write Change Cipher Spec");
763
764                         // Send Change Cipher Spec message with the current cipher
765                         // or as plain text if this is the initial negotiation
766                         return this.BeginSendRecord (ContentType.ChangeCipherSpec, new byte[] { 1 }, callback, state);
767                 }
768
769                 public void EndSendChangeCipherSpec (IAsyncResult asyncResult)
770                 {
771                         this.EndSendRecord (asyncResult);
772
773                         Context ctx = this.context;
774
775                         // Reset sequence numbers
776                         ctx.WriteSequenceNumber = 0;
777
778                         // all further data sent will be encrypted with the negotiated
779                         // security parameters (now the current parameters)
780                         if (ctx is ClientContext) {
781                                 ctx.StartSwitchingSecurityParameters (true);
782                         } else {
783                                 ctx.EndSwitchingSecurityParameters (false);
784                         }
785                 }
786
787                 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
788                 {
789                         HandshakeMessage msg = this.GetMessage(handshakeType);
790
791                         msg.Process();
792
793                         DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
794
795                         SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
796
797                         this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
798
799                         return internalResult;
800                 }
801
802                 private void InternalSendRecordCallback(IAsyncResult ar)
803                 {
804                         SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
805                         
806                         try
807                         {
808                                 this.EndSendRecord(ar);
809
810                                 // Update session
811                                 internalResult.Message.Update();
812
813                                 // Reset message contents
814                                 internalResult.Message.Reset();
815
816                                 internalResult.SetComplete();
817                         }
818                         catch (Exception ex)
819                         {
820                                 internalResult.SetComplete(ex);
821                         }
822                 }
823
824                 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
825                 {
826                         if (this.context.SentConnectionEnd)
827                         {
828                                 throw new TlsException(
829                                         AlertDescription.InternalError,
830                                         "The session is finished and it's no longer valid.");
831                         }
832
833                         byte[] record = this.EncodeRecord(contentType, recordData);
834
835                         return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
836                 }
837
838                 public void EndSendRecord(IAsyncResult asyncResult)
839                 {
840                         if (asyncResult is SendRecordAsyncResult)
841                         {
842                                 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
843                                 if (!internalResult.IsCompleted)
844                                         internalResult.AsyncWaitHandle.WaitOne();
845                                 if (internalResult.CompletedWithError)
846                                         throw internalResult.AsyncException;
847                         }
848                         else
849                         {
850                                 this.innerStream.EndWrite(asyncResult);
851                         }
852                 }
853
854                 public void SendRecord(ContentType contentType, byte[] recordData)
855                 {
856                         IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
857
858                         this.EndSendRecord(ar);
859                 }
860
861                 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
862                 {
863                         return this.EncodeRecord(
864                                 contentType,
865                                 recordData,
866                                 0,
867                                 recordData.Length);
868                 }
869
870                 public byte[] EncodeRecord(
871                         ContentType     contentType, 
872                         byte[]          recordData,
873                         int                     offset,
874                         int                     count)
875                 {
876                         if (this.context.SentConnectionEnd)
877                         {
878                                 throw new TlsException(
879                                         AlertDescription.InternalError,
880                                         "The session is finished and it's no longer valid.");
881                         }
882
883                         TlsStream record = new TlsStream();
884
885                         int     position = offset;
886
887                         while (position < ( offset + count ))
888                         {
889                                 short   fragmentLength = 0;
890                                 byte[]  fragment;
891
892                                 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
893                                 {
894                                         fragmentLength = Context.MAX_FRAGMENT_SIZE;
895                                 }
896                                 else
897                                 {
898                                         fragmentLength = (short)(count + offset - position);
899                                 }
900
901                                 // Fill the fragment data
902                                 fragment = new byte[fragmentLength];
903                                 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
904
905                                 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
906                                 {
907                                         // Encrypt fragment
908                                         fragment = this.encryptRecordFragment (contentType, fragment);
909                                 }
910
911                                 // Write tls message
912                                 record.Write((byte)contentType);
913                                 record.Write(this.context.Protocol);
914                                 record.Write((short)fragment.Length);
915                                 record.Write(fragment);
916
917                                 DebugHelper.WriteLine("Record data", fragment);
918
919                                 // Update buffer position
920                                 position += fragmentLength;
921                         }
922
923                         return record.ToArray();
924                 }
925
926                 public byte[] EncodeHandshakeRecord(HandshakeType handshakeType)
927                 {
928                         HandshakeMessage msg = this.GetMessage(handshakeType);
929
930                         msg.Process();
931
932                         var bytes = this.EncodeRecord (msg.ContentType, msg.EncodeMessage ());
933
934                         msg.Update();
935
936                         msg.Reset();
937
938                         return bytes;
939                 }
940                                 
941                 #endregion
942
943                 #region Cryptography Methods
944
945                 private byte[] encryptRecordFragment(
946                         ContentType     contentType, 
947                         byte[]          fragment)
948                 {
949                         byte[] mac      = null;
950
951                         // Calculate message MAC
952                         if (this.Context is ClientContext)
953                         {
954                                 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
955                         }       
956                         else
957                         {
958                                 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
959                         }
960
961                         DebugHelper.WriteLine(">>>> Record MAC", mac);
962
963                         // Encrypt the message
964                         byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
965
966                         // Update sequence number
967                         this.context.WriteSequenceNumber++;
968
969                         return ecr;
970                 }
971
972                 private byte[] decryptRecordFragment(
973                         ContentType     contentType, 
974                         byte[]          fragment)
975                 {
976                         byte[]  dcrFragment             = null;
977                         byte[]  dcrMAC                  = null;
978
979                         try
980                         {
981                                 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
982                         }
983                         catch
984                         {
985                                 if (this.context is ServerContext)
986                                 {
987                                         this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
988                                 }
989                                 throw;
990                         }
991                         
992                         // Generate record MAC
993                         byte[] mac = null;
994
995                         if (this.Context is ClientContext)
996                         {
997                                 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
998                         }
999                         else
1000                         {
1001                                 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
1002                         }
1003
1004                         DebugHelper.WriteLine(">>>> Record MAC", mac);
1005
1006                         // Check record MAC
1007                         if (!Compare (mac, dcrMAC))
1008                         {
1009                                 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
1010                         }
1011
1012                         // Update sequence number
1013                         this.context.ReadSequenceNumber++;
1014
1015                         return dcrFragment;
1016                 }
1017
1018                 private bool Compare (byte[] array1, byte[] array2)
1019                 {
1020                         if (array1 == null)
1021                                 return (array2 == null);
1022                         if (array2 == null)
1023                                 return false;
1024                         if (array1.Length != array2.Length)
1025                                 return false;
1026                         for (int i = 0; i < array1.Length; i++) {
1027                                 if (array1[i] != array2[i])
1028                                         return false;
1029                         }
1030                         return true;
1031                 }
1032
1033                 #endregion
1034
1035                 #region CipherSpecV2 processing
1036
1037                 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
1038                 {
1039                         TlsStream codes = new TlsStream(buffer);
1040
1041                         string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
1042
1043                         while (codes.Position < codes.Length)
1044                         {
1045                                 byte check = codes.ReadByte();
1046
1047                                 if (check == 0)
1048                                 {
1049                                         // SSL/TLS cipher spec
1050                                         short code = codes.ReadInt16(); 
1051                                         int index = this.Context.SupportedCiphers.IndexOf(code);
1052                                         if (index != -1)
1053                                         {
1054                                                 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
1055                                                 break;
1056                                         }
1057                                 }
1058                                 else
1059                                 {
1060                                         byte[] tmp = new byte[2];
1061                                         codes.Read(tmp, 0, tmp.Length);
1062
1063                                         int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
1064                                         CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
1065
1066                                         if (cipher != null)
1067                                         {
1068                                                 this.Context.Negotiating.Cipher = cipher;
1069                                                 break;
1070                                         }
1071                                 }
1072                         }
1073
1074                         if (this.Context.Negotiating == null)
1075                         {
1076                                 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
1077                         }
1078                 }
1079
1080                 private CipherSuite MapV2CipherCode(string prefix, int code)
1081                 {
1082                         try
1083                         {
1084                                 switch (code)
1085                                 {
1086                                         case 65664:
1087                                                 // TLS_RC4_128_WITH_MD5
1088                                                 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
1089                                         
1090                                         case 131200:
1091                                                 // TLS_RC4_128_EXPORT40_WITH_MD5
1092                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
1093                                         
1094                                         case 196736:
1095                                                 // TLS_RC2_CBC_128_CBC_WITH_MD5
1096                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
1097                                         
1098                                         case 262272:
1099                                                 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
1100                                                 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
1101                                         
1102                                         case 327808:
1103                                                 // TLS_IDEA_128_CBC_WITH_MD5
1104                                                 return null;
1105                                         
1106                                         case 393280:
1107                                                 // TLS_DES_64_CBC_WITH_MD5
1108                                                 return null;
1109
1110                                         case 458944:
1111                                                 // TLS_DES_192_EDE3_CBC_WITH_MD5
1112                                                 return null;
1113
1114                                         default:
1115                                                 return null;
1116                                 }
1117                         }
1118                         catch
1119                         {
1120                                 return null;
1121                         }
1122                 }
1123
1124                 #endregion
1125         }
1126 }