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;
29 using System.Net.Sockets;
30 using System.Security.Cryptography;
31 using System.Security.Cryptography.X509Certificates;
32 using System.Threading;
34 namespace Mono.Security.Protocol.Tls
36 public abstract class SslStreamBase: Stream, IDisposable
38 private delegate void AsyncHandshakeDelegate(InternalAsyncResult asyncResult, bool fromWrite);
42 private const int WaitTimeOut = 5 * 60 * 1000;
44 internal Stream innerStream;
45 internal MemoryStream inputBuffer;
46 internal Context context;
47 internal RecordProtocol protocol;
48 internal bool ownsStream;
49 private volatile bool disposed;
50 private bool checkCertRevocationStatus;
51 private object negotiate;
54 private ManualResetEvent negotiationComplete;
61 protected SslStreamBase(
67 throw new ArgumentNullException("stream is null.");
69 if (!stream.CanRead || !stream.CanWrite)
71 throw new ArgumentNullException("stream is not both readable and writable.");
74 this.inputBuffer = new MemoryStream();
75 this.innerStream = stream;
76 this.ownsStream = ownsStream;
77 this.negotiate = new object();
78 this.read = new object();
79 this.write = new object();
80 this.negotiationComplete = new ManualResetEvent(false);
86 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
88 InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
94 this.OnNegotiateHandshakeCallback(asyncResult);
96 catch (TlsException ex)
98 this.protocol.SendAlert(ex.Alert);
100 throw new IOException("The authentication or decryption has failed.", ex);
104 this.protocol.SendAlert(AlertDescription.InternalError);
106 throw new IOException("The authentication or decryption has failed.", ex);
109 if (internalResult.ProceedAfterHandshake)
111 //kick off the read or write process (whichever called us) after the handshake is complete
112 if (internalResult.FromWrite)
114 InternalBeginWrite(internalResult);
118 InternalBeginRead(internalResult);
120 negotiationComplete.Set();
124 negotiationComplete.Set();
125 internalResult.SetComplete();
131 negotiationComplete.Set();
132 internalResult.SetComplete(ex);
136 internal bool MightNeedHandshake
140 if (this.context.HandshakeState == HandshakeState.Finished)
146 lock (this.negotiate)
148 return (this.context.HandshakeState != HandshakeState.Finished);
154 internal void NegotiateHandshake()
156 if (this.MightNeedHandshake)
158 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
160 //if something already started negotiation, wait for it.
161 //otherwise end it ourselves.
162 if (!BeginNegotiateHandshake(ar))
164 this.negotiationComplete.WaitOne();
168 this.EndNegotiateHandshake(ar);
175 #region Abstracts/Virtuals
177 internal abstract IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state);
178 internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
180 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
181 X509Certificate serverCertificate,
183 X509CertificateCollection serverRequestedCertificates);
185 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
187 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
191 #region Event Methods
193 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
194 X509Certificate remoteCertificate,
196 X509CertificateCollection requestedCertificates)
198 return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
201 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
203 return OnRemoteCertificateValidation(certificate, errors);
206 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
207 X509Certificate certificate,
210 return OnLocalPrivateKeySelection(certificate, targetHost);
214 #region Security Properties
216 public bool CheckCertRevocationStatus
218 get { return this.checkCertRevocationStatus; }
219 set { this.checkCertRevocationStatus = value; }
222 public CipherAlgorithmType CipherAlgorithm
226 if (this.context.HandshakeState == HandshakeState.Finished)
228 return this.context.Current.Cipher.CipherAlgorithmType;
231 return CipherAlgorithmType.None;
235 public int CipherStrength
239 if (this.context.HandshakeState == HandshakeState.Finished)
241 return this.context.Current.Cipher.EffectiveKeyBits;
248 public HashAlgorithmType HashAlgorithm
252 if (this.context.HandshakeState == HandshakeState.Finished)
254 return this.context.Current.Cipher.HashAlgorithmType;
257 return HashAlgorithmType.None;
261 public int HashStrength
265 if (this.context.HandshakeState == HandshakeState.Finished)
267 return this.context.Current.Cipher.HashSize * 8;
274 public int KeyExchangeStrength
278 if (this.context.HandshakeState == HandshakeState.Finished)
280 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
287 public ExchangeAlgorithmType KeyExchangeAlgorithm
291 if (this.context.HandshakeState == HandshakeState.Finished)
293 return this.context.Current.Cipher.ExchangeAlgorithmType;
296 return ExchangeAlgorithmType.None;
300 public SecurityProtocolType SecurityProtocol
304 if (this.context.HandshakeState == HandshakeState.Finished)
306 return this.context.SecurityProtocol;
313 public X509Certificate ServerCertificate
317 if (this.context.HandshakeState == HandshakeState.Finished)
319 if (this.context.ServerSettings.Certificates != null &&
320 this.context.ServerSettings.Certificates.Count > 0)
322 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
330 // this is used by Mono's certmgr tool to download certificates
331 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
333 get { return context.ServerSettings.Certificates; }
338 #region Internal Async Result/State Class
340 private class InternalAsyncResult : IAsyncResult
342 private object locker = new object ();
343 private AsyncCallback _userCallback;
344 private object _userState;
345 private Exception _asyncException;
346 private ManualResetEvent handle;
347 private bool completed;
348 private int _bytesRead;
349 private bool _fromWrite;
350 private bool _proceedAfterHandshake;
352 private byte[] _buffer;
356 public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
358 _userCallback = userCallback;
359 _userState = userState;
363 _fromWrite = fromWrite;
364 _proceedAfterHandshake = proceedAfterHandshake;
367 public bool ProceedAfterHandshake
369 get { return _proceedAfterHandshake; }
372 public bool FromWrite
374 get { return _fromWrite; }
379 get { return _buffer; }
384 get { return _offset; }
389 get { return _count; }
394 get { return _bytesRead; }
397 public object AsyncState
399 get { return _userState; }
402 public Exception AsyncException
404 get { return _asyncException; }
407 public bool CompletedWithError
410 if (IsCompleted == false)
412 return null != _asyncException;
416 public WaitHandle AsyncWaitHandle
421 handle = new ManualResetEvent (completed);
427 public bool CompletedSynchronously
429 get { return false; }
432 public bool IsCompleted
440 private void SetComplete(Exception ex, int bytesRead)
447 _asyncException = ex;
448 _bytesRead = bytesRead;
452 if (_userCallback != null)
453 _userCallback.BeginInvoke (this, null, null);
456 public void SetComplete(Exception ex)
461 public void SetComplete(int bytesRead)
463 SetComplete(null, bytesRead);
466 public void SetComplete()
468 SetComplete(null, 0);
473 #region Stream Overrides and Async Stream Operations
475 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
479 lock (this.negotiate)
481 if (this.context.HandshakeState == HandshakeState.None)
483 this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
493 catch (TlsException ex)
495 this.negotiationComplete.Set();
496 this.protocol.SendAlert(ex.Alert);
498 throw new IOException("The authentication or decryption has failed.", ex);
502 this.negotiationComplete.Set();
503 this.protocol.SendAlert(AlertDescription.InternalError);
505 throw new IOException("The authentication or decryption has failed.", ex);
509 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
511 if (asyncResult.IsCompleted == false)
512 asyncResult.AsyncWaitHandle.WaitOne();
514 if (asyncResult.CompletedWithError)
516 throw asyncResult.AsyncException;
520 public override IAsyncResult BeginRead(
524 AsyncCallback callback,
527 this.checkDisposed();
531 throw new ArgumentNullException("buffer is a null reference.");
535 throw new ArgumentOutOfRangeException("offset is less than 0.");
537 if (offset > buffer.Length)
539 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
543 throw new ArgumentOutOfRangeException("count is less than 0.");
545 if (count > (buffer.Length - offset))
547 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
550 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
552 if (this.MightNeedHandshake)
554 if (! BeginNegotiateHandshake(asyncResult))
556 //we made it down here so the handshake was not started.
557 //another thread must have started it in the mean time.
558 //wait for it to complete and then perform our original operation
559 this.negotiationComplete.WaitOne();
561 InternalBeginRead(asyncResult);
566 InternalBeginRead(asyncResult);
572 // bigger than max record length for SSL/TLS
573 private byte[] recbuf = new byte[16384];
575 private void InternalBeginRead(InternalAsyncResult asyncResult)
583 // If actual buffer is fully read, reset it
584 bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
586 // If the buffer isn't fully read, but does have data, we need to immediately
587 // read the info from the buffer and let the user know that they have more data.
588 bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
594 else if (shouldReadImmediately)
596 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
600 // This is explicitly done outside the synclock to avoid
601 // any potential deadlocks in the delegate call.
604 asyncResult.SetComplete(preReadSize);
606 else if (!this.context.ConnectionEnd)
608 // this will read data from the network until we have (at least) one
609 // record to send back to the caller
610 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
611 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
615 // We're done with the connection so we need to let the caller know with 0 bytes read
616 asyncResult.SetComplete(0);
619 catch (TlsException ex)
621 this.protocol.SendAlert(ex.Alert);
623 throw new IOException("The authentication or decryption has failed.", ex);
627 throw new IOException("IO exception during read.", ex);
632 private MemoryStream recordStream = new MemoryStream();
634 // read encrypted data until we have enough to decrypt (at least) one
635 // record and return are the records (may be more than one) we have
636 private void InternalReadCallback(IAsyncResult result)
641 object[] state = (object[])result.AsyncState;
642 byte[] recbuf = (byte[])state[0];
643 InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
647 int n = innerStream.EndRead(result);
650 // Add the just received data to the waiting data
651 recordStream.Write(recbuf, 0, n);
655 // 0 length data means this read operation is done (lost connection in the case of a network stream).
656 internalResult.SetComplete(0);
660 bool dataToReturn = false;
661 long pos = recordStream.Position;
663 recordStream.Position = 0;
664 byte[] record = null;
666 // don't try to decode record unless we have at least 5 bytes
667 // i.e. type (1), protocol (2) and length (2)
668 if (recordStream.Length >= 5)
670 record = this.protocol.ReceiveRecord(recordStream);
673 // a record of 0 length is valid (and there may be more record after it)
674 while (record != null)
676 // we probably received more stuff after the record, and we must keep it!
677 long remainder = recordStream.Length - recordStream.Position;
678 byte[] outofrecord = null;
681 outofrecord = new byte[remainder];
682 recordStream.Read(outofrecord, 0, outofrecord.Length);
687 long position = this.inputBuffer.Position;
689 if (record.Length > 0)
691 // Write new data to the inputBuffer
692 this.inputBuffer.Seek(0, SeekOrigin.End);
693 this.inputBuffer.Write(record, 0, record.Length);
695 // Restore buffer position
696 this.inputBuffer.Seek(position, SeekOrigin.Begin);
701 recordStream.SetLength(0);
706 recordStream.Write(outofrecord, 0, outofrecord.Length);
707 // type (1), protocol (2) and length (2)
708 if (recordStream.Length >= 5)
710 // try to see if another record is available
711 recordStream.Position = 0;
712 record = this.protocol.ReceiveRecord(recordStream);
714 pos = recordStream.Length;
723 if (!dataToReturn && (n > 0))
725 // there is no record to return to caller and (possibly) more data waiting
726 // so continue reading from network (and appending to stream)
727 recordStream.Position = recordStream.Length;
728 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
729 new AsyncCallback(InternalReadCallback), state);
733 // we have record(s) to return -or- no more available to read from network
734 // reset position for further reading
735 recordStream.Position = pos;
740 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
743 internalResult.SetComplete(bytesRead);
748 internalResult.SetComplete(ex);
753 private void InternalBeginWrite(InternalAsyncResult asyncResult)
757 // Send the buffer as a TLS record
761 byte[] record = this.protocol.EncodeRecord(
762 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
764 this.innerStream.BeginWrite(
765 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
768 catch (TlsException ex)
770 this.protocol.SendAlert(ex.Alert);
773 throw new IOException("The authentication or decryption has failed.", ex);
777 throw new IOException("IO exception during Write.", ex);
781 private void InternalWriteCallback(IAsyncResult ar)
786 InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
790 this.innerStream.EndWrite(ar);
791 internalResult.SetComplete();
795 internalResult.SetComplete(ex);
799 public override IAsyncResult BeginWrite(
803 AsyncCallback callback,
806 this.checkDisposed();
810 throw new ArgumentNullException("buffer is a null reference.");
814 throw new ArgumentOutOfRangeException("offset is less than 0.");
816 if (offset > buffer.Length)
818 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
822 throw new ArgumentOutOfRangeException("count is less than 0.");
824 if (count > (buffer.Length - offset))
826 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
830 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
832 if (this.MightNeedHandshake)
834 if (! BeginNegotiateHandshake(asyncResult))
836 //we made it down here so the handshake was not started.
837 //another thread must have started it in the mean time.
838 //wait for it to complete and then perform our original operation
839 this.negotiationComplete.WaitOne();
841 InternalBeginWrite(asyncResult);
846 InternalBeginWrite(asyncResult);
852 public override int EndRead(IAsyncResult asyncResult)
854 this.checkDisposed();
856 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
857 if (internalResult == null)
859 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
862 // Always wait until the read is complete
863 if (!asyncResult.IsCompleted)
865 if (!asyncResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
866 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndRead");
869 if (internalResult.CompletedWithError)
871 throw internalResult.AsyncException;
874 return internalResult.BytesRead;
877 public override void EndWrite(IAsyncResult asyncResult)
879 this.checkDisposed();
881 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
882 if (internalResult == null)
884 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
888 if (!asyncResult.IsCompleted)
890 if (!internalResult.AsyncWaitHandle.WaitOne (WaitTimeOut, false))
891 throw new TlsException (AlertDescription.InternalError, "Couldn't complete EndWrite");
894 if (internalResult.CompletedWithError)
896 throw internalResult.AsyncException;
900 public override void Close()
902 ((IDisposable)this).Dispose();
905 public override void Flush()
907 this.checkDisposed();
909 this.innerStream.Flush();
912 public int Read(byte[] buffer)
914 return this.Read(buffer, 0, buffer.Length);
917 public override int Read(byte[] buffer, int offset, int count)
919 this.checkDisposed ();
923 throw new ArgumentNullException ("buffer");
927 throw new ArgumentOutOfRangeException("offset is less than 0.");
929 if (offset > buffer.Length)
931 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
935 throw new ArgumentOutOfRangeException("count is less than 0.");
937 if (count > (buffer.Length - offset))
939 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
942 if (this.context.HandshakeState != HandshakeState.Finished)
944 this.NegotiateHandshake (); // Handshake negotiation
949 // do we already have some decrypted data ?
950 if (this.inputBuffer.Position > 0) {
951 // or maybe we used all the buffer before ?
952 if (this.inputBuffer.Position == this.inputBuffer.Length) {
953 this.inputBuffer.SetLength (0);
955 int n = this.inputBuffer.Read (buffer, offset, count);
961 bool needMoreData = false;
963 // we first try to process the read with the data we already have
964 if ((recordStream.Position == 0) || needMoreData) {
965 needMoreData = false;
966 // if we loop, then it either means we need more data
967 byte[] recbuf = new byte[16384];
968 int n = innerStream.Read (recbuf, 0, recbuf.Length);
970 // Add the new received data to the waiting data
971 if ((recordStream.Length > 0) && (recordStream.Position != recordStream.Length))
972 recordStream.Seek (0, SeekOrigin.End);
973 recordStream.Write (recbuf, 0, n);
975 // or that the read operation is done (lost connection in the case of a network stream).
980 bool dataToReturn = false;
982 recordStream.Position = 0;
983 byte[] record = null;
985 // don't try to decode record unless we have at least 5 bytes
986 // i.e. type (1), protocol (2) and length (2)
987 if (recordStream.Length >= 5) {
988 record = this.protocol.ReceiveRecord (recordStream);
989 needMoreData = (record == null);
992 // a record of 0 length is valid (and there may be more record after it)
993 while (record != null) {
994 // we probably received more stuff after the record, and we must keep it!
995 long remainder = recordStream.Length - recordStream.Position;
996 byte[] outofrecord = null;
998 outofrecord = new byte[remainder];
999 recordStream.Read (outofrecord, 0, outofrecord.Length);
1002 long position = this.inputBuffer.Position;
1004 if (record.Length > 0) {
1005 // Write new data to the inputBuffer
1006 this.inputBuffer.Seek (0, SeekOrigin.End);
1007 this.inputBuffer.Write (record, 0, record.Length);
1009 // Restore buffer position
1010 this.inputBuffer.Seek (position, SeekOrigin.Begin);
1011 dataToReturn = true;
1014 recordStream.SetLength (0);
1017 if (remainder > 0) {
1018 recordStream.Write (outofrecord, 0, outofrecord.Length);
1022 // we have record(s) to return -or- no more available to read from network
1023 // reset position for further reading
1024 return this.inputBuffer.Read (buffer, offset, count);
1029 catch (TlsException ex)
1031 throw new IOException("The authentication or decryption has failed.", ex);
1033 catch (Exception ex)
1035 throw new IOException("IO exception during read.", ex);
1040 public override long Seek(long offset, SeekOrigin origin)
1042 throw new NotSupportedException();
1045 public override void SetLength(long value)
1047 throw new NotSupportedException();
1050 public void Write(byte[] buffer)
1052 this.Write(buffer, 0, buffer.Length);
1055 public override void Write(byte[] buffer, int offset, int count)
1057 this.checkDisposed ();
1061 throw new ArgumentNullException ("buffer");
1065 throw new ArgumentOutOfRangeException("offset is less than 0.");
1067 if (offset > buffer.Length)
1069 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
1073 throw new ArgumentOutOfRangeException("count is less than 0.");
1075 if (count > (buffer.Length - offset))
1077 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
1080 if (this.context.HandshakeState != HandshakeState.Finished)
1082 this.NegotiateHandshake ();
1089 // Send the buffer as a TLS record
1090 byte[] record = this.protocol.EncodeRecord (ContentType.ApplicationData, buffer, offset, count);
1091 this.innerStream.Write (record, 0, record.Length);
1093 catch (TlsException ex)
1095 this.protocol.SendAlert(ex.Alert);
1097 throw new IOException("The authentication or decryption has failed.", ex);
1099 catch (Exception ex)
1101 throw new IOException("IO exception during Write.", ex);
1106 public override bool CanRead
1108 get { return this.innerStream.CanRead; }
1111 public override bool CanSeek
1113 get { return false; }
1116 public override bool CanWrite
1118 get { return this.innerStream.CanWrite; }
1121 public override long Length
1123 get { throw new NotSupportedException(); }
1126 public override long Position
1130 throw new NotSupportedException();
1134 throw new NotSupportedException();
1139 #region IDisposable Members and Finalizer
1143 this.Dispose(false);
1146 public void Dispose()
1149 GC.SuppressFinalize(this);
1152 protected virtual void Dispose(bool disposing)
1158 if (this.innerStream != null)
1160 if (this.context.HandshakeState == HandshakeState.Finished &&
1161 !this.context.ConnectionEnd)
1163 // Write close notify
1164 this.protocol.SendAlert(AlertDescription.CloseNotify);
1167 if (this.ownsStream)
1169 // Close inner stream
1170 this.innerStream.Close();
1173 this.ownsStream = false;
1174 this.innerStream = null;
1177 this.disposed = true;
1183 #region Misc Methods
1185 private void resetBuffer()
1187 this.inputBuffer.SetLength(0);
1188 this.inputBuffer.Position = 0;
1191 internal void checkDisposed()
1195 throw new ObjectDisposedException("The Stream is closed.");