disable broken tests on net_4_0
[mcs.git] / class / System.ServiceModel / Mono.Security.Protocol.Tls / RecordProtocol.cs
blob6ccced66b85d9d50a9a0bf00fe3b58f12e49839b
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.
25 using System;
26 using System.Collections;
27 using System.IO;
28 using System.Threading;
30 using Mono.Security.Protocol.Tls.Handshake;
32 namespace Mono.Security.Protocol.Tls
34 internal abstract class RecordProtocol
36 #region Fields
38 private static ManualResetEvent record_processing = new ManualResetEvent (true);
40 protected Stream innerStream;
41 protected Context context;
43 #endregion
45 #region Properties
47 public Context Context
49 get { return this.context; }
50 set { this.context = value; }
53 #endregion
55 #region Constructors
57 public RecordProtocol(Stream innerStream, Context context)
59 this.innerStream = innerStream;
60 this.context = context;
61 this.context.RecordProtocol = this;
64 #endregion
66 #region Abstract Methods
68 public virtual void SendRecord(HandshakeType type)
71 IAsyncResult ar = this.BeginSendRecord(type, null, null);
73 this.EndSendRecord(ar);
77 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
79 protected virtual void ProcessChangeCipherSpec ()
81 Context ctx = this.Context;
83 // Reset sequence numbers
84 ctx.ReadSequenceNumber = 0;
86 if (ctx is ClientContext) {
87 ctx.EndSwitchingSecurityParameters (true);
88 } else {
89 ctx.StartSwitchingSecurityParameters (false);
93 public virtual HandshakeMessage GetMessage(HandshakeType type)
95 throw new NotSupportedException();
98 #endregion
100 #region Receive Record Async Result
101 private class ReceiveRecordAsyncResult : IAsyncResult
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;
112 private byte[] _initialBuffer;
114 public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
116 _userCallback = userCallback;
117 _userState = userState;
118 _initialBuffer = initialBuffer;
119 _record = record;
122 public Stream Record
124 get { return _record; }
127 public byte[] ResultingBuffer
129 get { return _resultingBuffer; }
132 public byte[] InitialBuffer
134 get { return _initialBuffer; }
137 public object AsyncState
139 get { return _userState; }
142 public Exception AsyncException
144 get { return _asyncException; }
147 public bool CompletedWithError
149 get {
150 if (!IsCompleted)
151 return false; // Perhaps throw InvalidOperationExcetion?
153 return null != _asyncException;
157 public WaitHandle AsyncWaitHandle
159 get {
160 lock (locker) {
161 if (handle == null)
162 handle = new ManualResetEvent (completed);
164 return handle;
169 public bool CompletedSynchronously
171 get { return false; }
174 public bool IsCompleted
176 get {
177 lock (locker) {
178 return completed;
183 private void SetComplete(Exception ex, byte[] resultingBuffer)
185 lock (locker) {
186 if (completed)
187 return;
189 completed = true;
190 _asyncException = ex;
191 _resultingBuffer = resultingBuffer;
192 if (handle != null)
193 handle.Set ();
195 if (_userCallback != null)
196 _userCallback.BeginInvoke (this, null, null);
200 public void SetComplete(Exception ex)
202 SetComplete(ex, null);
205 public void SetComplete(byte[] resultingBuffer)
207 SetComplete(null, resultingBuffer);
210 public void SetComplete()
212 SetComplete(null, null);
215 #endregion
217 #region Receive Record Async Result
218 private class SendRecordAsyncResult : IAsyncResult
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;
228 public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
230 _userCallback = userCallback;
231 _userState = userState;
232 _message = message;
235 public HandshakeMessage Message
237 get { return _message; }
240 public object AsyncState
242 get { return _userState; }
245 public Exception AsyncException
247 get { return _asyncException; }
250 public bool CompletedWithError
252 get {
253 if (!IsCompleted)
254 return false; // Perhaps throw InvalidOperationExcetion?
256 return null != _asyncException;
260 public WaitHandle AsyncWaitHandle
262 get {
263 lock (locker) {
264 if (handle == null)
265 handle = new ManualResetEvent (completed);
267 return handle;
272 public bool CompletedSynchronously
274 get { return false; }
277 public bool IsCompleted
279 get {
280 lock (locker) {
281 return completed;
286 public void SetComplete(Exception ex)
288 lock (locker) {
289 if (completed)
290 return;
292 completed = true;
293 if (handle != null)
294 handle.Set ();
296 if (_userCallback != null)
297 _userCallback.BeginInvoke (this, null, null);
299 _asyncException = ex;
303 public void SetComplete()
305 SetComplete(null);
308 #endregion
310 #region Reveive Record Methods
312 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
314 if (this.context.ConnectionEnd)
316 throw new TlsException(
317 AlertDescription.InternalError,
318 "The session is finished and it's no longer valid.");
321 record_processing.Reset ();
322 byte[] recordTypeBuffer = new byte[1];
324 ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
326 record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);
328 return internalResult;
331 private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
333 ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
334 Stream record = internalResult.Record;
339 int bytesRead = internalResult.Record.EndRead(asyncResult);
341 //We're at the end of the stream. Time to bail.
342 if (bytesRead == 0)
344 internalResult.SetComplete((byte[])null);
345 return;
348 // Try to read the Record Content Type
349 int type = internalResult.InitialBuffer[0];
351 // Set last handshake message received to None
352 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
354 ContentType contentType = (ContentType)type;
355 byte[] buffer = this.ReadRecordBuffer(type, record);
356 if (buffer == null)
358 // record incomplete (at the moment)
359 internalResult.SetComplete((byte[])null);
360 return;
363 // Decrypt message contents if needed
364 if (contentType == ContentType.Alert && buffer.Length == 2)
367 else if ((this.Context.Read != null) && (this.Context.Read.Cipher != null))
369 buffer = this.decryptRecordFragment (contentType, buffer);
370 DebugHelper.WriteLine ("Decrypted record data", buffer);
373 // Process record
374 switch (contentType)
376 case ContentType.Alert:
377 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
378 if (record.CanSeek)
380 // don't reprocess that memory block
381 record.SetLength (0);
383 buffer = null;
384 break;
386 case ContentType.ChangeCipherSpec:
387 this.ProcessChangeCipherSpec();
388 break;
390 case ContentType.ApplicationData:
391 break;
393 case ContentType.Handshake:
394 TlsStream message = new TlsStream (buffer);
395 while (!message.EOF)
397 this.ProcessHandshakeMessage(message);
399 break;
401 case (ContentType)0x80:
402 this.context.HandshakeMessages.Write (buffer);
403 break;
405 default:
406 throw new TlsException(
407 AlertDescription.UnexpectedMessage,
408 "Unknown record received from server.");
411 internalResult.SetComplete(buffer);
413 catch (Exception ex)
415 internalResult.SetComplete(ex);
420 public byte[] EndReceiveRecord(IAsyncResult asyncResult)
422 ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;
424 if (null == internalResult)
425 throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");
427 if (!internalResult.IsCompleted)
428 internalResult.AsyncWaitHandle.WaitOne();
430 if (internalResult.CompletedWithError)
431 throw internalResult.AsyncException;
433 byte[] result = internalResult.ResultingBuffer;
434 record_processing.Set ();
435 return result;
438 public byte[] ReceiveRecord(Stream record)
441 IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
442 return this.EndReceiveRecord(ar);
446 private byte[] ReadRecordBuffer (int contentType, Stream record)
448 switch (contentType)
450 case 0x80:
451 return this.ReadClientHelloV2(record);
453 default:
454 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
456 throw new TlsException(AlertDescription.DecodeError);
458 return this.ReadStandardRecordBuffer(record);
462 private byte[] ReadClientHelloV2 (Stream record)
464 int msgLength = record.ReadByte ();
465 // process further only if the whole record is available
466 if (record.CanSeek && (msgLength + 1 > record.Length))
468 return null;
471 byte[] message = new byte[msgLength];
472 record.Read (message, 0, msgLength);
474 int msgType = message [0];
475 if (msgType != 1)
477 throw new TlsException(AlertDescription.DecodeError);
479 int protocol = (message [1] << 8 | message [2]);
480 int cipherSpecLength = (message [3] << 8 | message [4]);
481 int sessionIdLength = (message [5] << 8 | message [6]);
482 int challengeLength = (message [7] << 8 | message [8]);
483 int length = (challengeLength > 32) ? 32 : challengeLength;
485 // Read CipherSpecs
486 byte[] cipherSpecV2 = new byte[cipherSpecLength];
487 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
489 // Read session ID
490 byte[] sessionId = new byte[sessionIdLength];
491 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
493 // Read challenge ID
494 byte[] challenge = new byte[challengeLength];
495 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
497 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
499 throw new TlsException(AlertDescription.DecodeError);
502 // Updated the Session ID
503 if (sessionId.Length > 0)
505 this.context.SessionId = sessionId;
508 // Update the protocol version
509 this.Context.ChangeProtocol((short)protocol);
511 // Select the Cipher suite
512 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
514 // Updated the Client Random
515 this.context.ClientRandom = new byte [32]; // Always 32
516 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
517 // 2. right justify (0) challenge in ClientRandom if less than 32
518 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
520 // Set
521 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
522 this.context.ProtocolNegotiated = true;
524 return message;
527 private byte[] ReadStandardRecordBuffer (Stream record)
529 byte[] header = new byte[4];
530 if (record.Read (header, 0, 4) != 4)
531 throw new TlsException ("buffer underrun");
533 short protocol = (short)((header [0] << 8) | header [1]);
534 short length = (short)((header [2] << 8) | header [3]);
536 // process further only if the whole record is available
537 // note: the first 5 bytes aren't part of the length
538 if (record.CanSeek && (length + 5 > record.Length))
540 return null;
543 // Read Record data
544 int totalReceived = 0;
545 byte[] buffer = new byte[length];
546 while (totalReceived != length)
548 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
550 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
551 if (0 == justReceived)
553 throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
556 totalReceived += justReceived;
559 // Check that the message has a valid protocol version
560 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
562 throw new TlsException(
563 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
566 DebugHelper.WriteLine("Record data", buffer);
568 return buffer;
571 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
573 switch (alertLevel)
575 case AlertLevel.Fatal:
576 throw new TlsException(alertLevel, alertDesc);
578 case AlertLevel.Warning:
579 default:
580 switch (alertDesc)
582 case AlertDescription.CloseNotify:
583 this.context.ConnectionEnd = true;
584 break;
586 break;
590 #endregion
592 #region Send Alert Methods
594 public void SendAlert(AlertDescription description)
596 this.SendAlert(new Alert(description));
599 public void SendAlert(
600 AlertLevel level,
601 AlertDescription description)
603 this.SendAlert(new Alert(level, description));
606 public void SendAlert(Alert alert)
608 AlertLevel level;
609 AlertDescription description;
610 bool close;
612 if (alert == null) {
613 DebugHelper.WriteLine(">>>> Write Alert NULL");
614 level = AlertLevel.Fatal;
615 description = AlertDescription.InternalError;
616 close = true;
617 } else {
618 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
619 level = alert.Level;
620 description = alert.Description;
621 close = alert.IsCloseNotify;
624 // Write record
625 this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
627 if (close)
629 this.context.ConnectionEnd = true;
633 #endregion
635 #region Send Record Methods
637 public void SendChangeCipherSpec()
639 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
641 // Send Change Cipher Spec message with the current cipher
642 // or as plain text if this is the initial negotiation
643 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
645 Context ctx = this.context;
647 // Reset sequence numbers
648 ctx.WriteSequenceNumber = 0;
650 // all further data sent will be encrypted with the negotiated
651 // security parameters (now the current parameters)
652 if (ctx is ClientContext) {
653 ctx.StartSwitchingSecurityParameters (true);
654 } else {
655 ctx.EndSwitchingSecurityParameters (false);
659 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
661 HandshakeMessage msg = this.GetMessage(handshakeType);
663 msg.Process();
665 DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
667 SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
669 this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
671 return internalResult;
674 private void InternalSendRecordCallback(IAsyncResult ar)
676 SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
680 this.EndSendRecord(ar);
682 // Update session
683 internalResult.Message.Update();
685 // Reset message contents
686 internalResult.Message.Reset();
688 internalResult.SetComplete();
690 catch (Exception ex)
692 internalResult.SetComplete(ex);
696 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
698 if (this.context.ConnectionEnd)
700 throw new TlsException(
701 AlertDescription.InternalError,
702 "The session is finished and it's no longer valid.");
705 byte[] record = this.EncodeRecord(contentType, recordData);
707 return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
710 public void EndSendRecord(IAsyncResult asyncResult)
712 if (asyncResult is SendRecordAsyncResult)
714 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
715 if (!internalResult.IsCompleted)
716 internalResult.AsyncWaitHandle.WaitOne();
717 if (internalResult.CompletedWithError)
718 throw internalResult.AsyncException;
720 else
722 this.innerStream.EndWrite(asyncResult);
726 public void SendRecord(ContentType contentType, byte[] recordData)
728 IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
730 this.EndSendRecord(ar);
733 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
735 return this.EncodeRecord(
736 contentType,
737 recordData,
739 recordData.Length);
742 public byte[] EncodeRecord(
743 ContentType contentType,
744 byte[] recordData,
745 int offset,
746 int count)
748 if (this.context.ConnectionEnd)
750 throw new TlsException(
751 AlertDescription.InternalError,
752 "The session is finished and it's no longer valid.");
755 TlsStream record = new TlsStream();
757 int position = offset;
759 while (position < ( offset + count ))
761 short fragmentLength = 0;
762 byte[] fragment;
764 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
766 fragmentLength = Context.MAX_FRAGMENT_SIZE;
768 else
770 fragmentLength = (short)(count + offset - position);
773 // Fill the fragment data
774 fragment = new byte[fragmentLength];
775 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
777 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
779 // Encrypt fragment
780 fragment = this.encryptRecordFragment (contentType, fragment);
783 // Write tls message
784 record.Write((byte)contentType);
785 record.Write(this.context.Protocol);
786 record.Write((short)fragment.Length);
787 record.Write(fragment);
789 DebugHelper.WriteLine("Record data", fragment);
791 // Update buffer position
792 position += fragmentLength;
795 return record.ToArray();
798 #endregion
800 #region Cryptography Methods
802 private byte[] encryptRecordFragment(
803 ContentType contentType,
804 byte[] fragment)
806 byte[] mac = null;
808 // Calculate message MAC
809 if (this.Context is ClientContext)
811 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
813 else
815 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
818 DebugHelper.WriteLine(">>>> Record MAC", mac);
820 // Encrypt the message
821 byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
823 // Update sequence number
824 this.context.WriteSequenceNumber++;
826 return ecr;
829 private byte[] decryptRecordFragment(
830 ContentType contentType,
831 byte[] fragment)
833 byte[] dcrFragment = null;
834 byte[] dcrMAC = null;
838 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
840 catch
842 if (this.context is ServerContext)
844 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
847 throw;
850 // Generate record MAC
851 byte[] mac = null;
853 if (this.Context is ClientContext)
855 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
857 else
859 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
862 DebugHelper.WriteLine(">>>> Record MAC", mac);
864 // Check record MAC
865 if (!Compare (mac, dcrMAC))
867 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
870 // Update sequence number
871 this.context.ReadSequenceNumber++;
873 return dcrFragment;
876 private bool Compare (byte[] array1, byte[] array2)
878 if (array1 == null)
879 return (array2 == null);
880 if (array2 == null)
881 return false;
882 if (array1.Length != array2.Length)
883 return false;
884 for (int i = 0; i < array1.Length; i++) {
885 if (array1[i] != array2[i])
886 return false;
888 return true;
891 #endregion
893 #region CipherSpecV2 processing
895 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
897 TlsStream codes = new TlsStream(buffer);
899 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
901 while (codes.Position < codes.Length)
903 byte check = codes.ReadByte();
905 if (check == 0)
907 // SSL/TLS cipher spec
908 short code = codes.ReadInt16();
909 int index = this.Context.SupportedCiphers.IndexOf(code);
910 if (index != -1)
912 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
913 break;
916 else
918 byte[] tmp = new byte[2];
919 codes.Read(tmp, 0, tmp.Length);
921 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
922 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
924 if (cipher != null)
926 this.Context.Negotiating.Cipher = cipher;
927 break;
932 if (this.Context.Negotiating == null)
934 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
938 private CipherSuite MapV2CipherCode(string prefix, int code)
942 switch (code)
944 case 65664:
945 // TLS_RC4_128_WITH_MD5
946 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
948 case 131200:
949 // TLS_RC4_128_EXPORT40_WITH_MD5
950 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
952 case 196736:
953 // TLS_RC2_CBC_128_CBC_WITH_MD5
954 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
956 case 262272:
957 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
958 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
960 case 327808:
961 // TLS_IDEA_128_CBC_WITH_MD5
962 return null;
964 case 393280:
965 // TLS_DES_64_CBC_WITH_MD5
966 return null;
968 case 458944:
969 // TLS_DES_192_EDE3_CBC_WITH_MD5
970 return null;
972 default:
973 return null;
976 catch
978 return null;
982 #endregion