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;
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 internal Stream innerStream;
43 internal MemoryStream inputBuffer;
44 internal Context context;
45 internal RecordProtocol protocol;
46 internal bool ownsStream;
47 private volatile bool disposed;
48 private bool checkCertRevocationStatus;
49 private object negotiate;
52 private ManualResetEvent negotiationComplete;
59 protected SslStreamBase(
65 throw new ArgumentNullException("stream is null.");
67 if (!stream.CanRead || !stream.CanWrite)
69 throw new ArgumentNullException("stream is not both readable and writable.");
72 this.inputBuffer = new MemoryStream();
73 this.innerStream = stream;
74 this.ownsStream = ownsStream;
75 this.negotiate = new object();
76 this.read = new object();
77 this.write = new object();
78 this.negotiationComplete = new ManualResetEvent(false);
84 private void AsyncHandshakeCallback(IAsyncResult asyncResult)
86 InternalAsyncResult internalResult = asyncResult.AsyncState as InternalAsyncResult;
92 this.OnNegotiateHandshakeCallback(asyncResult);
94 catch (TlsException ex)
96 this.protocol.SendAlert(ex.Alert);
98 throw new IOException("The authentication or decryption has failed.", ex);
102 this.protocol.SendAlert(AlertDescription.InternalError);
104 throw new IOException("The authentication or decryption has failed.", ex);
107 if (internalResult.ProceedAfterHandshake)
109 //kick off the read or write process (whichever called us) after the handshake is complete
110 if (internalResult.FromWrite)
112 InternalBeginWrite(internalResult);
116 InternalBeginRead(internalResult);
118 negotiationComplete.Set();
122 negotiationComplete.Set();
123 internalResult.SetComplete();
129 negotiationComplete.Set();
130 internalResult.SetComplete(ex);
134 internal bool MightNeedHandshake
138 if (this.context.HandshakeState == HandshakeState.Finished)
144 lock (this.negotiate)
146 return (this.context.HandshakeState != HandshakeState.Finished);
152 internal void NegotiateHandshake()
154 if (this.MightNeedHandshake)
156 InternalAsyncResult ar = new InternalAsyncResult(null, null, null, 0, 0, false, false);
158 //if something already started negotiation, wait for it.
159 //otherwise end it ourselves.
160 if (!BeginNegotiateHandshake(ar))
162 this.negotiationComplete.WaitOne();
166 this.EndNegotiateHandshake(ar);
173 #region Abstracts/Virtuals
175 internal abstract IAsyncResult OnBeginNegotiateHandshake(AsyncCallback callback, object state);
176 internal abstract void OnNegotiateHandshakeCallback(IAsyncResult asyncResult);
178 internal abstract X509Certificate OnLocalCertificateSelection(X509CertificateCollection clientCertificates,
179 X509Certificate serverCertificate,
181 X509CertificateCollection serverRequestedCertificates);
183 internal abstract bool OnRemoteCertificateValidation(X509Certificate certificate, int[] errors);
185 internal abstract AsymmetricAlgorithm OnLocalPrivateKeySelection(X509Certificate certificate, string targetHost);
189 #region Event Methods
191 internal X509Certificate RaiseLocalCertificateSelection(X509CertificateCollection certificates,
192 X509Certificate remoteCertificate,
194 X509CertificateCollection requestedCertificates)
196 return OnLocalCertificateSelection(certificates, remoteCertificate, targetHost, requestedCertificates);
199 internal bool RaiseRemoteCertificateValidation(X509Certificate certificate, int[] errors)
201 return OnRemoteCertificateValidation(certificate, errors);
204 internal AsymmetricAlgorithm RaiseLocalPrivateKeySelection(
205 X509Certificate certificate,
208 return OnLocalPrivateKeySelection(certificate, targetHost);
212 #region Security Properties
214 public bool CheckCertRevocationStatus
216 get { return this.checkCertRevocationStatus; }
217 set { this.checkCertRevocationStatus = value; }
220 public CipherAlgorithmType CipherAlgorithm
224 if (this.context.HandshakeState == HandshakeState.Finished)
226 return this.context.Cipher.CipherAlgorithmType;
229 return CipherAlgorithmType.None;
233 public int CipherStrength
237 if (this.context.HandshakeState == HandshakeState.Finished)
239 return this.context.Cipher.EffectiveKeyBits;
246 public HashAlgorithmType HashAlgorithm
250 if (this.context.HandshakeState == HandshakeState.Finished)
252 return this.context.Cipher.HashAlgorithmType;
255 return HashAlgorithmType.None;
259 public int HashStrength
263 if (this.context.HandshakeState == HandshakeState.Finished)
265 return this.context.Cipher.HashSize * 8;
272 public int KeyExchangeStrength
276 if (this.context.HandshakeState == HandshakeState.Finished)
278 return this.context.ServerSettings.Certificates[0].RSA.KeySize;
285 public ExchangeAlgorithmType KeyExchangeAlgorithm
289 if (this.context.HandshakeState == HandshakeState.Finished)
291 return this.context.Cipher.ExchangeAlgorithmType;
294 return ExchangeAlgorithmType.None;
298 public SecurityProtocolType SecurityProtocol
302 if (this.context.HandshakeState == HandshakeState.Finished)
304 return this.context.SecurityProtocol;
311 public X509Certificate ServerCertificate
315 if (this.context.HandshakeState == HandshakeState.Finished)
317 if (this.context.ServerSettings.Certificates != null &&
318 this.context.ServerSettings.Certificates.Count > 0)
320 return new X509Certificate(this.context.ServerSettings.Certificates[0].RawData);
328 // this is used by Mono's certmgr tool to download certificates
329 internal Mono.Security.X509.X509CertificateCollection ServerCertificates
331 get { return context.ServerSettings.Certificates; }
336 #region Internal Async Result/State Class
338 private class InternalAsyncResult : IAsyncResult
340 private object locker = new object ();
341 private AsyncCallback _userCallback;
342 private object _userState;
343 private Exception _asyncException;
344 private ManualResetEvent handle;
345 private bool completed;
346 private int _bytesRead;
347 private bool _fromWrite;
348 private bool _proceedAfterHandshake;
350 private byte[] _buffer;
354 public InternalAsyncResult(AsyncCallback userCallback, object userState, byte[] buffer, int offset, int count, bool fromWrite, bool proceedAfterHandshake)
356 _userCallback = userCallback;
357 _userState = userState;
361 _fromWrite = fromWrite;
362 _proceedAfterHandshake = proceedAfterHandshake;
365 public bool ProceedAfterHandshake
367 get { return _proceedAfterHandshake; }
370 public bool FromWrite
372 get { return _fromWrite; }
377 get { return _buffer; }
382 get { return _offset; }
387 get { return _count; }
392 get { return _bytesRead; }
395 public object AsyncState
397 get { return _userState; }
400 public Exception AsyncException
402 get { return _asyncException; }
405 public bool CompletedWithError
408 if (IsCompleted == false)
410 return null != _asyncException;
414 public WaitHandle AsyncWaitHandle
419 handle = new ManualResetEvent (completed);
425 public bool CompletedSynchronously
427 get { return false; }
430 public bool IsCompleted
438 private void SetComplete(Exception ex, int bytesRead)
447 _asyncException = ex;
448 _bytesRead = bytesRead;
450 if (_userCallback != null)
451 _userCallback.BeginInvoke (this, null, null);
454 public void SetComplete(Exception ex)
459 public void SetComplete(int bytesRead)
461 SetComplete(null, bytesRead);
464 public void SetComplete()
466 SetComplete(null, 0);
471 #region Stream Overrides and Async Stream Operations
473 private bool BeginNegotiateHandshake(InternalAsyncResult asyncResult)
477 lock (this.negotiate)
479 if (this.context.HandshakeState == HandshakeState.None)
481 this.OnBeginNegotiateHandshake(new AsyncCallback(AsyncHandshakeCallback), asyncResult);
491 catch (TlsException ex)
493 this.negotiationComplete.Set();
494 this.protocol.SendAlert(ex.Alert);
496 throw new IOException("The authentication or decryption has failed.", ex);
500 this.negotiationComplete.Set();
501 this.protocol.SendAlert(AlertDescription.InternalError);
503 throw new IOException("The authentication or decryption has failed.", ex);
507 private void EndNegotiateHandshake(InternalAsyncResult asyncResult)
509 if (asyncResult.IsCompleted == false)
510 asyncResult.AsyncWaitHandle.WaitOne();
512 if (asyncResult.CompletedWithError)
514 throw asyncResult.AsyncException;
518 public override IAsyncResult BeginRead(
522 AsyncCallback callback,
525 this.checkDisposed();
529 throw new ArgumentNullException("buffer is a null reference.");
533 throw new ArgumentOutOfRangeException("offset is less than 0.");
535 if (offset > buffer.Length)
537 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
541 throw new ArgumentOutOfRangeException("count is less than 0.");
543 if (count > (buffer.Length - offset))
545 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
548 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, false, true);
550 if (this.MightNeedHandshake)
552 if (! BeginNegotiateHandshake(asyncResult))
554 //we made it down here so the handshake was not started.
555 //another thread must have started it in the mean time.
556 //wait for it to complete and then perform our original operation
557 this.negotiationComplete.WaitOne();
559 InternalBeginRead(asyncResult);
564 InternalBeginRead(asyncResult);
570 private void InternalBeginRead(InternalAsyncResult asyncResult)
578 // If actual buffer is fully read, reset it
579 bool shouldReset = this.inputBuffer.Position == this.inputBuffer.Length && this.inputBuffer.Length > 0;
581 // If the buffer isn't fully read, but does have data, we need to immediately
582 // read the info from the buffer and let the user know that they have more data.
583 bool shouldReadImmediately = (this.inputBuffer.Length > 0) && (asyncResult.Count > 0);
589 else if (shouldReadImmediately)
591 preReadSize = this.inputBuffer.Read(asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
595 // This is explicitly done outside the synclock to avoid
596 // any potential deadlocks in the delegate call.
599 asyncResult.SetComplete(preReadSize);
601 else if (!this.context.ConnectionEnd)
603 // bigger than max record length for SSL/TLS
604 byte[] recbuf = new byte[16384];
606 // this will read data from the network until we have (at least) one
607 // record to send back to the caller
608 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
609 new AsyncCallback(InternalReadCallback), new object[] { recbuf, asyncResult });
613 // We're done with the connection so we need to let the caller know with 0 bytes read
614 asyncResult.SetComplete(0);
617 catch (TlsException ex)
619 this.protocol.SendAlert(ex.Alert);
621 throw new IOException("The authentication or decryption has failed.", ex);
625 throw new IOException("IO exception during read.", ex);
630 private MemoryStream recordStream = new MemoryStream();
632 // read encrypted data until we have enough to decrypt (at least) one
633 // record and return are the records (may be more than one) we have
634 private void InternalReadCallback(IAsyncResult result)
639 object[] state = (object[])result.AsyncState;
640 byte[] recbuf = (byte[])state[0];
641 InternalAsyncResult internalResult = (InternalAsyncResult)state[1];
645 int n = innerStream.EndRead(result);
648 // Add the just received data to the waiting data
649 recordStream.Write(recbuf, 0, n);
653 // 0 length data means this read operation is done (lost connection in the case of a network stream).
654 internalResult.SetComplete(0);
658 bool dataToReturn = false;
659 long pos = recordStream.Position;
661 recordStream.Position = 0;
662 byte[] record = null;
664 // don't try to decode record unless we have at least 5 bytes
665 // i.e. type (1), protocol (2) and length (2)
666 if (recordStream.Length >= 5)
668 record = this.protocol.ReceiveRecord(recordStream);
671 // a record of 0 length is valid (and there may be more record after it)
672 while (record != null)
674 // we probably received more stuff after the record, and we must keep it!
675 long remainder = recordStream.Length - recordStream.Position;
676 byte[] outofrecord = null;
679 outofrecord = new byte[remainder];
680 recordStream.Read(outofrecord, 0, outofrecord.Length);
685 long position = this.inputBuffer.Position;
687 if (record.Length > 0)
689 // Write new data to the inputBuffer
690 this.inputBuffer.Seek(0, SeekOrigin.End);
691 this.inputBuffer.Write(record, 0, record.Length);
693 // Restore buffer position
694 this.inputBuffer.Seek(position, SeekOrigin.Begin);
699 recordStream.SetLength(0);
704 recordStream.Write(outofrecord, 0, outofrecord.Length);
705 // type (1), protocol (2) and length (2)
706 if (recordStream.Length >= 5)
708 // try to see if another record is available
709 recordStream.Position = 0;
710 record = this.protocol.ReceiveRecord(recordStream);
712 pos = recordStream.Length;
721 if (!dataToReturn && (n > 0))
723 // there is no record to return to caller and (possibly) more data waiting
724 // so continue reading from network (and appending to stream)
725 recordStream.Position = recordStream.Length;
726 this.innerStream.BeginRead(recbuf, 0, recbuf.Length,
727 new AsyncCallback(InternalReadCallback), state);
731 // we have record(s) to return -or- no more available to read from network
732 // reset position for further reading
733 recordStream.Position = pos;
738 bytesRead = this.inputBuffer.Read(internalResult.Buffer, internalResult.Offset, internalResult.Count);
741 internalResult.SetComplete(bytesRead);
746 internalResult.SetComplete(ex);
751 private void InternalBeginWrite(InternalAsyncResult asyncResult)
755 // Send the buffer as a TLS record
759 byte[] record = this.protocol.EncodeRecord(
760 ContentType.ApplicationData, asyncResult.Buffer, asyncResult.Offset, asyncResult.Count);
762 this.innerStream.BeginWrite(
763 record, 0, record.Length, new AsyncCallback(InternalWriteCallback), asyncResult);
766 catch (TlsException ex)
768 this.protocol.SendAlert(ex.Alert);
771 throw new IOException("The authentication or decryption has failed.", ex);
775 throw new IOException("IO exception during Write.", ex);
779 private void InternalWriteCallback(IAsyncResult ar)
784 InternalAsyncResult internalResult = (InternalAsyncResult)ar.AsyncState;
788 this.innerStream.EndWrite(ar);
789 internalResult.SetComplete();
793 internalResult.SetComplete(ex);
797 public override IAsyncResult BeginWrite(
801 AsyncCallback callback,
804 this.checkDisposed();
808 throw new ArgumentNullException("buffer is a null reference.");
812 throw new ArgumentOutOfRangeException("offset is less than 0.");
814 if (offset > buffer.Length)
816 throw new ArgumentOutOfRangeException("offset is greater than the length of buffer.");
820 throw new ArgumentOutOfRangeException("count is less than 0.");
822 if (count > (buffer.Length - offset))
824 throw new ArgumentOutOfRangeException("count is less than the length of buffer minus the value of the offset parameter.");
828 InternalAsyncResult asyncResult = new InternalAsyncResult(callback, state, buffer, offset, count, true, true);
830 if (this.MightNeedHandshake)
832 if (! BeginNegotiateHandshake(asyncResult))
834 //we made it down here so the handshake was not started.
835 //another thread must have started it in the mean time.
836 //wait for it to complete and then perform our original operation
837 this.negotiationComplete.WaitOne();
839 InternalBeginWrite(asyncResult);
844 InternalBeginWrite(asyncResult);
850 public override int EndRead(IAsyncResult asyncResult)
852 this.checkDisposed();
854 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
855 if (internalResult == null)
857 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginRead.");
860 // Always wait until the read is complete
861 if (asyncResult.IsCompleted == false)
862 asyncResult.AsyncWaitHandle.WaitOne();
864 if (internalResult.CompletedWithError)
866 throw internalResult.AsyncException;
869 return internalResult.BytesRead;
872 public override void EndWrite(IAsyncResult asyncResult)
874 this.checkDisposed();
876 InternalAsyncResult internalResult = asyncResult as InternalAsyncResult;
877 if (internalResult == null)
879 throw new ArgumentNullException("asyncResult is null or was not obtained by calling BeginWrite.");
883 if (asyncResult.IsCompleted == false)
884 internalResult.AsyncWaitHandle.WaitOne();
886 if (internalResult.CompletedWithError)
888 throw internalResult.AsyncException;
892 public override void Close()
894 ((IDisposable)this).Dispose();
897 public override void Flush()
899 this.checkDisposed();
901 this.innerStream.Flush();
904 public int Read(byte[] buffer)
906 return this.Read(buffer, 0, buffer.Length);
909 public override int Read(byte[] buffer, int offset, int count)
911 IAsyncResult res = this.BeginRead(buffer, offset, count, null, null);
913 return this.EndRead(res);
916 public override long Seek(long offset, SeekOrigin origin)
918 throw new NotSupportedException();
921 public override void SetLength(long value)
923 throw new NotSupportedException();
926 public void Write(byte[] buffer)
928 this.Write(buffer, 0, buffer.Length);
931 public override void Write(byte[] buffer, int offset, int count)
933 IAsyncResult res = this.BeginWrite(buffer, offset, count, null, null);
938 public override bool CanRead
940 get { return this.innerStream.CanRead; }
943 public override bool CanSeek
945 get { return false; }
948 public override bool CanWrite
950 get { return this.innerStream.CanWrite; }
953 public override long Length
955 get { throw new NotSupportedException(); }
958 public override long Position
962 throw new NotSupportedException();
966 throw new NotSupportedException();
971 #region IDisposable Members and Finalizer
978 public void Dispose()
981 GC.SuppressFinalize(this);
984 protected virtual void Dispose(bool disposing)
990 if (this.innerStream != null)
992 if (this.context.HandshakeState == HandshakeState.Finished &&
993 !this.context.ConnectionEnd)
995 // Write close notify
996 this.protocol.SendAlert(AlertDescription.CloseNotify);
1001 // Close inner stream
1002 this.innerStream.Close();
1005 this.ownsStream = false;
1006 this.innerStream = null;
1009 this.disposed = true;
1015 #region Misc Methods
1017 private void resetBuffer()
1019 this.inputBuffer.SetLength(0);
1020 this.inputBuffer.Position = 0;
1023 internal void checkDisposed()
1027 throw new ObjectDisposedException("The Stream is closed.");