1 // Transport Security Layer (TLS)
2 // Copyright (c) 2003-2004 Carlos Guzman Alvarez
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.Security.Cryptography;
29 using System.Security.Cryptography.X509Certificates;
30 using System.Threading;
32 using Mono.Security.Protocol.Tls.Handshake;
34 namespace Mono.Security.Protocol.Tls
36 internal abstract class RecordProtocol
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);
78 protected abstract void ProcessChangeCipherSpec();
80 public virtual HandshakeMessage GetMessage(HandshakeType type)
82 throw new NotSupportedException();
87 #region Receive Record Async Result
88 private class ReceiveRecordAsyncResult : IAsyncResult
90 private object locker = new object ();
91 private AsyncCallback _userCallback;
92 private object _userState;
93 private Exception _asyncException;
94 private ManualResetEvent handle;
95 private byte[] _resultingBuffer;
96 private Stream _record;
97 private bool completed;
99 private byte[] _initialBuffer;
101 public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
103 _userCallback = userCallback;
104 _userState = userState;
105 _initialBuffer = initialBuffer;
111 get { return _record; }
114 public byte[] ResultingBuffer
116 get { return _resultingBuffer; }
119 public byte[] InitialBuffer
121 get { return _initialBuffer; }
124 public object AsyncState
126 get { return _userState; }
129 public Exception AsyncException
131 get { return _asyncException; }
134 public bool CompletedWithError
138 return false; // Perhaps throw InvalidOperationExcetion?
140 return null != _asyncException;
144 public WaitHandle AsyncWaitHandle
149 handle = new ManualResetEvent (completed);
156 public bool CompletedSynchronously
158 get { return false; }
161 public bool IsCompleted
170 private void SetComplete(Exception ex, byte[] resultingBuffer)
177 _asyncException = ex;
178 _resultingBuffer = resultingBuffer;
182 if (_userCallback != null)
183 _userCallback.BeginInvoke (this, null, null);
187 public void SetComplete(Exception ex)
189 SetComplete(ex, null);
192 public void SetComplete(byte[] resultingBuffer)
194 SetComplete(null, resultingBuffer);
197 public void SetComplete()
199 SetComplete(null, null);
204 #region Receive Record Async Result
205 private class SendRecordAsyncResult : IAsyncResult
207 private object locker = new object ();
208 private AsyncCallback _userCallback;
209 private object _userState;
210 private Exception _asyncException;
211 private ManualResetEvent handle;
212 private HandshakeMessage _message;
213 private bool completed;
215 public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
217 _userCallback = userCallback;
218 _userState = userState;
222 public HandshakeMessage Message
224 get { return _message; }
227 public object AsyncState
229 get { return _userState; }
232 public Exception AsyncException
234 get { return _asyncException; }
237 public bool CompletedWithError
241 return false; // Perhaps throw InvalidOperationExcetion?
243 return null != _asyncException;
247 public WaitHandle AsyncWaitHandle
252 handle = new ManualResetEvent (completed);
259 public bool CompletedSynchronously
261 get { return false; }
264 public bool IsCompleted
273 public void SetComplete(Exception ex)
283 if (_userCallback != null)
284 _userCallback.BeginInvoke (this, null, null);
286 _asyncException = ex;
290 public void SetComplete()
297 #region Reveive Record Methods
299 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
301 if (this.context.ConnectionEnd)
303 throw new TlsException(
304 AlertDescription.InternalError,
305 "The session is finished and it's no longer valid.");
308 byte[] recordTypeBuffer = new byte[1];
310 ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
312 record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);
314 return internalResult;
317 private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
319 ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
320 Stream record = internalResult.Record;
325 int bytesRead = internalResult.Record.EndRead(asyncResult);
327 //We're at the end of the stream. Time to bail.
330 internalResult.SetComplete((byte[])null);
334 // Try to read the Record Content Type
335 int type = internalResult.InitialBuffer[0];
337 // Set last handshake message received to None
338 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
340 ContentType contentType = (ContentType)type;
341 byte[] buffer = this.ReadRecordBuffer(type, record);
344 // record incomplete (at the moment)
345 internalResult.SetComplete((byte[])null);
349 // Decrypt message contents if needed
350 if (contentType == ContentType.Alert && buffer.Length == 2)
355 if (this.context.IsActual && contentType != ContentType.ChangeCipherSpec)
357 buffer = this.decryptRecordFragment(contentType, buffer);
358 DebugHelper.WriteLine("Decrypted record data", buffer);
365 case ContentType.Alert:
366 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
369 // don't reprocess that memory block
370 record.SetLength (0);
375 case ContentType.ChangeCipherSpec:
376 this.ProcessChangeCipherSpec();
379 case ContentType.ApplicationData:
382 case ContentType.Handshake:
383 TlsStream message = new TlsStream (buffer);
386 this.ProcessHandshakeMessage(message);
390 case (ContentType)0x80:
391 this.context.HandshakeMessages.Write (buffer);
395 throw new TlsException(
396 AlertDescription.UnexpectedMessage,
397 "Unknown record received from server.");
401 internalResult.SetComplete(buffer);
405 internalResult.SetComplete(ex);
410 public byte[] EndReceiveRecord(IAsyncResult asyncResult)
412 ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;
414 if (null == internalResult)
415 throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");
417 if (!internalResult.IsCompleted)
418 internalResult.AsyncWaitHandle.WaitOne();
420 if (internalResult.CompletedWithError)
421 throw internalResult.AsyncException;
423 return internalResult.ResultingBuffer;
426 public byte[] ReceiveRecord(Stream record)
429 IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
430 return this.EndReceiveRecord(ar);
434 private byte[] ReadRecordBuffer (int contentType, Stream record)
439 return this.ReadClientHelloV2(record);
442 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
444 throw new TlsException(AlertDescription.DecodeError);
446 return this.ReadStandardRecordBuffer(record);
450 private byte[] ReadClientHelloV2 (Stream record)
452 int msgLength = record.ReadByte ();
453 // process further only if the whole record is available
454 if (record.CanSeek && (msgLength + 1 > record.Length))
459 byte[] message = new byte[msgLength];
460 record.Read (message, 0, msgLength);
462 int msgType = message [0];
465 throw new TlsException(AlertDescription.DecodeError);
467 int protocol = (message [1] << 8 | message [2]);
468 int cipherSpecLength = (message [3] << 8 | message [4]);
469 int sessionIdLength = (message [5] << 8 | message [6]);
470 int challengeLength = (message [7] << 8 | message [8]);
471 int length = (challengeLength > 32) ? 32 : challengeLength;
474 byte[] cipherSpecV2 = new byte[cipherSpecLength];
475 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
478 byte[] sessionId = new byte[sessionIdLength];
479 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
482 byte[] challenge = new byte[challengeLength];
483 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
485 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
487 throw new TlsException(AlertDescription.DecodeError);
490 // Updated the Session ID
491 if (sessionId.Length > 0)
493 this.context.SessionId = sessionId;
496 // Update the protocol version
497 this.Context.ChangeProtocol((short)protocol);
499 // Select the Cipher suite
500 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
502 // Updated the Client Random
503 this.context.ClientRandom = new byte [32]; // Always 32
504 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
505 // 2. right justify (0) challenge in ClientRandom if less than 32
506 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
509 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
510 this.context.ProtocolNegotiated = true;
515 private byte[] ReadStandardRecordBuffer (Stream record)
517 short protocol = this.ReadShort(record);
518 short length = this.ReadShort(record);
520 // process further only if the whole record is available
521 // note: the first 5 bytes aren't part of the length
522 if (record.CanSeek && (length + 5 > record.Length))
528 int totalReceived = 0;
529 byte[] buffer = new byte[length];
530 while (totalReceived != length)
532 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
534 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
535 if (0 == justReceived)
537 throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
540 totalReceived += justReceived;
543 // Check that the message has a valid protocol version
544 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
546 throw new TlsException(
547 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
550 DebugHelper.WriteLine("Record data", buffer);
555 private short ReadShort(Stream record)
557 byte[] b = new byte[2];
558 record.Read(b, 0, b.Length);
560 short val = BitConverter.ToInt16(b, 0);
562 return System.Net.IPAddress.HostToNetworkOrder(val);
565 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
569 case AlertLevel.Fatal:
570 throw new TlsException(alertLevel, alertDesc);
572 case AlertLevel.Warning:
576 case AlertDescription.CloseNotify:
577 this.context.ConnectionEnd = true;
586 #region Send Alert Methods
588 public void SendAlert(AlertDescription description)
590 this.SendAlert(new Alert(description));
593 public void SendAlert(
595 AlertDescription description)
597 this.SendAlert(new Alert(level, description));
600 public void SendAlert(Alert alert)
602 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
607 new byte[]{(byte)alert.Level, (byte)alert.Description});
609 if (alert.IsCloseNotify)
611 this.context.ConnectionEnd = true;
617 #region Send Record Methods
619 public void SendChangeCipherSpec()
621 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
623 // Send Change Cipher Spec message as a plain message
624 this.context.IsActual = false;
626 // Send Change Cipher Spec message
627 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
629 // Reset sequence numbers
630 this.context.WriteSequenceNumber = 0;
632 // Make the pending state to be the current state
633 this.context.IsActual = true;
636 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
638 HandshakeMessage msg = this.GetMessage(handshakeType);
642 DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
644 SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
646 this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
648 return internalResult;
651 private void InternalSendRecordCallback(IAsyncResult ar)
653 SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
657 this.EndSendRecord(ar);
660 internalResult.Message.Update();
662 // Reset message contents
663 internalResult.Message.Reset();
665 internalResult.SetComplete();
669 internalResult.SetComplete(ex);
673 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
675 if (this.context.ConnectionEnd)
677 throw new TlsException(
678 AlertDescription.InternalError,
679 "The session is finished and it's no longer valid.");
682 byte[] record = this.EncodeRecord(contentType, recordData);
684 return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
687 public void EndSendRecord(IAsyncResult asyncResult)
689 if (asyncResult is SendRecordAsyncResult)
691 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
692 if (!internalResult.IsCompleted)
693 internalResult.AsyncWaitHandle.WaitOne();
694 if (internalResult.CompletedWithError)
695 throw internalResult.AsyncException;
699 this.innerStream.EndWrite(asyncResult);
703 public void SendRecord(ContentType contentType, byte[] recordData)
705 IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
707 this.EndSendRecord(ar);
710 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
712 return this.EncodeRecord(
719 public byte[] EncodeRecord(
720 ContentType contentType,
725 if (this.context.ConnectionEnd)
727 throw new TlsException(
728 AlertDescription.InternalError,
729 "The session is finished and it's no longer valid.");
732 TlsStream record = new TlsStream();
734 int position = offset;
736 while (position < ( offset + count ))
738 short fragmentLength = 0;
741 if ((count + offset - position) > Context.MAX_FRAGMENT_SIZE)
743 fragmentLength = Context.MAX_FRAGMENT_SIZE;
747 fragmentLength = (short)(count + offset - position);
750 // Fill the fragment data
751 fragment = new byte[fragmentLength];
752 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
754 if (this.context.IsActual)
757 fragment = this.encryptRecordFragment(contentType, fragment);
761 record.Write((byte)contentType);
762 record.Write(this.context.Protocol);
763 record.Write((short)fragment.Length);
764 record.Write(fragment);
766 DebugHelper.WriteLine("Record data", fragment);
768 // Update buffer position
769 position += fragmentLength;
772 return record.ToArray();
777 #region Cryptography Methods
779 private byte[] encryptRecordFragment(
780 ContentType contentType,
785 // Calculate message MAC
786 if (this.Context is ClientContext)
788 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
792 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
795 DebugHelper.WriteLine(">>>> Record MAC", mac);
797 // Encrypt the message
798 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
800 // Set new Client Cipher IV
801 if (this.context.Cipher.CipherMode == CipherMode.CBC)
803 byte[] iv = new byte[this.context.Cipher.IvSize];
804 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
806 this.context.Cipher.UpdateClientCipherIV(iv);
809 // Update sequence number
810 this.context.WriteSequenceNumber++;
815 private byte[] decryptRecordFragment(
816 ContentType contentType,
819 byte[] dcrFragment = null;
820 byte[] dcrMAC = null;
821 bool badRecordMac = false;
825 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
829 if (this.context is ServerContext)
831 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
837 // Generate record MAC
840 if (this.Context is ClientContext)
842 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
846 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
849 DebugHelper.WriteLine(">>>> Record MAC", mac);
852 if (mac.Length != dcrMAC.Length)
858 for (int i = 0; i < mac.Length; i++)
860 if (mac[i] != dcrMAC[i])
870 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
873 // Update sequence number
874 this.context.ReadSequenceNumber++;
881 #region CipherSpecV2 processing
883 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
885 TlsStream codes = new TlsStream(buffer);
887 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
889 while (codes.Position < codes.Length)
891 byte check = codes.ReadByte();
895 // SSL/TLS cipher spec
896 short code = codes.ReadInt16();
897 int index = this.Context.SupportedCiphers.IndexOf(code);
900 this.Context.Cipher = this.Context.SupportedCiphers[index];
906 byte[] tmp = new byte[2];
907 codes.Read(tmp, 0, tmp.Length);
909 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
910 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
914 this.Context.Cipher = cipher;
920 if (this.Context.Cipher == null)
922 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
926 private CipherSuite MapV2CipherCode(string prefix, int code)
933 // TLS_RC4_128_WITH_MD5
934 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
937 // TLS_RC4_128_EXPORT40_WITH_MD5
938 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
941 // TLS_RC2_CBC_128_CBC_WITH_MD5
942 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
945 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
946 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
949 // TLS_IDEA_128_CBC_WITH_MD5
953 // TLS_DES_64_CBC_WITH_MD5
957 // TLS_DES_192_EDE3_CBC_WITH_MD5