1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006-2007 Novell, Inc (http://www.novell.com)
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:
13 // The above copyright notice and this permission notice shall be
14 // included in all copies or substantial portions of the Software.
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.
26 using System.Collections;
28 using System.Threading;
30 using Mono.Security.Protocol.Tls.Handshake;
32 namespace Mono.Security.Protocol.Tls
34 internal abstract class RecordProtocol
38 private static ManualResetEvent record_processing = new ManualResetEvent (true);
40 protected Stream innerStream;
41 protected Context context;
47 public Context Context
49 get { return this.context; }
50 set { this.context = value; }
57 public RecordProtocol(Stream innerStream, Context context)
59 this.innerStream = innerStream;
60 this.context = context;
61 this.context.RecordProtocol = this;
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);
89 ctx.StartSwitchingSecurityParameters (false);
93 public virtual HandshakeMessage GetMessage(HandshakeType type)
95 throw new NotSupportedException();
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;
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
151 return false; // Perhaps throw InvalidOperationExcetion?
153 return null != _asyncException;
157 public WaitHandle AsyncWaitHandle
162 handle = new ManualResetEvent (completed);
169 public bool CompletedSynchronously
171 get { return false; }
174 public bool IsCompleted
183 private void SetComplete(Exception ex, byte[] resultingBuffer)
190 _asyncException = ex;
191 _resultingBuffer = resultingBuffer;
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);
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;
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
254 return false; // Perhaps throw InvalidOperationExcetion?
256 return null != _asyncException;
260 public WaitHandle AsyncWaitHandle
265 handle = new ManualResetEvent (completed);
272 public bool CompletedSynchronously
274 get { return false; }
277 public bool IsCompleted
286 public void SetComplete(Exception ex)
296 if (_userCallback != null)
297 _userCallback.BeginInvoke (this, null, null);
299 _asyncException = ex;
303 public void SetComplete()
310 #region Reveive Record Methods
312 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
314 if (this.context.ReceivedConnectionEnd)
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.
344 internalResult.SetComplete((byte[])null);
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);
358 // record incomplete (at the moment)
359 internalResult.SetComplete((byte[])null);
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);
376 case ContentType.Alert:
377 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
380 // don't reprocess that memory block
381 record.SetLength (0);
386 case ContentType.ChangeCipherSpec:
387 this.ProcessChangeCipherSpec();
390 case ContentType.ApplicationData:
393 case ContentType.Handshake:
394 TlsStream message = new TlsStream (buffer);
397 this.ProcessHandshakeMessage(message);
401 case (ContentType)0x80:
402 this.context.HandshakeMessages.Write (buffer);
406 throw new TlsException(
407 AlertDescription.UnexpectedMessage,
408 "Unknown record received from server.");
411 internalResult.SetComplete(buffer);
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 ();
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)
451 return this.ReadClientHelloV2(record);
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))
471 byte[] message = new byte[msgLength];
472 record.Read (message, 0, msgLength);
474 int msgType = message [0];
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;
486 byte[] cipherSpecV2 = new byte[cipherSpecLength];
487 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
490 byte[] sessionId = new byte[sessionIdLength];
491 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
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);
521 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
522 this.context.ProtocolNegotiated = true;
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))
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);
571 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
575 case AlertLevel.Fatal:
576 throw new TlsException(alertLevel, alertDesc);
578 case AlertLevel.Warning:
582 case AlertDescription.CloseNotify:
583 this.context.ReceivedConnectionEnd = true;
592 #region Send Alert Methods
594 public void SendAlert(AlertDescription description)
596 this.SendAlert(new Alert(description));
599 public void SendAlert(
601 AlertDescription description)
603 this.SendAlert(new Alert(level, description));
606 public void SendAlert(Alert alert)
609 AlertDescription description;
613 DebugHelper.WriteLine(">>>> Write Alert NULL");
614 level = AlertLevel.Fatal;
615 description = AlertDescription.InternalError;
618 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
620 description = alert.Description;
621 close = alert.IsCloseNotify;
625 this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
628 this.context.SentConnectionEnd = true;
634 #region Send Record Methods
636 public void SendChangeCipherSpec()
638 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
640 // Send Change Cipher Spec message with the current cipher
641 // or as plain text if this is the initial negotiation
642 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
644 Context ctx = this.context;
646 // Reset sequence numbers
647 ctx.WriteSequenceNumber = 0;
649 // all further data sent will be encrypted with the negotiated
650 // security parameters (now the current parameters)
651 if (ctx is ClientContext) {
652 ctx.StartSwitchingSecurityParameters (true);
654 ctx.EndSwitchingSecurityParameters (false);
658 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
660 HandshakeMessage msg = this.GetMessage(handshakeType);
664 DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
666 SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
668 this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
670 return internalResult;
673 private void InternalSendRecordCallback(IAsyncResult ar)
675 SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
679 this.EndSendRecord(ar);
682 internalResult.Message.Update();
684 // Reset message contents
685 internalResult.Message.Reset();
687 internalResult.SetComplete();
691 internalResult.SetComplete(ex);
695 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
697 if (this.context.SentConnectionEnd)
699 throw new TlsException(
700 AlertDescription.InternalError,
701 "The session is finished and it's no longer valid.");
704 byte[] record = this.EncodeRecord(contentType, recordData);
706 return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
709 public void EndSendRecord(IAsyncResult asyncResult)
711 if (asyncResult is SendRecordAsyncResult)
713 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
714 if (!internalResult.IsCompleted)
715 internalResult.AsyncWaitHandle.WaitOne();
716 if (internalResult.CompletedWithError)
717 throw internalResult.AsyncException;
721 this.innerStream.EndWrite(asyncResult);
725 public void SendRecord(ContentType contentType, byte[] recordData)
727 IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
729 this.EndSendRecord(ar);
732 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
734 return this.EncodeRecord(
741 public byte[] EncodeRecord(
742 ContentType contentType,
747 if (this.context.SentConnectionEnd)
749 throw new TlsException(
750 AlertDescription.InternalError,
751 "The session is finished and it's no longer valid.");
754 TlsStream record = new TlsStream();
756 int position = offset;
758 while (position < ( offset + count ))
760 short fragmentLength = 0;
763 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
765 fragmentLength = Context.MAX_FRAGMENT_SIZE;
769 fragmentLength = (short)(count + offset - position);
772 // Fill the fragment data
773 fragment = new byte[fragmentLength];
774 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
776 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
779 fragment = this.encryptRecordFragment (contentType, fragment);
783 record.Write((byte)contentType);
784 record.Write(this.context.Protocol);
785 record.Write((short)fragment.Length);
786 record.Write(fragment);
788 DebugHelper.WriteLine("Record data", fragment);
790 // Update buffer position
791 position += fragmentLength;
794 return record.ToArray();
799 #region Cryptography Methods
801 private byte[] encryptRecordFragment(
802 ContentType contentType,
807 // Calculate message MAC
808 if (this.Context is ClientContext)
810 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
814 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
817 DebugHelper.WriteLine(">>>> Record MAC", mac);
819 // Encrypt the message
820 byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
822 // Update sequence number
823 this.context.WriteSequenceNumber++;
828 private byte[] decryptRecordFragment(
829 ContentType contentType,
832 byte[] dcrFragment = null;
833 byte[] dcrMAC = null;
837 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
841 if (this.context is ServerContext)
843 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
848 // Generate record MAC
851 if (this.Context is ClientContext)
853 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
857 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
860 DebugHelper.WriteLine(">>>> Record MAC", mac);
863 if (!Compare (mac, dcrMAC))
865 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
868 // Update sequence number
869 this.context.ReadSequenceNumber++;
874 private bool Compare (byte[] array1, byte[] array2)
877 return (array2 == null);
880 if (array1.Length != array2.Length)
882 for (int i = 0; i < array1.Length; i++) {
883 if (array1[i] != array2[i])
891 #region CipherSpecV2 processing
893 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
895 TlsStream codes = new TlsStream(buffer);
897 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
899 while (codes.Position < codes.Length)
901 byte check = codes.ReadByte();
905 // SSL/TLS cipher spec
906 short code = codes.ReadInt16();
907 int index = this.Context.SupportedCiphers.IndexOf(code);
910 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
916 byte[] tmp = new byte[2];
917 codes.Read(tmp, 0, tmp.Length);
919 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
920 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
924 this.Context.Negotiating.Cipher = cipher;
930 if (this.Context.Negotiating == null)
932 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
936 private CipherSuite MapV2CipherCode(string prefix, int code)
943 // TLS_RC4_128_WITH_MD5
944 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
947 // TLS_RC4_128_EXPORT40_WITH_MD5
948 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
951 // TLS_RC2_CBC_128_CBC_WITH_MD5
952 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
955 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
956 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
959 // TLS_IDEA_128_CBC_WITH_MD5
963 // TLS_DES_64_CBC_WITH_MD5
967 // TLS_DES_192_EDE3_CBC_WITH_MD5