diff options
19 files changed, 667 insertions, 527 deletions
diff --git a/mcs/class/Mono.Security/Mono.Security.Interface/IMonoSslStream.cs b/mcs/class/Mono.Security/Mono.Security.Interface/IMonoSslStream.cs index 4982e804477..00556863db9 100644 --- a/mcs/class/Mono.Security/Mono.Security.Interface/IMonoSslStream.cs +++ b/mcs/class/Mono.Security/Mono.Security.Interface/IMonoSslStream.cs @@ -86,6 +86,8 @@ namespace Mono.Security.Interface void EndWrite (IAsyncResult asyncResult); + Task ShutdownAsync (); + TransportContext TransportContext { get; } diff --git a/mcs/class/System/Mono.AppleTls/AppleTlsContext.cs b/mcs/class/System/Mono.AppleTls/AppleTlsContext.cs index 6a57babab4e..4fa1730df2a 100644 --- a/mcs/class/System/Mono.AppleTls/AppleTlsContext.cs +++ b/mcs/class/System/Mono.AppleTls/AppleTlsContext.cs @@ -848,12 +848,12 @@ namespace Mono.AppleTls [DllImport (SecurityLibrary)] extern static /* OSStatus */ SslStatus SSLClose (/* SSLContextRef */ IntPtr context); - public override void Close () + public override void Shutdown () { if (Interlocked.Exchange (ref pendingIO, 1) == 1) throw new InvalidOperationException (); - Debug ("Close"); + Debug ("Shutdown"); lastException = null; @@ -862,7 +862,7 @@ namespace Mono.AppleTls return; var status = SSLClose (Handle); - Debug ("Close done: {0}", status); + Debug ("Shutdown done: {0}", status); CheckStatusAndThrow (status); } finally { closed = true; diff --git a/mcs/class/System/Mono.AppleTls/AppleTlsStream.cs b/mcs/class/System/Mono.AppleTls/AppleTlsStream.cs index e3b9fa85933..412b4da1f10 100644 --- a/mcs/class/System/Mono.AppleTls/AppleTlsStream.cs +++ b/mcs/class/System/Mono.AppleTls/AppleTlsStream.cs @@ -38,12 +38,12 @@ namespace Mono.AppleTls } protected override MNS.MobileTlsContext CreateContext ( - MNS.MobileAuthenticatedStream parent, bool serverMode, string targetHost, - SslProtocols enabledProtocols, X509Certificate serverCertificate, - X509CertificateCollection clientCertificates, bool askForClientCert) + bool serverMode, string targetHost, SslProtocols enabledProtocols, + X509Certificate serverCertificate, X509CertificateCollection clientCertificates, + bool askForClientCert) { return new AppleTlsContext ( - parent, serverMode, targetHost, + this, serverMode, targetHost, enabledProtocols, serverCertificate, clientCertificates, askForClientCert); } diff --git a/mcs/class/System/Mono.Btls/MonoBtlsContext.cs b/mcs/class/System/Mono.Btls/MonoBtlsContext.cs index 841daa270fa..08c2678a227 100644 --- a/mcs/class/System/Mono.Btls/MonoBtlsContext.cs +++ b/mcs/class/System/Mono.Btls/MonoBtlsContext.cs @@ -316,6 +316,9 @@ namespace Mono.Btls if (status == MonoBtlsSslError.WantRead) { wantMore = true; return 0; + } else if (status == MonoBtlsSslError.ZeroReturn) { + wantMore = false; + return size; } else if (status != MonoBtlsSslError.None) { throw GetException (status); } @@ -358,26 +361,11 @@ namespace Mono.Btls } } - public override void Close () + public override void Shutdown () { - Debug ("Close!"); - - if (ssl != null) { - ssl.Dispose (); - ssl = null; - } - if (ctx != null) { - ctx.Dispose (); - ctx = null; - } - if (bio != null) { - bio.Dispose (); - bio = null; - } - if (errbio != null) { - errbio.Dispose (); - errbio = null; - } + Debug ("Shutdown!"); +// ssl.SetQuietShutdown (); + ssl.Shutdown (); } void Dispose<T> (ref T disposable) @@ -397,12 +385,12 @@ namespace Mono.Btls { try { if (disposing) { + Dispose (ref ssl); + Dispose (ref ctx); Dispose (ref remoteCertificate); Dispose (ref nativeServerCertificate); Dispose (ref nativeClientCertificate); Dispose (ref clientCertificate); - Dispose (ref ctx); - Dispose (ref ssl); Dispose (ref bio); Dispose (ref errbio); } diff --git a/mcs/class/System/Mono.Btls/MonoBtlsObject.cs b/mcs/class/System/Mono.Btls/MonoBtlsObject.cs index 4264411bd90..19e72cbda64 100644 --- a/mcs/class/System/Mono.Btls/MonoBtlsObject.cs +++ b/mcs/class/System/Mono.Btls/MonoBtlsObject.cs @@ -102,6 +102,20 @@ namespace Mono.Btls CheckError (ret == 1, callerName); } + protected internal void CheckLastError ([CallerMemberName] string callerName = null) + { + var error = Interlocked.Exchange (ref lastError, null); + if (error == null) + return; + + string message; + if (callerName != null) + message = string.Format ("Caught unhandled exception in {0}.{1}.", GetType ().Name, callerName); + else + message = string.Format ("Caught unhandled exception."); + throw new MonoBtlsException (message, error); + } + [DllImport (BTLS_DYLIB)] extern static void mono_btls_free (IntPtr data); diff --git a/mcs/class/System/Mono.Btls/MonoBtlsSsl.cs b/mcs/class/System/Mono.Btls/MonoBtlsSsl.cs index 09e171485f6..e5fac698c0e 100644 --- a/mcs/class/System/Mono.Btls/MonoBtlsSsl.cs +++ b/mcs/class/System/Mono.Btls/MonoBtlsSsl.cs @@ -47,6 +47,7 @@ namespace Mono.Btls protected override bool ReleaseHandle () { mono_btls_ssl_destroy (handle); + handle = IntPtr.Zero; return true; } } @@ -79,6 +80,12 @@ namespace Mono.Btls extern static void mono_btls_ssl_close (IntPtr handle); [DllImport (BTLS_DYLIB)] + extern static int mono_btls_ssl_shutdown (IntPtr handle); + + [DllImport (BTLS_DYLIB)] + extern static void mono_btls_ssl_set_quiet_shutdown (IntPtr handle, int mode); + + [DllImport (BTLS_DYLIB)] extern static void mono_btls_ssl_set_bio (IntPtr handle, IntPtr bio); [DllImport (BTLS_DYLIB)] @@ -131,6 +138,7 @@ namespace Mono.Btls return new BoringSslHandle (handle); } + MonoBtlsBio bio; PrintErrorsCallbackFunc printErrorsFunc; IntPtr printErrorsFuncPtr; @@ -148,6 +156,7 @@ namespace Mono.Btls public void SetBio (MonoBtlsBio bio) { CheckThrow (); + this.bio = bio; mono_btls_ssl_set_bio ( Handle.DangerousGetHandle (), bio.Handle.DangerousGetHandle ()); @@ -164,18 +173,17 @@ namespace Mono.Btls errors = null; } - if (errors != null) { - Console.Error.WriteLine ("ERROR: {0} failed: {1}", callerName, errors); + if (errors != null) throw new MonoBtlsException ("{0} failed: {1}.", callerName, errors); - } else { - Console.Error.WriteLine ("ERROR: {0} failed.", callerName); + else throw new MonoBtlsException ("{0} failed.", callerName); - } } MonoBtlsSslError GetError (int ret_code) { CheckThrow (); + bio.CheckLastError (); + var error = mono_btls_ssl_get_error ( Handle.DangerousGetHandle (), ret_code); return (MonoBtlsSslError)error; @@ -287,15 +295,20 @@ namespace Mono.Btls var ret = mono_btls_ssl_read ( Handle.DangerousGetHandle (), data, dataSize); - if (ret >= 0) { + if (ret > 0) { dataSize = ret; return MonoBtlsSslError.None; } - var error = mono_btls_ssl_get_error ( - Handle.DangerousGetHandle (), ret); + var error = GetError (ret); + if (ret == 0 && error == MonoBtlsSslError.Syscall) { + // End-of-stream + dataSize = 0; + return MonoBtlsSslError.None; + } + dataSize = 0; - return (MonoBtlsSslError)error; + return error; } public MonoBtlsSslError Write (IntPtr data, ref int dataSize) @@ -416,9 +429,24 @@ namespace Mono.Btls return Marshal.PtrToStringAnsi (namePtr); } + public void Shutdown () + { + CheckThrow (); + var ret = mono_btls_ssl_shutdown (Handle.DangerousGetHandle ()); + if (ret < 0) + throw ThrowError (); + } + + public void SetQuietShutdown () + { + CheckThrow (); + mono_btls_ssl_set_quiet_shutdown (Handle.DangerousGetHandle (), 1); + } + protected override void Close () { - mono_btls_ssl_close (Handle.DangerousGetHandle ()); + if (!Handle.IsInvalid) + mono_btls_ssl_close (Handle.DangerousGetHandle ()); } } } diff --git a/mcs/class/System/Mono.Btls/MonoBtlsStream.cs b/mcs/class/System/Mono.Btls/MonoBtlsStream.cs index e941fcd1c0c..b3bb65f9b8e 100644 --- a/mcs/class/System/Mono.Btls/MonoBtlsStream.cs +++ b/mcs/class/System/Mono.Btls/MonoBtlsStream.cs @@ -53,12 +53,12 @@ namespace Mono.Btls } protected override MNS.MobileTlsContext CreateContext ( - MNS.MobileAuthenticatedStream parent, bool serverMode, string targetHost, - SslProtocols enabledProtocols, X509Certificate serverCertificate, - X509CertificateCollection clientCertificates, bool askForClientCert) + bool serverMode, string targetHost, SslProtocols enabledProtocols, + X509Certificate serverCertificate, X509CertificateCollection clientCertificates, + bool askForClientCert) { return new MonoBtlsContext ( - parent, serverMode, targetHost, + this, serverMode, targetHost, enabledProtocols, serverCertificate, clientCertificates, askForClientCert); } diff --git a/mcs/class/System/Mono.Net.Security/AsyncProtocolRequest.cs b/mcs/class/System/Mono.Net.Security/AsyncProtocolRequest.cs index 5f90ffe2b82..3c537a7e427 100644 --- a/mcs/class/System/Mono.Net.Security/AsyncProtocolRequest.cs +++ b/mcs/class/System/Mono.Net.Security/AsyncProtocolRequest.cs @@ -11,14 +11,14 @@ using System; using System.IO; using System.Net; using System.Net.Security; +using System.Security.Authentication; using SD = System.Diagnostics; using System.Threading; using System.Threading.Tasks; +using System.Runtime.ExceptionServices; namespace Mono.Net.Security { - delegate AsyncOperationStatus AsyncOperation (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status); - class BufferOffsetSize { public byte[] Buffer; @@ -37,6 +37,13 @@ namespace Mono.Net.Security public BufferOffsetSize (byte[] buffer, int offset, int size) { + if (buffer == null) + throw new ArgumentNullException (nameof (buffer)); + if (offset < 0) + throw new ArgumentOutOfRangeException (nameof (offset)); + if (size < 0 || offset + size > buffer.Length) + throw new ArgumentOutOfRangeException (nameof (size)); + Buffer = buffer; Offset = offset; Size = size; @@ -54,7 +61,7 @@ namespace Mono.Net.Security public readonly int InitialSize; public BufferOffsetSize2 (int size) - : base (new byte [size], 0, 0) + : base (new byte[size], 0, 0) { InitialSize = size; } @@ -63,7 +70,7 @@ namespace Mono.Net.Security { Offset = Size = 0; TotalBytes = 0; - Buffer = new byte [InitialSize]; + Buffer = new byte[InitialSize]; Complete = false; } @@ -74,11 +81,11 @@ namespace Mono.Net.Security int missing = size - Remaining; if (Offset == 0 && Size == 0) { - Buffer = new byte [size]; + Buffer = new byte[size]; return; } - var buffer = new byte [Buffer.Length + missing]; + var buffer = new byte[Buffer.Length + missing]; Buffer.CopyTo (buffer, 0); Buffer = buffer; } @@ -91,201 +98,296 @@ namespace Mono.Net.Security } } - enum AsyncOperationStatus { - NotStarted, + enum AsyncOperationStatus + { Initialize, Continue, - Running, - Complete, - WantRead, - WantWrite, ReadDone, - FinishWrite + Complete } - class AsyncProtocolRequest + class AsyncProtocolResult { - public readonly MobileAuthenticatedStream Parent; - public readonly BufferOffsetSize UserBuffer; + public int UserResult { + get; + } + public ExceptionDispatchInfo Error { + get; + } - int RequestedSize; - public int CurrentSize; - public int UserResult; + public AsyncProtocolResult (int result) + { + UserResult = result; + } - AsyncOperation Operation; - int Status; + public AsyncProtocolResult (ExceptionDispatchInfo error) + { + Error = error; + } + } - public readonly int ID = ++next_id; - static int next_id; + abstract class AsyncProtocolRequest + { + public MobileAuthenticatedStream Parent { + get; + } - public readonly LazyAsyncResult UserAsyncResult; + public bool RunSynchronously { + get; + } - public AsyncProtocolRequest (MobileAuthenticatedStream parent, LazyAsyncResult lazyResult, BufferOffsetSize userBuffer = null) - { - Parent = parent; - UserAsyncResult = lazyResult; - UserBuffer = userBuffer; + public int ID => ++next_id; + + public string Name => GetType ().Name; + + public int UserResult { + get; + protected set; } - public bool CompleteWithError (Exception ex) + int Started; + int RequestedSize; + int WriteRequested; + readonly object locker = new object (); + + static int next_id; + + public AsyncProtocolRequest (MobileAuthenticatedStream parent, bool sync) { - Status = (int)AsyncOperationStatus.Complete; - if (UserAsyncResult == null) - return true; - if (!UserAsyncResult.InternalPeekCompleted) - UserAsyncResult.InvokeCallback (ex); - return false; + Parent = parent; + RunSynchronously = sync; } [SD.Conditional ("MARTIN_DEBUG")] protected void Debug (string message, params object[] args) { - Parent.Debug ("AsyncProtocolRequest({0}:{1}): {2}", Parent.ID, ID, string.Format (message, args)); + Parent.Debug ("{0}({1}:{2}): {3}", Name, Parent.ID, ID, string.Format (message, args)); } internal void RequestRead (int size) { - var oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.WantRead, (int)AsyncOperationStatus.Running); - Debug ("RequestRead: {0} {1}", oldStatus, size); - if (oldStatus == AsyncOperationStatus.Running) - RequestedSize = size; - else if (oldStatus == AsyncOperationStatus.WantRead) + lock (locker) { RequestedSize += size; - else if (oldStatus != AsyncOperationStatus.WantWrite) - throw new InvalidOperationException (); + Debug ("RequestRead: {0}", size); + } } - internal void ResetRead () + internal void RequestWrite () { - var oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.Complete, (int)AsyncOperationStatus.WantRead); - Debug ("ResetRead: {0} {1}", oldStatus, Status); + WriteRequested = 1; } - internal void ResetWrite () + internal async Task<AsyncProtocolResult> StartOperation (CancellationToken cancellationToken) { - var oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.Complete, (int)AsyncOperationStatus.WantWrite); - Debug ("ResetWrite: {0} {1}", oldStatus, Status); + Debug ("Start Operation: {0}", this); + if (Interlocked.CompareExchange (ref Started, 1, 0) != 0) + throw new InvalidOperationException (); + + try { + await ProcessOperation (cancellationToken).ConfigureAwait (false); + return new AsyncProtocolResult (UserResult); + } catch (Exception ex) { + var info = Parent.SetException (MobileAuthenticatedStream.GetSSPIException (ex)); + return new AsyncProtocolResult (info); + } } - internal void RequestWrite () + async Task ProcessOperation (CancellationToken cancellationToken) { - var oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.WantWrite, (int)AsyncOperationStatus.Running); - Debug ("RequestWrite: {0} {1}", oldStatus, Status); - if (oldStatus == AsyncOperationStatus.Running) - return; - else if (oldStatus != AsyncOperationStatus.WantRead && oldStatus != AsyncOperationStatus.WantWrite) - throw new InvalidOperationException (); + var status = AsyncOperationStatus.Initialize; + while (status != AsyncOperationStatus.Complete) { + cancellationToken.ThrowIfCancellationRequested (); + Debug ("ProcessOperation: {0}", status); + + var ret = await InnerRead (cancellationToken).ConfigureAwait (false); + if (ret != null) { + if (ret == 0) { + // End-of-stream + Debug ("END OF STREAM!"); + status = AsyncOperationStatus.ReadDone; + } else if (ret < 0) { + // remote prematurely closed connection. + throw new IOException ("Remote prematurely closed connection."); + } + } + + Debug ("ProcessOperation run: {0}", status); + + AsyncOperationStatus newStatus; + switch (status) { + case AsyncOperationStatus.Initialize: + case AsyncOperationStatus.Continue: + case AsyncOperationStatus.ReadDone: + newStatus = Run (status); + break; + default: + throw new InvalidOperationException (); + } + + if (Interlocked.Exchange (ref WriteRequested, 0) != 0) { + // Flush the write queue. + await Parent.InnerWrite (RunSynchronously, cancellationToken); + } + + Debug ("ProcessOperation done: {0} -> {1}", status, newStatus); + + status = newStatus; + } } - internal void StartOperation (AsyncOperation operation) + async Task<int?> InnerRead (CancellationToken cancellationToken) { - Debug ("Start Operation: {0} {1}", Status, operation); - if (Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.Initialize, (int)AsyncOperationStatus.NotStarted) != (int)AsyncOperationStatus.NotStarted) - throw new InvalidOperationException (); + int? totalRead = null; + var requestedSize = Interlocked.Exchange (ref RequestedSize, 0); + while (requestedSize > 0) { + Debug ("ProcessOperation - read inner: {0}", requestedSize); - Operation = operation; + var ret = await Parent.InnerRead (RunSynchronously, requestedSize, cancellationToken).ConfigureAwait (false); + Debug ("ProcessOperation - read inner done: {0} - {1}", requestedSize, ret); - if (UserAsyncResult == null) { - StartOperation (); - return; + if (ret <= 0) + return ret; + if (ret > requestedSize) + throw new InvalidOperationException (); + + totalRead += ret; + requestedSize -= ret; + var newRequestedSize = Interlocked.Exchange (ref RequestedSize, 0); + requestedSize += newRequestedSize; } - ThreadPool.QueueUserWorkItem (_ => StartOperation ()); + return totalRead; } - void StartOperation () + /* + * This will operate on the internal buffers and never block. + */ + protected abstract AsyncOperationStatus Run (AsyncOperationStatus status); + + public override string ToString () { - try { - ProcessOperation (); - if (UserAsyncResult != null && !UserAsyncResult.InternalPeekCompleted) - UserAsyncResult.InvokeCallback (UserResult); - } catch (Exception ex) { - if (UserAsyncResult == null) - throw; - if (!UserAsyncResult.InternalPeekCompleted) - UserAsyncResult.InvokeCallback (ex); - } + return string.Format ("[{0}]", Name); } + } - void ProcessOperation () + class AsyncHandshakeRequest : AsyncProtocolRequest + { + public AsyncHandshakeRequest (MobileAuthenticatedStream parent, bool sync) + : base (parent, sync) { - AsyncOperationStatus status; - do { - status = (AsyncOperationStatus)Interlocked.Exchange (ref Status, (int)AsyncOperationStatus.Running); + } - Debug ("ProcessOperation: {0}", status); + protected override AsyncOperationStatus Run (AsyncOperationStatus status) + { + return Parent.ProcessHandshake (status); + } + } - status = ProcessOperation (status); + abstract class AsyncReadOrWriteRequest : AsyncProtocolRequest + { + protected BufferOffsetSize UserBuffer { + get; + } - Debug ("ProcessOperation done: {0}", status); + protected int CurrentSize { + get; set; + } - AsyncOperationStatus oldStatus; - if (status == AsyncOperationStatus.Complete) { - oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)AsyncOperationStatus.FinishWrite, (int)AsyncOperationStatus.WantWrite); - if (oldStatus == AsyncOperationStatus.WantWrite) { - // We are done, but still need to flush the write queue. - status = AsyncOperationStatus.FinishWrite; - continue; - } - } + public AsyncReadOrWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size) + : base (parent, sync) + { + UserBuffer = new BufferOffsetSize (buffer, offset, size); + } - oldStatus = (AsyncOperationStatus)Interlocked.CompareExchange (ref Status, (int)status, (int)AsyncOperationStatus.Running); - Debug ("ProcessOperation done: {0} -> {1}", oldStatus, status); + public override string ToString () + { + return string.Format ("[{0}: {1}]", Name, UserBuffer); + } + } - if (oldStatus != AsyncOperationStatus.Running) { - if (status == oldStatus || status == AsyncOperationStatus.Continue || status == AsyncOperationStatus.Complete) - status = oldStatus; - else - throw new InvalidOperationException (); - } - } while (status != AsyncOperationStatus.Complete); + class AsyncReadRequest : AsyncReadOrWriteRequest + { + public AsyncReadRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size) + : base (parent, sync, buffer, offset, size) + { } - AsyncOperationStatus ProcessOperation (AsyncOperationStatus status) + protected override AsyncOperationStatus Run (AsyncOperationStatus status) { - if (status == AsyncOperationStatus.WantRead) { - if (RequestedSize < 0) - throw new InvalidOperationException (); - else if (RequestedSize == 0) - return AsyncOperationStatus.Continue; - - Debug ("ProcessOperation - read inner: {0}", RequestedSize); - var ret = Parent.InnerRead (RequestedSize); - Debug ("ProcessOperation - read inner done: {0} - {1}", RequestedSize, ret); - - if (ret < 0) - return AsyncOperationStatus.ReadDone; - - RequestedSize -= ret; - - if (ret == 0 || RequestedSize == 0) - return AsyncOperationStatus.Continue; - else - return AsyncOperationStatus.WantRead; - } else if (status == AsyncOperationStatus.WantWrite) { - Debug ("ProcessOperation - want write"); - Parent.InnerWrite (); - Debug ("ProcessOperation - want write done"); + Debug ("ProcessRead - read user: {0} {1}", this, status); + + var (ret, wantMore) = Parent.ProcessRead (UserBuffer); + + Debug ("ProcessRead - read user done: {0} - {1} {2}", this, ret, wantMore); + + if (ret < 0) { + UserResult = -1; + return AsyncOperationStatus.Complete; + } + + CurrentSize += ret; + UserBuffer.Offset += ret; + UserBuffer.Size -= ret; + + Debug ("Process Read - read user done #1: {0} - {1} {2}", this, CurrentSize, wantMore); + + if (wantMore && CurrentSize == 0) return AsyncOperationStatus.Continue; - } else if (status == AsyncOperationStatus.Initialize || status == AsyncOperationStatus.Continue) { - Debug ("ProcessOperation - continue"); - status = Operation (this, status); - Debug ("ProcessOperation - continue done: {0}", status); - return status; - } else if (status == AsyncOperationStatus.ReadDone) { - Debug ("ProcessOperation - read done"); - status = Operation (this, status); - Debug ("ProcessOperation - read done: {0}", status); - return status; - } else if (status == AsyncOperationStatus.FinishWrite) { - Debug ("ProcessOperation - finish write"); - Parent.InnerWrite (); - Debug ("ProcessOperation - finish write done"); + + UserResult = CurrentSize; + return AsyncOperationStatus.Complete; + } + } + + class AsyncWriteRequest : AsyncReadOrWriteRequest + { + public AsyncWriteRequest (MobileAuthenticatedStream parent, bool sync, byte[] buffer, int offset, int size) + : base (parent, sync, buffer, offset, size) + { + } + + protected override AsyncOperationStatus Run (AsyncOperationStatus status) + { + Debug ("ProcessWrite - write user: {0} {1}", this, status); + + if (UserBuffer.Size == 0) { + UserResult = CurrentSize; + return AsyncOperationStatus.Complete; + } + + var (ret, wantMore) = Parent.ProcessWrite (UserBuffer); + + Debug ("ProcessWrite - write user done: {0} - {1} {2}", this, ret, wantMore); + + if (ret < 0) { + UserResult = -1; return AsyncOperationStatus.Complete; } - throw new InvalidOperationException (); + CurrentSize += ret; + UserBuffer.Offset += ret; + UserBuffer.Size -= ret; + + if (wantMore) + return AsyncOperationStatus.Continue; + + UserResult = CurrentSize; + return AsyncOperationStatus.Complete; + } + } + + class AsyncShutdownRequest : AsyncProtocolRequest + { + public AsyncShutdownRequest (MobileAuthenticatedStream parent) + : base (parent, false) + { + } + + protected override AsyncOperationStatus Run (AsyncOperationStatus status) + { + return Parent.ProcessShutdown (status); } } + } #endif diff --git a/mcs/class/System/Mono.Net.Security/LegacySslStream.cs b/mcs/class/System/Mono.Net.Security/LegacySslStream.cs index a5572addc13..e4032496da1 100644 --- a/mcs/class/System/Mono.Net.Security/LegacySslStream.cs +++ b/mcs/class/System/Mono.Net.Security/LegacySslStream.cs @@ -575,6 +575,11 @@ namespace Mono.Net.Security.Private #region IMonoSslStream + Task IMonoSslStream.ShutdownAsync () + { + return Task.CompletedTask; + } + AuthenticatedStream IMonoSslStream.AuthenticatedStream { get { return this; } } diff --git a/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs b/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs index 1e57c673415..2b380a1ae6c 100644 --- a/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs +++ b/mcs/class/System/Mono.Net.Security/MobileAuthenticatedStream.cs @@ -23,6 +23,7 @@ using System.IO; using System.Net; using System.Net.Security; using System.Globalization; +using System.Security.Authentication; using System.Runtime.ExceptionServices; using System.Threading; using System.Threading.Tasks; @@ -36,8 +37,13 @@ namespace Mono.Net.Security { abstract class MobileAuthenticatedStream : AuthenticatedStream, MSI.IMonoSslStream { + /* + * This is intentionally called `xobileTlsContext'. It is a "dangerous" object + * that must not be touched outside the `ioLock' and we need to be very careful + * where we access it. + */ MobileTlsContext xobileTlsContext; - Exception lastException; + ExceptionDispatchInfo lastException; AsyncProtocolRequest asyncHandshakeRequest; AsyncProtocolRequest asyncReadRequest; @@ -47,11 +53,12 @@ namespace Mono.Net.Security object ioLock = new object (); int closeRequested; + bool shutdown; static int uniqueNameInteger = 123; public MobileAuthenticatedStream (Stream innerStream, bool leaveInnerStreamOpen, SslStream owner, - MSI.MonoTlsSettings settings, MSI.MonoTlsProvider provider) + MSI.MonoTlsSettings settings, MSI.MonoTlsProvider provider) : base (innerStream, leaveInnerStreamOpen) { SslStream = owner; @@ -78,42 +85,47 @@ namespace Mono.Net.Security get { return xobileTlsContext != null; } } - internal MobileTlsContext Context { - get { - CheckThrow (true); - return xobileTlsContext; - } - } - - internal void CheckThrow (bool authSuccessCheck) + internal void CheckThrow (bool authSuccessCheck, bool shutdownCheck = false) { - if (closeRequested != 0) - throw new InvalidOperationException ("Stream is closed."); if (lastException != null) - throw lastException; + lastException.Throw (); if (authSuccessCheck && !IsAuthenticated) - throw new InvalidOperationException ("Must be authenticated."); + throw new InvalidOperationException (SR.net_auth_noauth); + if (shutdownCheck && shutdown) + throw new InvalidOperationException (SR.net_ssl_io_already_shutdown); } - Exception SetException (Exception e) + internal static Exception GetSSPIException (Exception e) { - e = SetException_internal (e); - if (e != null && xobileTlsContext != null) - xobileTlsContext.Dispose (); - return e; + if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException) + return e; + return new AuthenticationException (SR.net_auth_SSPI, e); } - Exception SetException_internal (Exception e) + internal static Exception GetIOException (Exception e, string message) { - if (lastException == null) - lastException = e; - return lastException; + if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException) + return e; + return new IOException (message, e); + } + + internal ExceptionDispatchInfo SetException (Exception e) + { + var info = ExceptionDispatchInfo.Capture (e); + var old = Interlocked.CompareExchange (ref lastException, info, null); + return old ?? info; } SslProtocols DefaultProtocols { get { return SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; } } + enum OperationType { + Read, + Write, + Shutdown + } + public void AuthenticateAsClient (string targetHost) { AuthenticateAsClient (targetHost, new X509CertificateCollection (), DefaultProtocols, false); @@ -121,8 +133,8 @@ namespace Mono.Net.Security public void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation) { - ValidateCreateContext (false, targetHost, enabledSslProtocols, null, clientCertificates, false); - ProcessAuthentication (null); + var task = ProcessAuthentication (true, false, targetHost, enabledSslProtocols, null, clientCertificates, false); + task.Wait (); } public IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState) @@ -132,15 +144,13 @@ namespace Mono.Net.Security public IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState) { - ValidateCreateContext (false, targetHost, enabledSslProtocols, null, clientCertificates, false); - var result = new LazyAsyncResult (this, asyncState, asyncCallback); - ProcessAuthentication (result); - return result; + var task = ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false); + return TaskToApm.Begin (task, asyncCallback, asyncState); } public void EndAuthenticateAsClient (IAsyncResult asyncResult) { - EndProcessAuthentication (asyncResult); + TaskToApm.End (asyncResult); } public void AuthenticateAsServer (X509Certificate serverCertificate) @@ -150,8 +160,8 @@ namespace Mono.Net.Security public void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation) { - ValidateCreateContext (true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired); - ProcessAuthentication (null); + var task = ProcessAuthentication (true, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired); + task.Wait (); } public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState) @@ -161,218 +171,214 @@ namespace Mono.Net.Security public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState) { - ValidateCreateContext (true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired); - var result = new LazyAsyncResult (this, asyncState, asyncCallback); - ProcessAuthentication (result); - return result; + var task = ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired); + return TaskToApm.Begin (task, asyncCallback, asyncState); } public void EndAuthenticateAsServer (IAsyncResult asyncResult) { - EndProcessAuthentication (asyncResult); + TaskToApm.End (asyncResult); } public Task AuthenticateAsClientAsync (string targetHost) { - return Task.Factory.FromAsync (BeginAuthenticateAsClient, EndAuthenticateAsClient, targetHost, null); + return ProcessAuthentication (false, false, targetHost, DefaultProtocols, null, null, false); } public Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation) { - return Task.Factory.FromAsync ((callback, state) => BeginAuthenticateAsClient (targetHost, clientCertificates, enabledSslProtocols, checkCertificateRevocation, callback, state), EndAuthenticateAsClient, null); + return ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false); } public Task AuthenticateAsServerAsync (X509Certificate serverCertificate) { - return Task.Factory.FromAsync (BeginAuthenticateAsServer, EndAuthenticateAsServer, serverCertificate, null); + return AuthenticateAsServerAsync (serverCertificate, false, DefaultProtocols, false); } public Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation) { - return Task.Factory.FromAsync ((callback, state) => BeginAuthenticateAsServer (serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation, callback, state), EndAuthenticateAsServer, null); + return ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired); + } + + public Task ShutdownAsync () + { + Debug ("ShutdownAsync"); + + /* + * SSLClose() is a little bit tricky as it might attempt to send a close_notify alert + * and thus call our write callback. + * + * It is also not thread-safe with SSLRead() or SSLWrite(), so we need to take the I/O lock here. + */ + var asyncRequest = new AsyncShutdownRequest (this); + var task = StartOperation (OperationType.Shutdown, asyncRequest, CancellationToken.None); + return task; } public AuthenticatedStream AuthenticatedStream { get { return this; } } - internal void ProcessAuthentication (LazyAsyncResult lazyResult) + async Task ProcessAuthentication ( + bool runSynchronously, bool serverMode, string targetHost, SslProtocols enabledProtocols, + X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool clientCertRequired) { - var asyncRequest = new AsyncProtocolRequest (this, lazyResult); + if (serverMode) { + if (serverCertificate == null) + throw new ArgumentException (nameof (serverCertificate)); + } else { + if (targetHost == null) + throw new ArgumentException (nameof (targetHost)); + if (targetHost.Length == 0) + targetHost = "?" + Interlocked.Increment (ref uniqueNameInteger).ToString (NumberFormatInfo.InvariantInfo); + } + + if (lastException != null) + lastException.Throw (); + + var asyncRequest = new AsyncHandshakeRequest (this, runSynchronously); if (Interlocked.CompareExchange (ref asyncHandshakeRequest, asyncRequest, null) != null) throw new InvalidOperationException ("Invalid nested call."); + // Make sure no other async requests can be started during the handshake. + if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null) + throw new InvalidOperationException ("Invalid nested call."); + if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null) + throw new InvalidOperationException ("Invalid nested call."); + + AsyncProtocolResult result; try { - if (lastException != null) - throw lastException; - if (xobileTlsContext == null) - throw new InvalidOperationException (); + lock (ioLock) { + if (xobileTlsContext != null) + throw new InvalidOperationException (); + readBuffer.Reset (); + writeBuffer.Reset (); - readBuffer.Reset (); - writeBuffer.Reset (); + xobileTlsContext = CreateContext ( + serverMode, targetHost, enabledProtocols, serverCertificate, + clientCertificates, clientCertRequired); + } try { - asyncRequest.StartOperation (ProcessHandshake); + result = await asyncRequest.StartOperation (CancellationToken.None).ConfigureAwait (false); } catch (Exception ex) { - ExceptionDispatchInfo.Capture (SetException (ex)).Throw (); + result = new AsyncProtocolResult (SetException (GetSSPIException (ex))); } } finally { - if (lazyResult == null || lastException != null) { + lock (ioLock) { readBuffer.Reset (); writeBuffer.Reset (); + asyncWriteRequest = null; + asyncReadRequest = null; asyncHandshakeRequest = null; } } - } - - internal void EndProcessAuthentication (IAsyncResult result) - { - if (result == null) - throw new ArgumentNullException ("asyncResult"); - - var lazyResult = (LazyAsyncResult)result; - if (Interlocked.Exchange (ref asyncHandshakeRequest, null) == null) - throw new InvalidOperationException ("Invalid end call."); - - lazyResult.InternalWaitForCompletion (); - - readBuffer.Reset (); - writeBuffer.Reset (); - - var e = lazyResult.Result as Exception; - if (e != null) - ExceptionDispatchInfo.Capture (SetException (e)).Throw (); - } - - internal void ValidateCreateContext (bool serverMode, string targetHost, SslProtocols enabledProtocols, X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool clientCertRequired) - { - if (xobileTlsContext != null) - throw new InvalidOperationException (); - - if (serverMode) { - if (serverCertificate == null) - throw new ArgumentException ("serverCertificate"); - } else { - if (targetHost == null) - throw new ArgumentException ("targetHost"); - if (targetHost.Length == 0) - targetHost = "?" + Interlocked.Increment (ref uniqueNameInteger).ToString (NumberFormatInfo.InvariantInfo); - } - xobileTlsContext = CreateContext (this, serverMode, targetHost, enabledProtocols, serverCertificate, clientCertificates, clientCertRequired); + if (result.Error != null) + result.Error.Throw (); } protected abstract MobileTlsContext CreateContext ( - MobileAuthenticatedStream parent, bool serverMode, string targetHost, - SSA.SslProtocols enabledProtocols, X509Certificate serverCertificate, - X509CertificateCollection clientCertificates, bool askForClientCert); + bool serverMode, string targetHost, SSA.SslProtocols enabledProtocols, + X509Certificate serverCertificate, X509CertificateCollection clientCertificates, + bool askForClientCert); public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState) { - return BeginReadOrWrite (ref asyncReadRequest, ref readBuffer, ProcessRead, new BufferOffsetSize (buffer, offset, count), asyncCallback, asyncState); + var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count); + var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None); + return TaskToApm.Begin (task, asyncCallback, asyncState); } public override int EndRead (IAsyncResult asyncResult) { - return (int)EndReadOrWrite (asyncResult, ref asyncReadRequest); + return TaskToApm.End<int> (asyncResult); } public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState) { - return BeginReadOrWrite (ref asyncWriteRequest, ref writeBuffer, ProcessWrite, new BufferOffsetSize (buffer, offset, count), asyncCallback, asyncState); + var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count); + var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None); + return TaskToApm.Begin (task, asyncCallback, asyncState); } public override void EndWrite (IAsyncResult asyncResult) { - EndReadOrWrite (asyncResult, ref asyncWriteRequest); + TaskToApm.End (asyncResult); } public override int Read (byte[] buffer, int offset, int count) { - return ProcessReadOrWrite (ref asyncReadRequest, ref readBuffer, ProcessRead, new BufferOffsetSize (buffer, offset, count), null); + var asyncRequest = new AsyncReadRequest (this, true, buffer, offset, count); + var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None); + return task.Result; } public void Write (byte[] buffer) { Write (buffer, 0, buffer.Length); } + public override void Write (byte[] buffer, int offset, int count) { - ProcessReadOrWrite (ref asyncWriteRequest, ref writeBuffer, ProcessWrite, new BufferOffsetSize (buffer, offset, count), null); + var asyncRequest = new AsyncWriteRequest (this, true, buffer, offset, count); + var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None); + task.Wait (); } - IAsyncResult BeginReadOrWrite (ref AsyncProtocolRequest nestedRequest, ref BufferOffsetSize2 internalBuffer, AsyncOperation operation, BufferOffsetSize userBuffer, AsyncCallback asyncCallback, object asyncState) + public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - LazyAsyncResult lazyResult = new LazyAsyncResult (this, asyncState, asyncCallback); - ProcessReadOrWrite (ref nestedRequest, ref internalBuffer, operation, userBuffer, lazyResult); - return lazyResult; + var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count); + return StartOperation (OperationType.Read, asyncRequest, cancellationToken); } - object EndReadOrWrite (IAsyncResult asyncResult, ref AsyncProtocolRequest nestedRequest) + public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken) { - if (asyncResult == null) - throw new ArgumentNullException("asyncResult"); - - var lazyResult = (LazyAsyncResult)asyncResult; - - if (Interlocked.Exchange (ref nestedRequest, null) == null) - throw new InvalidOperationException ("Invalid end call."); - - // No "artificial" timeouts implemented so far, InnerStream controls timeout. - lazyResult.InternalWaitForCompletion (); - - Debug ("EndReadOrWrite"); - - var e = lazyResult.Result as Exception; - if (e != null) { - var ioEx = e as IOException; - if (ioEx != null) - throw ioEx; - throw new IOException ("read failed", e); - } - - return lazyResult.Result; + var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count); + return StartOperation (OperationType.Write, asyncRequest, cancellationToken); } - int ProcessReadOrWrite (ref AsyncProtocolRequest nestedRequest, ref BufferOffsetSize2 internalBuffer, AsyncOperation operation, BufferOffsetSize userBuffer, LazyAsyncResult lazyResult) + async Task<int> StartOperation (OperationType type, AsyncProtocolRequest asyncRequest, CancellationToken cancellationToken) { - if (userBuffer == null || userBuffer.Buffer == null) - throw new ArgumentNullException ("buffer"); - if (userBuffer.Offset < 0) - throw new ArgumentOutOfRangeException ("offset"); - if (userBuffer.Size < 0 || userBuffer.Offset + userBuffer.Size > userBuffer.Buffer.Length) - throw new ArgumentOutOfRangeException ("count"); - - CheckThrow (true); + CheckThrow (true, type != OperationType.Read); + Debug ("StartOperationAsync: {0} {1}", asyncRequest, type); - var name = internalBuffer == readBuffer ? "read" : "write"; - Debug ("ProcessReadOrWrite: {0} {1}", name, userBuffer); + if (type == OperationType.Read) { + if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null) + throw new InvalidOperationException ("Invalid nested call."); + } else { + if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null) + throw new InvalidOperationException ("Invalid nested call."); + } - var asyncRequest = new AsyncProtocolRequest (this, lazyResult, userBuffer); - return StartOperation (ref nestedRequest, ref internalBuffer, operation, asyncRequest, name); - } + AsyncProtocolResult result; - int StartOperation (ref AsyncProtocolRequest nestedRequest, ref BufferOffsetSize2 internalBuffer, AsyncOperation operation, AsyncProtocolRequest asyncRequest, string name) - { - if (Interlocked.CompareExchange (ref nestedRequest, asyncRequest, null) != null) - throw new InvalidOperationException ("Invalid nested call."); - - bool failed = false; try { - internalBuffer.Reset (); - asyncRequest.StartOperation (operation); - return asyncRequest.UserResult; + lock (ioLock) { + if (type == OperationType.Read) + readBuffer.Reset (); + else + writeBuffer.Reset (); + } + result = await asyncRequest.StartOperation (cancellationToken).ConfigureAwait (false); } catch (Exception e) { - failed = true; - if (e is IOException) - throw; - throw new IOException (name + " failed", e); + var info = SetException (GetIOException (e, asyncRequest.Name + " failed")); + result = new AsyncProtocolResult (info); } finally { - if (asyncRequest.UserAsyncResult == null || failed) { - internalBuffer.Reset (); - nestedRequest = null; + lock (ioLock) { + if (type == OperationType.Read) { + readBuffer.Reset (); + asyncReadRequest = null; + } else { + writeBuffer.Reset (); + asyncWriteRequest = null; + } } } + + if (result.Error != null) + result.Error.Throw (); + return result.UserResult; } static int nextId; @@ -384,26 +390,31 @@ namespace Mono.Net.Security Console.Error.WriteLine ("MobileAuthenticatedStream({0}): {1}", ID, string.Format (message, args)); } - #region Called back from native code via SslConnection +#region Called back from native code via SslConnection /* * Called from within SSLRead() and SSLHandshake(). We only access tha managed byte[] here. */ - internal int InternalRead (byte[] buffer, int offset, int size, out bool wantMore) + internal int InternalRead (byte[] buffer, int offset, int size, out bool outWantMore) { try { - Debug ("InternalRead: {0} {1} {2} {3}", offset, size, asyncReadRequest != null, readBuffer != null); + Debug ("InternalRead: {0} {1} {2} {3} {4}", offset, size, + asyncHandshakeRequest != null ? "handshake" : "", + asyncReadRequest != null ? "async" : "", + readBuffer != null ? readBuffer.ToString () : ""); var asyncRequest = asyncHandshakeRequest ?? asyncReadRequest; - return InternalRead (asyncRequest, readBuffer, buffer, offset, size, out wantMore); + var (ret, wantMore) = InternalRead (asyncRequest, readBuffer, buffer, offset, size); + outWantMore = wantMore; + return ret; } catch (Exception ex) { Debug ("InternalRead failed: {0}", ex); - SetException_internal (ex); - wantMore = false; + SetException (GetIOException (ex, "InternalRead() failed")); + outWantMore = false; return -1; } } - int InternalRead (AsyncProtocolRequest asyncRequest, BufferOffsetSize internalBuffer, byte[] buffer, int offset, int size, out bool wantMore) + (int, bool) InternalRead (AsyncProtocolRequest asyncRequest, BufferOffsetSize internalBuffer, byte[] buffer, int offset, int size) { if (asyncRequest == null) throw new InvalidOperationException (); @@ -422,11 +433,10 @@ namespace Mono.Net.Security * native function again. */ if (internalBuffer.Size == 0 && !internalBuffer.Complete) { - Debug ("InternalRead #1: {0} {1}", internalBuffer.Offset, internalBuffer.TotalBytes); + Debug ("InternalRead #1: {0} {1} {2}", internalBuffer.Offset, internalBuffer.TotalBytes, size); internalBuffer.Offset = internalBuffer.Size = 0; asyncRequest.RequestRead (size); - wantMore = true; - return 0; + return (0, true); } /* @@ -441,8 +451,7 @@ namespace Mono.Net.Security Buffer.BlockCopy (internalBuffer.Buffer, internalBuffer.Offset, buffer, offset, len); internalBuffer.Offset += len; internalBuffer.Size -= len; - wantMore = !internalBuffer.Complete && len < size; - return len; + return (len, !internalBuffer.Complete && len < size); } /* @@ -456,7 +465,7 @@ namespace Mono.Net.Security return InternalWrite (asyncRequest, writeBuffer, buffer, offset, size); } catch (Exception ex) { Debug ("InternalWrite failed: {0}", ex); - SetException_internal (ex); + SetException (GetIOException (ex, "InternalWrite() failed")); return false; } } @@ -511,22 +520,30 @@ namespace Mono.Net.Security return true; } - #endregion +#endregion - #region Inner Stream +#region Inner Stream /* * Read / write data from the inner stream; we're only called from managed code and only manipulate * the internal buffers. */ - internal int InnerRead (int requestedSize) + internal async Task<int> InnerRead (bool sync, int requestedSize, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested (); Debug ("InnerRead: {0} {1} {2} {3}", readBuffer.Offset, readBuffer.Size, readBuffer.Remaining, requestedSize); var len = System.Math.Min (readBuffer.Remaining, requestedSize); if (len == 0) throw new InvalidOperationException (); - var ret = InnerStream.Read (readBuffer.Buffer, readBuffer.EndOffset, len); + + Task<int> task; + if (sync) + task = Task.Run (() => InnerStream.Read (readBuffer.Buffer, readBuffer.EndOffset, len)); + else + task = InnerStream.ReadAsync (readBuffer.Buffer, readBuffer.EndOffset, len, cancellationToken); + + var ret = await task.ConfigureAwait (false); Debug ("InnerRead done: {0} {1} - {2}", readBuffer.Remaining, len, ret); if (ret >= 0) { @@ -549,165 +566,126 @@ namespace Mono.Net.Security return ret; } - internal void InnerWrite () + internal async Task InnerWrite (bool sync, CancellationToken cancellationToken) { + cancellationToken.ThrowIfCancellationRequested (); Debug ("InnerWrite: {0} {1}", writeBuffer.Offset, writeBuffer.Size); - InnerFlush (); - } - internal void InnerFlush () - { - if (writeBuffer.Size > 0) { - InnerStream.Write (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size); - writeBuffer.TotalBytes += writeBuffer.Size; - writeBuffer.Offset = writeBuffer.Size = 0; - } + if (writeBuffer.Size == 0) + return; + + Task task; + if (sync) + task = Task.Run (() => InnerStream.Write (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size)); + else + task = InnerStream.WriteAsync (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size); + + await task.ConfigureAwait (false); + + writeBuffer.TotalBytes += writeBuffer.Size; + writeBuffer.Offset = writeBuffer.Size = 0; } - #endregion +#endregion - #region Main async I/O loop +#region Main async I/O loop - AsyncOperationStatus ProcessHandshake (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status) + internal AsyncOperationStatus ProcessHandshake (AsyncOperationStatus status) { Debug ("ProcessHandshake: {0}", status); - /* - * The first time we're called (AsyncOperationStatus.Initialize), we need to setup the SslContext and - * start the handshake. - */ - if (status == AsyncOperationStatus.Initialize) { - xobileTlsContext.StartHandshake (); - return AsyncOperationStatus.Continue; - } else if (status == AsyncOperationStatus.ReadDone) { - // remote prematurely closed connection. - throw new IOException ("Remote prematurely closed connection."); - } else if (status != AsyncOperationStatus.Continue) { - throw new InvalidOperationException (); - } + lock (ioLock) { + /* + * The first time we're called (AsyncOperationStatus.Initialize), we need to setup the SslContext and + * start the handshake. + */ + if (status == AsyncOperationStatus.Initialize) { + xobileTlsContext.StartHandshake (); + return AsyncOperationStatus.Continue; + } else if (status == AsyncOperationStatus.ReadDone) { + throw new IOException (SR.net_auth_eof); + } else if (status != AsyncOperationStatus.Continue) { + throw new InvalidOperationException (); + } - /* - * SSLHandshake() will return repeatedly with 'SslStatus.WouldBlock', we then need - * to take care of I/O and call it again. - */ - if (!xobileTlsContext.ProcessHandshake ()) { /* - * Flush the internal write buffer. - */ - InnerFlush (); + * SSLHandshake() will return repeatedly with 'SslStatus.WouldBlock', we then need + * to take care of I/O and call it again. + */ + if (xobileTlsContext.ProcessHandshake ()) { + xobileTlsContext.FinishHandshake (); + return AsyncOperationStatus.Complete; + } return AsyncOperationStatus.Continue; } - - xobileTlsContext.FinishHandshake (); - return AsyncOperationStatus.Complete; } - AsyncOperationStatus ProcessRead (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status) + internal (int, bool) ProcessRead (BufferOffsetSize userBuffer) { - Debug ("ProcessRead - read user: {0} {1}", status, asyncRequest.UserBuffer); - - int ret; - bool wantMore; lock (ioLock) { - ret = Context.Read (asyncRequest.UserBuffer.Buffer, asyncRequest.UserBuffer.Offset, asyncRequest.UserBuffer.Size, out wantMore); + // This operates on the internal buffer and will never block. + var ret = xobileTlsContext.Read (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size, out bool wantMore); + return (ret, wantMore); } - Debug ("ProcessRead - read user done: {0} - {1} {2}", asyncRequest.UserBuffer, ret, wantMore); - - if (ret < 0) { - asyncRequest.UserResult = -1; - return AsyncOperationStatus.Complete; - } - - asyncRequest.CurrentSize += ret; - asyncRequest.UserBuffer.Offset += ret; - asyncRequest.UserBuffer.Size -= ret; - - Debug ("Process Read - read user done #1: {0} - {1} {2}", asyncRequest.UserBuffer, asyncRequest.CurrentSize, wantMore); - - if (wantMore && asyncRequest.CurrentSize == 0) - return AsyncOperationStatus.WantRead; - - asyncRequest.ResetRead (); - asyncRequest.UserResult = asyncRequest.CurrentSize; - return AsyncOperationStatus.Complete; } - AsyncOperationStatus ProcessWrite (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status) + internal (int, bool) ProcessWrite (BufferOffsetSize userBuffer) { - Debug ("ProcessWrite - write user: {0} {1}", status, asyncRequest.UserBuffer); - - if (asyncRequest.UserBuffer.Size == 0) { - asyncRequest.UserResult = asyncRequest.CurrentSize; - return AsyncOperationStatus.Complete; - } - - int ret; - bool wantMore; lock (ioLock) { - ret = Context.Write (asyncRequest.UserBuffer.Buffer, asyncRequest.UserBuffer.Offset, asyncRequest.UserBuffer.Size, out wantMore); - } - Debug ("ProcessWrite - write user done: {0} - {1} {2}", asyncRequest.UserBuffer, ret, wantMore); - - if (ret < 0) { - asyncRequest.UserResult = -1; - return AsyncOperationStatus.Complete; + // This operates on the internal buffer and will never block. + var ret = xobileTlsContext.Write (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size, out bool wantMore); + return (ret, wantMore); } - - asyncRequest.CurrentSize += ret; - asyncRequest.UserBuffer.Offset += ret; - asyncRequest.UserBuffer.Size -= ret; - - if (wantMore || writeBuffer.Size > 0) - return AsyncOperationStatus.WantWrite; - - asyncRequest.ResetWrite (); - asyncRequest.UserResult = asyncRequest.CurrentSize; - return AsyncOperationStatus.Complete; } - AsyncOperationStatus ProcessClose (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status) + internal AsyncOperationStatus ProcessShutdown (AsyncOperationStatus status) { - Debug ("ProcessClose: {0}", status); + Debug ("ProcessShutdown: {0}", status); lock (ioLock) { - if (xobileTlsContext == null) - return AsyncOperationStatus.Complete; - - xobileTlsContext.Close (); - xobileTlsContext = null; - return AsyncOperationStatus.Continue; + xobileTlsContext.Shutdown (); + shutdown = true; + return AsyncOperationStatus.Complete; } } - AsyncOperationStatus ProcessFlush (AsyncProtocolRequest asyncRequest, AsyncOperationStatus status) - { - Debug ("ProcessFlush: {0}", status); - return AsyncOperationStatus.Complete; - } - - #endregion +#endregion public override bool IsServer { - get { return xobileTlsContext != null && xobileTlsContext.IsServer; } + get { + CheckThrow (false); + return xobileTlsContext != null && xobileTlsContext.IsServer; + } } public override bool IsAuthenticated { - get { return xobileTlsContext != null && lastException == null && xobileTlsContext.IsAuthenticated; } + get { + lock (ioLock) { + // Don't use CheckThrow(), we want to return false if we're not authenticated. + return xobileTlsContext != null && lastException == null && xobileTlsContext.IsAuthenticated; + } + } } public override bool IsMutuallyAuthenticated { get { - return IsAuthenticated && - (Context.IsServer? Context.LocalServerCertificate: Context.LocalClientCertificate) != null && - Context.IsRemoteCertificateAvailable; + lock (ioLock) { + // Don't use CheckThrow() here. + if (!IsAuthenticated) + return false; + if ((xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate) == null) + return false; + return xobileTlsContext.IsRemoteCertificateAvailable; + } } } protected override void Dispose (bool disposing) { try { - lastException = new ObjectDisposedException ("MobileAuthenticatedStream"); lock (ioLock) { + Debug ("Dispose: {0}", xobileTlsContext != null); + lastException = ExceptionDispatchInfo.Capture (new ObjectDisposedException ("MobileAuthenticatedStream")); if (xobileTlsContext != null) { xobileTlsContext.Dispose (); xobileTlsContext = null; @@ -720,26 +698,53 @@ namespace Mono.Net.Security public override void Flush () { - CheckThrow (true); - var asyncRequest = new AsyncProtocolRequest (this, null); - StartOperation (ref asyncWriteRequest, ref writeBuffer, ProcessFlush, asyncRequest, "flush"); + // Write() automatically flushes the underlying stream. } - public override void Close () - { - /* - * SSLClose() is a little bit tricky as it might attempt to send a close_notify alert - * and thus call our write callback. - * - * It is also not thread-safe with SSLRead() or SSLWrite(), so we need to take the I/O lock here. - */ - if (Interlocked.Exchange (ref closeRequested, 1) == 1) - return; - if (xobileTlsContext == null) - return; + public SslProtocols SslProtocol { + get { + lock (ioLock) { + CheckThrow (true); + return (SslProtocols)xobileTlsContext.NegotiatedProtocol; + } + } + } + + public X509Certificate RemoteCertificate { + get { + lock (ioLock) { + CheckThrow (true); + return xobileTlsContext.RemoteCertificate; + } + } + } + + public X509Certificate LocalCertificate { + get { + lock (ioLock) { + CheckThrow (true); + return InternalLocalCertificate; + } + } + } + + public X509Certificate InternalLocalCertificate { + get { + lock (ioLock) { + CheckThrow (false); + if (xobileTlsContext == null) + return null; + return xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate; + } + } + } - var asyncRequest = new AsyncProtocolRequest (this, null); - StartOperation (ref asyncWriteRequest, ref writeBuffer, ProcessClose, asyncRequest, "close"); + public MSI.MonoTlsConnectionInfo GetConnectionInfo () + { + lock (ioLock) { + CheckThrow (true); + return xobileTlsContext.ConnectionInfo; + } } // @@ -769,7 +774,7 @@ namespace Mono.Net.Security } public override bool CanWrite { - get { return IsAuthenticated & InnerStream.CanWrite; } + get { return IsAuthenticated & InnerStream.CanWrite && !shutdown; } } public override bool CanSeek { @@ -803,46 +808,10 @@ namespace Mono.Net.Security set { InnerStream.WriteTimeout = value; } } - public SslProtocols SslProtocol { - get { - CheckThrow (true); - return (SslProtocols)Context.NegotiatedProtocol; - } - } - - public X509Certificate RemoteCertificate { - get { - CheckThrow (true); - return Context.RemoteCertificate; - } - } - - public X509Certificate LocalCertificate { - get { - CheckThrow (true); - return InternalLocalCertificate; - } - } - - public X509Certificate InternalLocalCertificate { - get { - CheckThrow (false); - if (!HasContext) - return null; - return Context.IsServer ? Context.LocalServerCertificate : Context.LocalClientCertificate; - } - } - - public MSI.MonoTlsConnectionInfo GetConnectionInfo () - { - CheckThrow (true); - return Context.ConnectionInfo; - } - public SSA.CipherAlgorithmType CipherAlgorithm { get { CheckThrow (true); - var info = Context.ConnectionInfo; + var info = GetConnectionInfo (); if (info == null) return SSA.CipherAlgorithmType.None; switch (info.CipherAlgorithmType) { @@ -861,7 +830,7 @@ namespace Mono.Net.Security public SSA.HashAlgorithmType HashAlgorithm { get { CheckThrow (true); - var info = Context.ConnectionInfo; + var info = GetConnectionInfo (); if (info == null) return SSA.HashAlgorithmType.None; switch (info.HashAlgorithmType) { @@ -883,7 +852,7 @@ namespace Mono.Net.Security public SSA.ExchangeAlgorithmType KeyExchangeAlgorithm { get { CheckThrow (true); - var info = Context.ConnectionInfo; + var info = GetConnectionInfo (); if (info == null) return SSA.ExchangeAlgorithmType.None; switch (info.ExchangeAlgorithmType) { @@ -898,7 +867,7 @@ namespace Mono.Net.Security } } - #region Need to Implement +#region Need to Implement public int CipherStrength { get { throw new NotImplementedException (); @@ -920,7 +889,7 @@ namespace Mono.Net.Security } } - #endregion +#endregion } } #endif diff --git a/mcs/class/System/Mono.Net.Security/MobileTlsContext.cs b/mcs/class/System/Mono.Net.Security/MobileTlsContext.cs index 79f0468d461..b4ba1c014ca 100644 --- a/mcs/class/System/Mono.Net.Security/MobileTlsContext.cs +++ b/mcs/class/System/Mono.Net.Security/MobileTlsContext.cs @@ -169,7 +169,7 @@ namespace Mono.Net.Security public abstract int Write (byte[] buffer, int offset, int count, out bool wantMore); - public abstract void Close (); + public abstract void Shutdown (); protected bool ValidateCertificate (X509Certificate leaf, X509Chain chain) { diff --git a/mcs/class/System/ReferenceSources/SR2.cs b/mcs/class/System/ReferenceSources/SR2.cs index cfcd038e9b5..896362d98a4 100644 --- a/mcs/class/System/ReferenceSources/SR2.cs +++ b/mcs/class/System/ReferenceSources/SR2.cs @@ -9,6 +9,8 @@ partial class SR { public const string mono_net_io_shutdown = "mono_net_io_shutdown"; public const string mono_net_io_renegotiate = "mono_net_io_renegotiate"; + + public const string net_ssl_io_already_shutdown = "Write operations are not allowed after the channel was shutdown."; public const string net_log_set_socketoption_reuseport_default_on = "net_log_set_socketoption_reuseport_default_on"; public const string net_log_set_socketoption_reuseport_not_supported = "net_log_set_socketoption_reuseport_not_supported"; diff --git a/mcs/class/System/System.Net.Security/SslStream.cs b/mcs/class/System/System.Net.Security/SslStream.cs index 6d61e08dfb1..c0b702fde1b 100644 --- a/mcs/class/System/System.Net.Security/SslStream.cs +++ b/mcs/class/System/System.Net.Security/SslStream.cs @@ -223,6 +223,11 @@ namespace System.Net.Security return Impl.AuthenticateAsServerAsync (serverCertificate, clientCertificateRequired, enabledSslProtocols, checkCertificateRevocation); } + public virtual Task ShutdownAsync () + { + return Impl.ShutdownAsync (); + } + public override bool IsAuthenticated { get { return Impl.IsAuthenticated; } } diff --git a/mcs/class/System/System.Net.Security/SslStream.platformnotsupported.cs b/mcs/class/System/System.Net.Security/SslStream.platformnotsupported.cs index 06fefad105a..1c2a4167301 100644 --- a/mcs/class/System/System.Net.Security/SslStream.platformnotsupported.cs +++ b/mcs/class/System/System.Net.Security/SslStream.platformnotsupported.cs @@ -152,6 +152,11 @@ namespace System.Net.Security throw new PlatformNotSupportedException (EXCEPTION_MESSAGE); } + public virtual Task ShutdownAsync () + { + throw new PlatformNotSupportedException (EXCEPTION_MESSAGE); + } + public override bool IsAuthenticated { get { throw new PlatformNotSupportedException (EXCEPTION_MESSAGE); } } diff --git a/mcs/class/System/common.sources b/mcs/class/System/common.sources index f20e501c687..a38c2ff9c48 100644 --- a/mcs/class/System/common.sources +++ b/mcs/class/System/common.sources @@ -276,6 +276,7 @@ ReferenceSources/SettingsSectionInternal.cs ReferenceSources/SecureStringHelper.cs ReferenceSources/Socket.cs ReferenceSources/SR.cs +ReferenceSources/SR2.cs ReferenceSources/SRCategoryAttribute.cs ReferenceSources/Win32Exception.cs diff --git a/mcs/class/System/net_4_x_System.dll.sources b/mcs/class/System/net_4_x_System.dll.sources index 46b53b43052..110199b4bcd 100644 --- a/mcs/class/System/net_4_x_System.dll.sources +++ b/mcs/class/System/net_4_x_System.dll.sources @@ -245,7 +245,6 @@ Mono.Net.Dns/ResolverError.cs Mono.Net.Dns/SimpleResolverEventArgs.cs ReferenceSources/BinaryCompatibility.cs ReferenceSources/ConfigurationManagerInternalFactory.cs -ReferenceSources/SR2.cs ../referencesource/System/misc/PrivilegedConfigurationManager.cs diff --git a/mono/btls/btls-bio.c b/mono/btls/btls-bio.c index ea8fda67dc3..016175bea3b 100644 --- a/mono/btls/btls-bio.c +++ b/mono/btls/btls-bio.c @@ -39,8 +39,10 @@ mono_read (BIO *bio, char *out, int outl) ret = mono->read_func (mono->instance, out, outl, &wantMore); - if (ret < 0) + if (ret < 0) { + errno = EIO; return -1; + } if (ret > 0) return ret; diff --git a/mono/btls/btls-ssl.c b/mono/btls/btls-ssl.c index eca323ce2ae..17291b5c585 100644 --- a/mono/btls/btls-ssl.c +++ b/mono/btls/btls-ssl.c @@ -60,6 +60,18 @@ mono_btls_ssl_close (MonoBtlsSsl *ptr) ; } +MONO_API int +mono_btls_ssl_shutdown (MonoBtlsSsl *ptr) +{ + return SSL_shutdown (ptr->ssl); +} + +MONO_API void +mono_btls_ssl_set_quiet_shutdown (MonoBtlsSsl *ptr, int mode) +{ + SSL_set_quiet_shutdown (ptr->ssl, mode); +} + MONO_API void mono_btls_ssl_set_bio (MonoBtlsSsl *ptr, BIO *bio) { diff --git a/mono/btls/btls-ssl.h b/mono/btls/btls-ssl.h index 98b017340e1..ac93c114995 100644 --- a/mono/btls/btls-ssl.h +++ b/mono/btls/btls-ssl.h @@ -69,6 +69,12 @@ void mono_btls_ssl_close (MonoBtlsSsl *ptr); int +mono_btls_ssl_shutdown (MonoBtlsSsl *ptr); + +MONO_API void +mono_btls_ssl_set_quiet_shutdown (MonoBtlsSsl *ptr, int mode); + +int mono_btls_ssl_get_error (MonoBtlsSsl *ptr, int ret_code); int |