1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
3 // Copyright (C) 2006 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 protected Stream innerStream;
39 protected Context context;
45 public Context Context
47 get { return this.context; }
48 set { this.context = value; }
55 public RecordProtocol(Stream innerStream, Context context)
57 this.innerStream = innerStream;
58 this.context = context;
59 this.context.RecordProtocol = this;
64 #region Abstract Methods
66 public virtual void SendRecord(HandshakeType type)
69 IAsyncResult ar = this.BeginSendRecord(type, null, null);
71 this.EndSendRecord(ar);
75 protected abstract void ProcessHandshakeMessage(TlsStream handMsg);
77 protected virtual void ProcessChangeCipherSpec ()
79 Context ctx = this.Context;
81 // Reset sequence numbers
82 ctx.ReadSequenceNumber = 0;
84 if (ctx is ClientContext) {
85 ctx.EndSwitchingSecurityParameters (true);
87 ctx.StartSwitchingSecurityParameters (false);
91 public virtual HandshakeMessage GetMessage(HandshakeType type)
93 throw new NotSupportedException();
98 #region Receive Record Async Result
99 private class ReceiveRecordAsyncResult : IAsyncResult
101 private object locker = new object ();
102 private AsyncCallback _userCallback;
103 private object _userState;
104 private Exception _asyncException;
105 private ManualResetEvent handle;
106 private byte[] _resultingBuffer;
107 private Stream _record;
108 private bool completed;
110 private byte[] _initialBuffer;
112 public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
114 _userCallback = userCallback;
115 _userState = userState;
116 _initialBuffer = initialBuffer;
122 get { return _record; }
125 public byte[] ResultingBuffer
127 get { return _resultingBuffer; }
130 public byte[] InitialBuffer
132 get { return _initialBuffer; }
135 public object AsyncState
137 get { return _userState; }
140 public Exception AsyncException
142 get { return _asyncException; }
145 public bool CompletedWithError
149 return false; // Perhaps throw InvalidOperationExcetion?
151 return null != _asyncException;
155 public WaitHandle AsyncWaitHandle
160 handle = new ManualResetEvent (completed);
167 public bool CompletedSynchronously
169 get { return false; }
172 public bool IsCompleted
181 private void SetComplete(Exception ex, byte[] resultingBuffer)
188 _asyncException = ex;
189 _resultingBuffer = resultingBuffer;
193 if (_userCallback != null)
194 _userCallback.BeginInvoke (this, null, null);
198 public void SetComplete(Exception ex)
200 SetComplete(ex, null);
203 public void SetComplete(byte[] resultingBuffer)
205 SetComplete(null, resultingBuffer);
208 public void SetComplete()
210 SetComplete(null, null);
215 #region Receive Record Async Result
216 private class SendRecordAsyncResult : IAsyncResult
218 private object locker = new object ();
219 private AsyncCallback _userCallback;
220 private object _userState;
221 private Exception _asyncException;
222 private ManualResetEvent handle;
223 private HandshakeMessage _message;
224 private bool completed;
226 public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
228 _userCallback = userCallback;
229 _userState = userState;
233 public HandshakeMessage Message
235 get { return _message; }
238 public object AsyncState
240 get { return _userState; }
243 public Exception AsyncException
245 get { return _asyncException; }
248 public bool CompletedWithError
252 return false; // Perhaps throw InvalidOperationExcetion?
254 return null != _asyncException;
258 public WaitHandle AsyncWaitHandle
263 handle = new ManualResetEvent (completed);
270 public bool CompletedSynchronously
272 get { return false; }
275 public bool IsCompleted
284 public void SetComplete(Exception ex)
294 if (_userCallback != null)
295 _userCallback.BeginInvoke (this, null, null);
297 _asyncException = ex;
301 public void SetComplete()
308 #region Reveive Record Methods
310 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
312 if (this.context.ConnectionEnd)
314 throw new TlsException(
315 AlertDescription.InternalError,
316 "The session is finished and it's no longer valid.");
319 byte[] recordTypeBuffer = new byte[1];
321 ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
323 record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);
325 return internalResult;
328 private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
330 ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
331 Stream record = internalResult.Record;
336 int bytesRead = internalResult.Record.EndRead(asyncResult);
338 //We're at the end of the stream. Time to bail.
341 internalResult.SetComplete((byte[])null);
345 // Try to read the Record Content Type
346 int type = internalResult.InitialBuffer[0];
348 // Set last handshake message received to None
349 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
351 ContentType contentType = (ContentType)type;
352 byte[] buffer = this.ReadRecordBuffer(type, record);
355 // record incomplete (at the moment)
356 internalResult.SetComplete((byte[])null);
360 // Decrypt message contents if needed
361 if (contentType == ContentType.Alert && buffer.Length == 2)
364 else if ((this.Context.Read != null) && (this.Context.Read.Cipher != null))
366 buffer = this.decryptRecordFragment (contentType, buffer);
367 DebugHelper.WriteLine ("Decrypted record data", buffer);
373 case ContentType.Alert:
374 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
377 // don't reprocess that memory block
378 record.SetLength (0);
383 case ContentType.ChangeCipherSpec:
384 this.ProcessChangeCipherSpec();
387 case ContentType.ApplicationData:
390 case ContentType.Handshake:
391 TlsStream message = new TlsStream (buffer);
394 this.ProcessHandshakeMessage(message);
398 case (ContentType)0x80:
399 this.context.HandshakeMessages.Write (buffer);
403 throw new TlsException(
404 AlertDescription.UnexpectedMessage,
405 "Unknown record received from server.");
408 internalResult.SetComplete(buffer);
412 internalResult.SetComplete(ex);
417 public byte[] EndReceiveRecord(IAsyncResult asyncResult)
419 ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;
421 if (null == internalResult)
422 throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");
424 if (!internalResult.IsCompleted)
425 internalResult.AsyncWaitHandle.WaitOne();
427 if (internalResult.CompletedWithError)
428 throw internalResult.AsyncException;
430 return internalResult.ResultingBuffer;
433 public byte[] ReceiveRecord(Stream record)
436 IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
437 return this.EndReceiveRecord(ar);
441 private byte[] ReadRecordBuffer (int contentType, Stream record)
446 return this.ReadClientHelloV2(record);
449 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
451 throw new TlsException(AlertDescription.DecodeError);
453 return this.ReadStandardRecordBuffer(record);
457 private byte[] ReadClientHelloV2 (Stream record)
459 int msgLength = record.ReadByte ();
460 // process further only if the whole record is available
461 if (record.CanSeek && (msgLength + 1 > record.Length))
466 byte[] message = new byte[msgLength];
467 record.Read (message, 0, msgLength);
469 int msgType = message [0];
472 throw new TlsException(AlertDescription.DecodeError);
474 int protocol = (message [1] << 8 | message [2]);
475 int cipherSpecLength = (message [3] << 8 | message [4]);
476 int sessionIdLength = (message [5] << 8 | message [6]);
477 int challengeLength = (message [7] << 8 | message [8]);
478 int length = (challengeLength > 32) ? 32 : challengeLength;
481 byte[] cipherSpecV2 = new byte[cipherSpecLength];
482 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
485 byte[] sessionId = new byte[sessionIdLength];
486 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
489 byte[] challenge = new byte[challengeLength];
490 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
492 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
494 throw new TlsException(AlertDescription.DecodeError);
497 // Updated the Session ID
498 if (sessionId.Length > 0)
500 this.context.SessionId = sessionId;
503 // Update the protocol version
504 this.Context.ChangeProtocol((short)protocol);
506 // Select the Cipher suite
507 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
509 // Updated the Client Random
510 this.context.ClientRandom = new byte [32]; // Always 32
511 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
512 // 2. right justify (0) challenge in ClientRandom if less than 32
513 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
516 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
517 this.context.ProtocolNegotiated = true;
522 private byte[] ReadStandardRecordBuffer (Stream record)
524 byte[] header = new byte[4];
525 if (record.Read (header, 0, 4) != 4)
526 throw new TlsException ("buffer underrun");
528 short protocol = (short)((header [0] << 8) | header [1]);
529 short length = (short)((header [2] << 8) | header [3]);
531 // process further only if the whole record is available
532 // note: the first 5 bytes aren't part of the length
533 if (record.CanSeek && (length + 5 > record.Length))
539 int totalReceived = 0;
540 byte[] buffer = new byte[length];
541 while (totalReceived != length)
543 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
545 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
546 if (0 == justReceived)
548 throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
551 totalReceived += justReceived;
554 // Check that the message has a valid protocol version
555 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
557 throw new TlsException(
558 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
561 DebugHelper.WriteLine("Record data", buffer);
566 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
570 case AlertLevel.Fatal:
571 throw new TlsException(alertLevel, alertDesc);
573 case AlertLevel.Warning:
577 case AlertDescription.CloseNotify:
578 this.context.ConnectionEnd = true;
587 #region Send Alert Methods
589 public void SendAlert(AlertDescription description)
591 this.SendAlert(new Alert(description));
594 public void SendAlert(
596 AlertDescription description)
598 this.SendAlert(new Alert(level, description));
601 public void SendAlert(Alert alert)
604 AlertDescription description;
608 DebugHelper.WriteLine(">>>> Write Alert NULL");
609 level = AlertLevel.Fatal;
610 description = AlertDescription.InternalError;
613 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
615 description = alert.Description;
616 close = alert.IsCloseNotify;
620 this.SendRecord (ContentType.Alert, new byte[2] { (byte) level, (byte) description });
624 this.context.ConnectionEnd = true;
630 #region Send Record Methods
632 public void SendChangeCipherSpec()
634 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
636 // Send Change Cipher Spec message with the current cipher
637 // or as plain text if this is the initial negotiation
638 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
640 Context ctx = this.context;
642 // Reset sequence numbers
643 ctx.WriteSequenceNumber = 0;
645 // all further data sent will be encrypted with the negotiated
646 // security parameters (now the current parameters)
647 if (ctx is ClientContext) {
648 ctx.StartSwitchingSecurityParameters (true);
650 ctx.EndSwitchingSecurityParameters (false);
654 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
656 HandshakeMessage msg = this.GetMessage(handshakeType);
660 DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
662 SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
664 this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
666 return internalResult;
669 private void InternalSendRecordCallback(IAsyncResult ar)
671 SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
675 this.EndSendRecord(ar);
678 internalResult.Message.Update();
680 // Reset message contents
681 internalResult.Message.Reset();
683 internalResult.SetComplete();
687 internalResult.SetComplete(ex);
691 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
693 if (this.context.ConnectionEnd)
695 throw new TlsException(
696 AlertDescription.InternalError,
697 "The session is finished and it's no longer valid.");
700 byte[] record = this.EncodeRecord(contentType, recordData);
702 return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
705 public void EndSendRecord(IAsyncResult asyncResult)
707 if (asyncResult is SendRecordAsyncResult)
709 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
710 if (!internalResult.IsCompleted)
711 internalResult.AsyncWaitHandle.WaitOne();
712 if (internalResult.CompletedWithError)
713 throw internalResult.AsyncException;
717 this.innerStream.EndWrite(asyncResult);
721 public void SendRecord(ContentType contentType, byte[] recordData)
723 IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
725 this.EndSendRecord(ar);
728 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
730 return this.EncodeRecord(
737 public byte[] EncodeRecord(
738 ContentType contentType,
743 if (this.context.ConnectionEnd)
745 throw new TlsException(
746 AlertDescription.InternalError,
747 "The session is finished and it's no longer valid.");
750 TlsStream record = new TlsStream();
752 int position = offset;
754 while (position < ( offset + count ))
756 short fragmentLength = 0;
759 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
761 fragmentLength = Context.MAX_FRAGMENT_SIZE;
765 fragmentLength = (short)(count + offset - position);
768 // Fill the fragment data
769 fragment = new byte[fragmentLength];
770 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
772 if ((this.Context.Write != null) && (this.Context.Write.Cipher != null))
775 fragment = this.encryptRecordFragment (contentType, fragment);
779 record.Write((byte)contentType);
780 record.Write(this.context.Protocol);
781 record.Write((short)fragment.Length);
782 record.Write(fragment);
784 DebugHelper.WriteLine("Record data", fragment);
786 // Update buffer position
787 position += fragmentLength;
790 return record.ToArray();
795 #region Cryptography Methods
797 private byte[] encryptRecordFragment(
798 ContentType contentType,
803 // Calculate message MAC
804 if (this.Context is ClientContext)
806 mac = this.context.Write.Cipher.ComputeClientRecordMAC(contentType, fragment);
810 mac = this.context.Write.Cipher.ComputeServerRecordMAC (contentType, fragment);
813 DebugHelper.WriteLine(">>>> Record MAC", mac);
815 // Encrypt the message
816 byte[] ecr = this.context.Write.Cipher.EncryptRecord (fragment, mac);
818 // Update sequence number
819 this.context.WriteSequenceNumber++;
824 private byte[] decryptRecordFragment(
825 ContentType contentType,
828 byte[] dcrFragment = null;
829 byte[] dcrMAC = null;
833 this.context.Read.Cipher.DecryptRecord (fragment, out dcrFragment, out dcrMAC);
837 if (this.context is ServerContext)
839 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
845 // Generate record MAC
848 if (this.Context is ClientContext)
850 mac = this.context.Read.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
854 mac = this.context.Read.Cipher.ComputeClientRecordMAC (contentType, dcrFragment);
857 DebugHelper.WriteLine(">>>> Record MAC", mac);
860 if (!Compare (mac, dcrMAC))
862 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
865 // Update sequence number
866 this.context.ReadSequenceNumber++;
871 private bool Compare (byte[] array1, byte[] array2)
874 return (array2 == null);
877 if (array1.Length != array2.Length)
879 for (int i = 0; i < array1.Length; i++) {
880 if (array1[i] != array2[i])
888 #region CipherSpecV2 processing
890 private void ProcessCipherSpecV2Buffer (SecurityProtocolType protocol, byte[] buffer)
892 TlsStream codes = new TlsStream(buffer);
894 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
896 while (codes.Position < codes.Length)
898 byte check = codes.ReadByte();
902 // SSL/TLS cipher spec
903 short code = codes.ReadInt16();
904 int index = this.Context.SupportedCiphers.IndexOf(code);
907 this.Context.Negotiating.Cipher = this.Context.SupportedCiphers[index];
913 byte[] tmp = new byte[2];
914 codes.Read(tmp, 0, tmp.Length);
916 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
917 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
921 this.Context.Negotiating.Cipher = cipher;
927 if (this.Context.Negotiating == null)
929 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
933 private CipherSuite MapV2CipherCode(string prefix, int code)
940 // TLS_RC4_128_WITH_MD5
941 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
944 // TLS_RC4_128_EXPORT40_WITH_MD5
945 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
948 // TLS_RC2_CBC_128_CBC_WITH_MD5
949 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
952 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
953 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
956 // TLS_IDEA_128_CBC_WITH_MD5
960 // TLS_DES_64_CBC_WITH_MD5
964 // TLS_DES_192_EDE3_CBC_WITH_MD5