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 AsyncCallback _userCallback;
91 private object _userState;
92 private Exception _asyncException;
93 private ManualResetEvent _complete;
94 private byte[] _resultingBuffer;
95 private Stream _record;
97 private byte[] _initialBuffer;
99 public ReceiveRecordAsyncResult(AsyncCallback userCallback, object userState, byte[] initialBuffer, Stream record)
101 _userCallback = userCallback;
102 _userState = userState;
103 _complete = new ManualResetEvent(false);
104 _initialBuffer = initialBuffer;
110 get { return _record; }
113 public byte[] ResultingBuffer
115 get { return _resultingBuffer; }
118 public byte[] InitialBuffer
120 get { return _initialBuffer; }
123 public object AsyncState
125 get { return _userState; }
128 public Exception AsyncException
130 get { return _asyncException; }
133 public bool CompletedWithError
135 get { return null != _asyncException; }
138 public WaitHandle AsyncWaitHandle
140 get { return _complete; }
143 public bool CompletedSynchronously
145 get { return false; }
148 public bool IsCompleted
150 get { return _complete.WaitOne(0, false); }
153 private void SetComplete(Exception ex, byte[] resultingBuffer)
155 if (this.IsCompleted)
160 if (this.IsCompleted)
163 _asyncException = ex;
164 _resultingBuffer = resultingBuffer;
168 if (null != _userCallback)
169 _userCallback (this);
173 public void SetComplete(Exception ex)
175 SetComplete(ex, null);
178 public void SetComplete(byte[] resultingBuffer)
180 SetComplete(null, resultingBuffer);
183 public void SetComplete()
185 SetComplete(null, null);
190 #region Receive Record Async Result
191 private class SendRecordAsyncResult : IAsyncResult
193 private AsyncCallback _userCallback;
194 private object _userState;
195 private Exception _asyncException;
196 private ManualResetEvent _complete;
197 private HandshakeMessage _message;
199 public SendRecordAsyncResult(AsyncCallback userCallback, object userState, HandshakeMessage message)
201 _userCallback = userCallback;
202 _userState = userState;
203 _complete = new ManualResetEvent(false);
207 public HandshakeMessage Message
209 get { return _message; }
212 public object AsyncState
214 get { return _userState; }
217 public Exception AsyncException
219 get { return _asyncException; }
222 public bool CompletedWithError
224 get { return null != _asyncException; }
227 public WaitHandle AsyncWaitHandle
229 get { return _complete; }
232 public bool CompletedSynchronously
234 get { return false; }
237 public bool IsCompleted
239 get { return _complete.WaitOne(0, false); }
242 public void SetComplete(Exception ex)
244 if (this.IsCompleted)
249 if (this.IsCompleted)
252 _asyncException = ex;
256 if (null != _userCallback)
257 _userCallback (this);
261 public void SetComplete()
268 #region Reveive Record Methods
270 public IAsyncResult BeginReceiveRecord(Stream record, AsyncCallback callback, object state)
272 if (this.context.ConnectionEnd)
274 throw new TlsException(
275 AlertDescription.InternalError,
276 "The session is finished and it's no longer valid.");
279 byte[] recordTypeBuffer = new byte[1];
281 ReceiveRecordAsyncResult internalResult = new ReceiveRecordAsyncResult(callback, state, recordTypeBuffer, record);
283 record.BeginRead(internalResult.InitialBuffer, 0, internalResult.InitialBuffer.Length, new AsyncCallback(InternalReceiveRecordCallback), internalResult);
285 return internalResult;
288 private void InternalReceiveRecordCallback(IAsyncResult asyncResult)
290 ReceiveRecordAsyncResult internalResult = asyncResult.AsyncState as ReceiveRecordAsyncResult;
291 Stream record = internalResult.Record;
296 int bytesRead = internalResult.Record.EndRead(asyncResult);
298 //We're at the end of the stream. Time to bail.
301 internalResult.SetComplete((byte[])null);
305 // Try to read the Record Content Type
306 int type = internalResult.InitialBuffer[0];
308 // Set last handshake message received to None
309 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
311 ContentType contentType = (ContentType)type;
312 byte[] buffer = this.ReadRecordBuffer(type, record);
315 // record incomplete (at the moment)
316 internalResult.SetComplete((byte[])null);
320 // Decrypt message contents if needed
321 if (contentType == ContentType.Alert && buffer.Length == 2)
326 if (this.context.IsActual && contentType != ContentType.ChangeCipherSpec)
328 buffer = this.decryptRecordFragment(contentType, buffer);
329 DebugHelper.WriteLine("Decrypted record data", buffer);
336 case ContentType.Alert:
337 this.ProcessAlert((AlertLevel)buffer [0], (AlertDescription)buffer [1]);
340 // don't reprocess that memory block
341 record.SetLength (0);
346 case ContentType.ChangeCipherSpec:
347 this.ProcessChangeCipherSpec();
350 case ContentType.ApplicationData:
353 case ContentType.Handshake:
354 TlsStream message = new TlsStream (buffer);
357 this.ProcessHandshakeMessage(message);
361 // FIXME / MCS bug - http://bugzilla.ximian.com/show_bug.cgi?id=67711
362 // case (ContentType)0x80:
363 // this.context.HandshakeMessages.Write (result);
367 if (contentType != (ContentType)0x80)
369 throw new TlsException(
370 AlertDescription.UnexpectedMessage,
371 "Unknown record received from server.");
373 this.context.HandshakeMessages.Write (buffer);
377 internalResult.SetComplete(buffer);
381 internalResult.SetComplete(ex);
386 public byte[] EndReceiveRecord(IAsyncResult asyncResult)
388 ReceiveRecordAsyncResult internalResult = asyncResult as ReceiveRecordAsyncResult;
390 if (null == internalResult)
391 throw new ArgumentException("Either the provided async result is null or was not created by this RecordProtocol.");
393 internalResult.AsyncWaitHandle.WaitOne();
395 if (internalResult.CompletedWithError)
396 throw internalResult.AsyncException;
398 return internalResult.ResultingBuffer;
401 public byte[] ReceiveRecord(Stream record)
404 IAsyncResult ar = this.BeginReceiveRecord(record, null, null);
405 return this.EndReceiveRecord(ar);
409 private byte[] ReadRecordBuffer (int contentType, Stream record)
414 return this.ReadClientHelloV2(record);
417 if (!Enum.IsDefined(typeof(ContentType), (ContentType)contentType))
419 throw new TlsException(AlertDescription.DecodeError);
421 return this.ReadStandardRecordBuffer(record);
425 private byte[] ReadClientHelloV2 (Stream record)
427 int msgLength = record.ReadByte ();
428 // process further only if the whole record is available
429 if (record.CanSeek && (msgLength + 1 > record.Length))
434 byte[] message = new byte[msgLength];
435 record.Read (message, 0, msgLength);
437 int msgType = message [0];
440 throw new TlsException(AlertDescription.DecodeError);
442 int protocol = (message [1] << 8 | message [2]);
443 int cipherSpecLength = (message [3] << 8 | message [4]);
444 int sessionIdLength = (message [5] << 8 | message [6]);
445 int challengeLength = (message [7] << 8 | message [8]);
446 int length = (challengeLength > 32) ? 32 : challengeLength;
449 byte[] cipherSpecV2 = new byte[cipherSpecLength];
450 Buffer.BlockCopy (message, 9, cipherSpecV2, 0, cipherSpecLength);
453 byte[] sessionId = new byte[sessionIdLength];
454 Buffer.BlockCopy (message, 9 + cipherSpecLength, sessionId, 0, sessionIdLength);
457 byte[] challenge = new byte[challengeLength];
458 Buffer.BlockCopy (message, 9 + cipherSpecLength + sessionIdLength, challenge, 0, challengeLength);
460 if (challengeLength < 16 || cipherSpecLength == 0 || (cipherSpecLength % 3) != 0)
462 throw new TlsException(AlertDescription.DecodeError);
465 // Updated the Session ID
466 if (sessionId.Length > 0)
468 this.context.SessionId = sessionId;
471 // Update the protocol version
472 this.Context.ChangeProtocol((short)protocol);
474 // Select the Cipher suite
475 this.ProcessCipherSpecV2Buffer(this.Context.SecurityProtocol, cipherSpecV2);
477 // Updated the Client Random
478 this.context.ClientRandom = new byte [32]; // Always 32
479 // 1. if challenge is bigger than 32 bytes only use the last 32 bytes
480 // 2. right justify (0) challenge in ClientRandom if less than 32
481 Buffer.BlockCopy (challenge, challenge.Length - length, this.context.ClientRandom, 32 - length, length);
484 this.context.LastHandshakeMsg = HandshakeType.ClientHello;
485 this.context.ProtocolNegotiated = true;
490 private byte[] ReadStandardRecordBuffer (Stream record)
492 short protocol = this.ReadShort(record);
493 short length = this.ReadShort(record);
495 // process further only if the whole record is available
496 // note: the first 5 bytes aren't part of the length
497 if (record.CanSeek && (length + 5 > record.Length))
503 int totalReceived = 0;
504 byte[] buffer = new byte[length];
505 while (totalReceived != length)
507 int justReceived = record.Read(buffer, totalReceived, buffer.Length - totalReceived);
509 //Make sure we get some data so we don't end up in an infinite loop here before shutdown.
510 if (0 == justReceived)
512 throw new TlsException(AlertDescription.CloseNotify, "Received 0 bytes from stream. It must be closed.");
515 totalReceived += justReceived;
518 // Check that the message has a valid protocol version
519 if (protocol != this.context.Protocol && this.context.ProtocolNegotiated)
521 throw new TlsException(
522 AlertDescription.ProtocolVersion, "Invalid protocol version on message received");
525 DebugHelper.WriteLine("Record data", buffer);
530 private short ReadShort(Stream record)
532 byte[] b = new byte[2];
533 record.Read(b, 0, b.Length);
535 short val = BitConverter.ToInt16(b, 0);
537 return System.Net.IPAddress.HostToNetworkOrder(val);
540 private void ProcessAlert(AlertLevel alertLevel, AlertDescription alertDesc)
544 case AlertLevel.Fatal:
545 throw new TlsException(alertLevel, alertDesc);
547 case AlertLevel.Warning:
551 case AlertDescription.CloseNotify:
552 this.context.ConnectionEnd = true;
561 #region Send Alert Methods
563 public void SendAlert(AlertDescription description)
565 this.SendAlert(new Alert(description));
568 public void SendAlert(
570 AlertDescription description)
572 this.SendAlert(new Alert(level, description));
575 public void SendAlert(Alert alert)
577 DebugHelper.WriteLine(">>>> Write Alert ({0}|{1})", alert.Description, alert.Message);
582 new byte[]{(byte)alert.Level, (byte)alert.Description});
584 if (alert.IsCloseNotify)
586 this.context.ConnectionEnd = true;
592 #region Send Record Methods
594 public void SendChangeCipherSpec()
596 DebugHelper.WriteLine(">>>> Write Change Cipher Spec");
598 // Send Change Cipher Spec message as a plain message
599 this.context.IsActual = false;
601 // Send Change Cipher Spec message
602 this.SendRecord(ContentType.ChangeCipherSpec, new byte[] {1});
604 // Reset sequence numbers
605 this.context.WriteSequenceNumber = 0;
607 // Make the pending state to be the current state
608 this.context.IsActual = true;
610 // Send Finished message
611 this.SendRecord(HandshakeType.Finished);
614 public IAsyncResult BeginSendRecord(HandshakeType handshakeType, AsyncCallback callback, object state)
616 HandshakeMessage msg = this.GetMessage(handshakeType);
620 DebugHelper.WriteLine(">>>> Write handshake record ({0}|{1})", context.Protocol, msg.ContentType);
622 SendRecordAsyncResult internalResult = new SendRecordAsyncResult(callback, state, msg);
624 this.BeginSendRecord(msg.ContentType, msg.EncodeMessage(), new AsyncCallback(InternalSendRecordCallback), internalResult);
626 return internalResult;
629 private void InternalSendRecordCallback(IAsyncResult ar)
631 SendRecordAsyncResult internalResult = ar.AsyncState as SendRecordAsyncResult;
635 this.EndSendRecord(ar);
638 internalResult.Message.Update();
640 // Reset message contents
641 internalResult.Message.Reset();
643 internalResult.SetComplete();
647 internalResult.SetComplete(ex);
651 public IAsyncResult BeginSendRecord(ContentType contentType, byte[] recordData, AsyncCallback callback, object state)
653 if (this.context.ConnectionEnd)
655 throw new TlsException(
656 AlertDescription.InternalError,
657 "The session is finished and it's no longer valid.");
660 byte[] record = this.EncodeRecord(contentType, recordData);
662 return this.innerStream.BeginWrite(record, 0, record.Length, callback, state);
665 public void EndSendRecord(IAsyncResult asyncResult)
667 if (asyncResult is SendRecordAsyncResult)
669 SendRecordAsyncResult internalResult = asyncResult as SendRecordAsyncResult;
670 internalResult.AsyncWaitHandle.WaitOne();
671 if (internalResult.CompletedWithError)
672 throw internalResult.AsyncException;
676 this.innerStream.EndWrite(asyncResult);
680 public void SendRecord(ContentType contentType, byte[] recordData)
682 IAsyncResult ar = this.BeginSendRecord(contentType, recordData, null, null);
684 this.EndSendRecord(ar);
687 public byte[] EncodeRecord(ContentType contentType, byte[] recordData)
689 return this.EncodeRecord(
696 public byte[] EncodeRecord(
697 ContentType contentType,
702 if (this.context.ConnectionEnd)
704 throw new TlsException(
705 AlertDescription.InternalError,
706 "The session is finished and it's no longer valid.");
709 TlsStream record = new TlsStream();
711 int position = offset;
713 while (position < ( offset + count ))
715 short fragmentLength = 0;
718 if ((count - position) > Context.MAX_FRAGMENT_SIZE)
720 fragmentLength = Context.MAX_FRAGMENT_SIZE;
724 fragmentLength = (short)(count - position);
727 // Fill the fragment data
728 fragment = new byte[fragmentLength];
729 Buffer.BlockCopy(recordData, position, fragment, 0, fragmentLength);
731 if (this.context.IsActual)
734 fragment = this.encryptRecordFragment(contentType, fragment);
738 record.Write((byte)contentType);
739 record.Write(this.context.Protocol);
740 record.Write((short)fragment.Length);
741 record.Write(fragment);
743 DebugHelper.WriteLine("Record data", fragment);
745 // Update buffer position
746 position += fragmentLength;
749 return record.ToArray();
754 #region Cryptography Methods
756 private byte[] encryptRecordFragment(
757 ContentType contentType,
762 // Calculate message MAC
763 if (this.Context is ClientContext)
765 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, fragment);
769 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, fragment);
772 DebugHelper.WriteLine(">>>> Record MAC", mac);
774 // Encrypt the message
775 byte[] ecr = this.context.Cipher.EncryptRecord(fragment, mac);
777 // Set new Client Cipher IV
778 if (this.context.Cipher.CipherMode == CipherMode.CBC)
780 byte[] iv = new byte[this.context.Cipher.IvSize];
781 Buffer.BlockCopy(ecr, ecr.Length - iv.Length, iv, 0, iv.Length);
783 this.context.Cipher.UpdateClientCipherIV(iv);
786 // Update sequence number
787 this.context.WriteSequenceNumber++;
792 private byte[] decryptRecordFragment(
793 ContentType contentType,
796 byte[] dcrFragment = null;
797 byte[] dcrMAC = null;
798 bool badRecordMac = false;
802 this.context.Cipher.DecryptRecord(fragment, ref dcrFragment, ref dcrMAC);
806 if (this.context is ServerContext)
808 this.Context.RecordProtocol.SendAlert(AlertDescription.DecryptionFailed);
814 // Generate record MAC
817 if (this.Context is ClientContext)
819 mac = this.context.Cipher.ComputeServerRecordMAC(contentType, dcrFragment);
823 mac = this.context.Cipher.ComputeClientRecordMAC(contentType, dcrFragment);
826 DebugHelper.WriteLine(">>>> Record MAC", mac);
829 if (mac.Length != dcrMAC.Length)
835 for (int i = 0; i < mac.Length; i++)
837 if (mac[i] != dcrMAC[i])
847 throw new TlsException(AlertDescription.BadRecordMAC, "Bad record MAC");
850 // Update sequence number
851 this.context.ReadSequenceNumber++;
858 #region CipherSpecV2 processing
860 private void ProcessCipherSpecV2Buffer(SecurityProtocolType protocol, byte[] buffer)
862 TlsStream codes = new TlsStream(buffer);
864 string prefix = (protocol == SecurityProtocolType.Ssl3) ? "SSL_" : "TLS_";
866 while (codes.Position < codes.Length)
868 byte check = codes.ReadByte();
872 // SSL/TLS cipher spec
873 short code = codes.ReadInt16();
874 int index = this.Context.SupportedCiphers.IndexOf(code);
877 this.Context.Cipher = this.Context.SupportedCiphers[index];
883 byte[] tmp = new byte[2];
884 codes.Read(tmp, 0, tmp.Length);
886 int tmpCode = ((check & 0xff) << 16) | ((tmp[0] & 0xff) << 8) | (tmp[1] & 0xff);
887 CipherSuite cipher = this.MapV2CipherCode(prefix, tmpCode);
891 this.Context.Cipher = cipher;
897 if (this.Context.Cipher == null)
899 throw new TlsException(AlertDescription.InsuficientSecurity, "Insuficient Security");
903 private CipherSuite MapV2CipherCode(string prefix, int code)
910 // TLS_RC4_128_WITH_MD5
911 return this.Context.SupportedCiphers[prefix + "RSA_WITH_RC4_128_MD5"];
914 // TLS_RC4_128_EXPORT40_WITH_MD5
915 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC4_40_MD5"];
918 // TLS_RC2_CBC_128_CBC_WITH_MD5
919 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
922 // TLS_RC2_CBC_128_CBC_EXPORT40_WITH_MD5
923 return this.Context.SupportedCiphers[prefix + "RSA_EXPORT_WITH_RC2_CBC_40_MD5"];
926 // TLS_IDEA_128_CBC_WITH_MD5
930 // TLS_DES_64_CBC_WITH_MD5
934 // TLS_DES_192_EDE3_CBC_WITH_MD5