95ecb8fd64a2d1e8a4a59296eba02dd660a86bea
[mono.git] / mcs / class / System / Mono.Net.Security / MobileAuthenticatedStream.cs
1 //
2 // MobileAuthenticatedStream.cs
3 //
4 // Author:
5 //       Martin Baulig <martin.baulig@xamarin.com>
6 //
7 // Copyright (c) 2015 Xamarin, Inc.
8 //
9
10 #if SECURITY_DEP
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
13 #endif
14
15 #if MONO_SECURITY_ALIAS
16 using MSI = MonoSecurity::Mono.Security.Interface;
17 #else
18 using MSI = Mono.Security.Interface;
19 #endif
20
21 using System;
22 using System.IO;
23 using System.Net;
24 using System.Net.Security;
25 using System.Globalization;
26 using System.Security.Authentication;
27 using System.Runtime.ExceptionServices;
28 using System.Threading;
29 using System.Threading.Tasks;
30 using System.Security.Cryptography.X509Certificates;
31
32 using SD = System.Diagnostics;
33 using SSA = System.Security.Authentication;
34 using SslProtocols = System.Security.Authentication.SslProtocols;
35
36 namespace Mono.Net.Security
37 {
38         abstract class MobileAuthenticatedStream : AuthenticatedStream, MSI.IMonoSslStream
39         {
40                 /*
41                  * This is intentionally called `xobileTlsContext'.  It is a "dangerous" object
42                  * that must not be touched outside the `ioLock' and we need to be very careful
43                  * where we access it.
44                  */
45                 MobileTlsContext xobileTlsContext;
46                 ExceptionDispatchInfo lastException;
47
48                 AsyncProtocolRequest asyncHandshakeRequest;
49                 AsyncProtocolRequest asyncReadRequest;
50                 AsyncProtocolRequest asyncWriteRequest;
51                 BufferOffsetSize2 readBuffer;
52                 BufferOffsetSize2 writeBuffer;
53
54                 object ioLock = new object ();
55                 int closeRequested;
56                 bool shutdown;
57
58                 static int uniqueNameInteger = 123;
59
60                 public MobileAuthenticatedStream (Stream innerStream, bool leaveInnerStreamOpen, SslStream owner,
61                                                   MSI.MonoTlsSettings settings, MSI.MonoTlsProvider provider)
62                         : base (innerStream, leaveInnerStreamOpen)
63                 {
64                         SslStream = owner;
65                         Settings = settings;
66                         Provider = provider;
67
68                         readBuffer = new BufferOffsetSize2 (16834);
69                         writeBuffer = new BufferOffsetSize2 (16384);
70                 }
71
72                 public SslStream SslStream {
73                         get;
74                 }
75
76                 public MSI.MonoTlsSettings Settings {
77                         get;
78                 }
79
80                 public MSI.MonoTlsProvider Provider {
81                         get;
82                 }
83
84                 internal bool HasContext {
85                         get { return xobileTlsContext != null; }
86                 }
87
88                 internal void CheckThrow (bool authSuccessCheck, bool shutdownCheck = false)
89                 {
90                         if (lastException != null)
91                                 lastException.Throw ();
92                         if (authSuccessCheck && !IsAuthenticated)
93                                 throw new InvalidOperationException (SR.net_auth_noauth);
94                         if (shutdownCheck && shutdown)
95                                 throw new InvalidOperationException (SR.net_ssl_io_already_shutdown);
96                 }
97
98                 internal static Exception GetSSPIException (Exception e)
99                 {
100                         if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException)
101                                 return e;
102                         return new AuthenticationException (SR.net_auth_SSPI, e);
103                 }
104
105                 internal static Exception GetIOException (Exception e, string message)
106                 {
107                         if (e is OperationCanceledException || e is IOException || e is ObjectDisposedException || e is AuthenticationException)
108                                 return e;
109                         return new IOException (message, e);
110                 }
111
112                 internal ExceptionDispatchInfo SetException (Exception e)
113                 {
114                         var info = ExceptionDispatchInfo.Capture (e);
115                         var old = Interlocked.CompareExchange (ref lastException, info, null);
116                         return old ?? info;
117                 }
118
119                 SslProtocols DefaultProtocols {
120                         get { return SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; }
121                 }
122
123                 enum OperationType {
124                         Read,
125                         Write,
126                         Shutdown
127                 }
128
129                 public void AuthenticateAsClient (string targetHost)
130                 {
131                         AuthenticateAsClient (targetHost, new X509CertificateCollection (), DefaultProtocols, false);
132                 }
133
134                 public void AuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
135                 {
136                         var task = ProcessAuthentication (true, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
137                         task.Wait ();
138                 }
139
140                 public IAsyncResult BeginAuthenticateAsClient (string targetHost, AsyncCallback asyncCallback, object asyncState)
141                 {
142                         return BeginAuthenticateAsClient (targetHost, new X509CertificateCollection (), DefaultProtocols, false, asyncCallback, asyncState);
143                 }
144
145                 public IAsyncResult BeginAuthenticateAsClient (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
146                 {
147                         var task = ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
148                         return TaskToApm.Begin (task, asyncCallback, asyncState);
149                 }
150
151                 public void EndAuthenticateAsClient (IAsyncResult asyncResult)
152                 {
153                         TaskToApm.End (asyncResult);
154                 }
155
156                 public void AuthenticateAsServer (X509Certificate serverCertificate)
157                 {
158                         AuthenticateAsServer (serverCertificate, false, DefaultProtocols, false);
159                 }
160
161                 public void AuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
162                 {
163                         var task = ProcessAuthentication (true, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
164                         task.Wait ();
165                 }
166
167                 public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, AsyncCallback asyncCallback, object asyncState)
168                 {
169                         return BeginAuthenticateAsServer (serverCertificate, false, DefaultProtocols, false, asyncCallback, asyncState);
170                 }
171
172                 public IAsyncResult BeginAuthenticateAsServer (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation, AsyncCallback asyncCallback, object asyncState)
173                 {
174                         var task = ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
175                         return TaskToApm.Begin (task, asyncCallback, asyncState);
176                 }
177
178                 public void EndAuthenticateAsServer (IAsyncResult asyncResult)
179                 {
180                         TaskToApm.End (asyncResult);
181                 }
182
183                 public Task AuthenticateAsClientAsync (string targetHost)
184                 {
185                         return ProcessAuthentication (false, false, targetHost, DefaultProtocols, null, null, false);
186                 }
187
188                 public Task AuthenticateAsClientAsync (string targetHost, X509CertificateCollection clientCertificates, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
189                 {
190                         return ProcessAuthentication (false, false, targetHost, enabledSslProtocols, null, clientCertificates, false);
191                 }
192
193                 public Task AuthenticateAsServerAsync (X509Certificate serverCertificate)
194                 {
195                         return AuthenticateAsServerAsync (serverCertificate, false, DefaultProtocols, false);
196                 }
197
198                 public Task AuthenticateAsServerAsync (X509Certificate serverCertificate, bool clientCertificateRequired, SslProtocols enabledSslProtocols, bool checkCertificateRevocation)
199                 {
200                         return ProcessAuthentication (false, true, string.Empty, enabledSslProtocols, serverCertificate, null, clientCertificateRequired);
201                 }
202
203                 public Task ShutdownAsync ()
204                 {
205                         Debug ("ShutdownAsync");
206
207                         /*
208                          * SSLClose() is a little bit tricky as it might attempt to send a close_notify alert
209                          * and thus call our write callback.
210                          *
211                          * It is also not thread-safe with SSLRead() or SSLWrite(), so we need to take the I/O lock here.
212                          */
213                         var asyncRequest = new AsyncShutdownRequest (this);
214                         var task = StartOperation (OperationType.Shutdown, asyncRequest, CancellationToken.None);
215                         return task;
216                 }
217
218                 public AuthenticatedStream AuthenticatedStream {
219                         get { return this; }
220                 }
221
222                 async Task ProcessAuthentication (
223                         bool runSynchronously, bool serverMode, string targetHost, SslProtocols enabledProtocols,
224                         X509Certificate serverCertificate, X509CertificateCollection clientCertificates, bool clientCertRequired)
225                 {
226                         if (serverMode) {
227                                 if (serverCertificate == null)
228                                         throw new ArgumentException (nameof (serverCertificate));
229                         } else {
230                                 if (targetHost == null)
231                                         throw new ArgumentException (nameof (targetHost));
232                                 if (targetHost.Length == 0)
233                                         targetHost = "?" + Interlocked.Increment (ref uniqueNameInteger).ToString (NumberFormatInfo.InvariantInfo);
234                         }
235
236                         if (lastException != null)
237                                 lastException.Throw ();
238
239                         var asyncRequest = new AsyncHandshakeRequest (this, runSynchronously);
240                         if (Interlocked.CompareExchange (ref asyncHandshakeRequest, asyncRequest, null) != null)
241                                 throw new InvalidOperationException ("Invalid nested call.");
242                         // Make sure no other async requests can be started during the handshake.
243                         if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null)
244                                 throw new InvalidOperationException ("Invalid nested call.");
245                         if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null)
246                                 throw new InvalidOperationException ("Invalid nested call.");
247
248                         AsyncProtocolResult result;
249
250                         try {
251                                 lock (ioLock) {
252                                         if (xobileTlsContext != null)
253                                                 throw new InvalidOperationException ();
254                                         readBuffer.Reset ();
255                                         writeBuffer.Reset ();
256
257                                         xobileTlsContext = CreateContext (
258                                                 serverMode, targetHost, enabledProtocols, serverCertificate,
259                                                 clientCertificates, clientCertRequired);
260                                 }
261
262                                 try {
263                                         result = await asyncRequest.StartOperation (CancellationToken.None).ConfigureAwait (false);
264                                 } catch (Exception ex) {
265                                         result = new AsyncProtocolResult (SetException (GetSSPIException (ex)));
266                                 }
267                         } finally {
268                                 lock (ioLock) {
269                                         readBuffer.Reset ();
270                                         writeBuffer.Reset ();
271                                         asyncWriteRequest = null;
272                                         asyncReadRequest = null;
273                                         asyncHandshakeRequest = null;
274                                 }
275                         }
276
277                         if (result.Error != null)
278                                 result.Error.Throw ();
279                 }
280
281                 protected abstract MobileTlsContext CreateContext (
282                         bool serverMode, string targetHost, SSA.SslProtocols enabledProtocols,
283                         X509Certificate serverCertificate, X509CertificateCollection clientCertificates,
284                         bool askForClientCert);
285
286                 public override IAsyncResult BeginRead (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
287                 {
288                         var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count);
289                         var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None);
290                         return TaskToApm.Begin (task, asyncCallback, asyncState);
291                 }
292
293                 public override int EndRead (IAsyncResult asyncResult)
294                 {
295                         return TaskToApm.End<int> (asyncResult);
296                 }
297
298                 public override IAsyncResult BeginWrite (byte[] buffer, int offset, int count, AsyncCallback asyncCallback, object asyncState)
299                 {
300                         var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count);
301                         var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None);
302                         return TaskToApm.Begin (task, asyncCallback, asyncState);
303                 }
304
305                 public override void EndWrite (IAsyncResult asyncResult)
306                 {
307                         TaskToApm.End (asyncResult);
308                 }
309
310                 public override int Read (byte[] buffer, int offset, int count)
311                 {
312                         var asyncRequest = new AsyncReadRequest (this, true, buffer, offset, count);
313                         var task = StartOperation (OperationType.Read, asyncRequest, CancellationToken.None);
314                         return task.Result;
315                 }
316
317                 public void Write (byte[] buffer)
318                 {
319                         Write (buffer, 0, buffer.Length);
320                 }
321
322                 public override void Write (byte[] buffer, int offset, int count)
323                 {
324                         var asyncRequest = new AsyncWriteRequest (this, true, buffer, offset, count);
325                         var task = StartOperation (OperationType.Write, asyncRequest, CancellationToken.None);
326                         task.Wait ();
327                 }
328
329                 public override Task<int> ReadAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
330                 {
331                         var asyncRequest = new AsyncReadRequest (this, false, buffer, offset, count);
332                         return StartOperation (OperationType.Read, asyncRequest, cancellationToken);
333                 }
334
335                 public override Task WriteAsync (byte[] buffer, int offset, int count, CancellationToken cancellationToken)
336                 {
337                         var asyncRequest = new AsyncWriteRequest (this, false, buffer, offset, count);
338                         return StartOperation (OperationType.Write, asyncRequest, cancellationToken);
339                 }
340
341                 async Task<int> StartOperation (OperationType type, AsyncProtocolRequest asyncRequest, CancellationToken cancellationToken)
342                 {
343                         CheckThrow (true, type != OperationType.Read);
344                         Debug ("StartOperationAsync: {0} {1}", asyncRequest, type);
345
346                         if (type == OperationType.Read) {
347                                 if (Interlocked.CompareExchange (ref asyncReadRequest, asyncRequest, null) != null)
348                                         throw new InvalidOperationException ("Invalid nested call.");
349                         } else {
350                                 if (Interlocked.CompareExchange (ref asyncWriteRequest, asyncRequest, null) != null)
351                                         throw new InvalidOperationException ("Invalid nested call.");
352                         }
353
354                         AsyncProtocolResult result;
355
356                         try {
357                                 lock (ioLock) {
358                                         if (type == OperationType.Read)
359                                                 readBuffer.Reset ();
360                                         else
361                                                 writeBuffer.Reset ();
362                                 }
363                                 result = await asyncRequest.StartOperation (cancellationToken).ConfigureAwait (false);
364                         } catch (Exception e) {
365                                 var info = SetException (GetIOException (e, asyncRequest.Name + " failed"));
366                                 result = new AsyncProtocolResult (info);
367                         } finally {
368                                 lock (ioLock) {
369                                         if (type == OperationType.Read) {
370                                                 readBuffer.Reset ();
371                                                 asyncReadRequest = null;
372                                         } else {
373                                                 writeBuffer.Reset ();
374                                                 asyncWriteRequest = null;
375                                         }
376                                 }
377                         }
378
379                         if (result.Error != null)
380                                 result.Error.Throw ();
381                         return result.UserResult;
382                 }
383
384                 static int nextId;
385                 internal readonly int ID = ++nextId;
386
387                 [SD.Conditional ("MONO_TLS_DEBUG")]
388                 protected internal void Debug (string message, params object[] args)
389                 {
390                         MonoTlsProviderFactory.Debug ("MobileAuthenticatedStream({0}): {1}", ID, string.Format (message, args));
391                 }
392
393 #region Called back from native code via SslConnection
394
395                 /*
396                  * Called from within SSLRead() and SSLHandshake().  We only access tha managed byte[] here.
397                  */
398                 internal int InternalRead (byte[] buffer, int offset, int size, out bool outWantMore)
399                 {
400                         try {
401                                 Debug ("InternalRead: {0} {1} {2} {3} {4}", offset, size,
402                                        asyncHandshakeRequest != null ? "handshake" : "",
403                                        asyncReadRequest != null ? "async" : "",
404                                        readBuffer != null ? readBuffer.ToString () : "");
405                                 var asyncRequest = asyncHandshakeRequest ?? asyncReadRequest;
406                                 var (ret, wantMore) = InternalRead (asyncRequest, readBuffer, buffer, offset, size);
407                                 outWantMore = wantMore;
408                                 return ret;
409                         } catch (Exception ex) {
410                                 Debug ("InternalRead failed: {0}", ex);
411                                 SetException (GetIOException (ex, "InternalRead() failed"));
412                                 outWantMore = false;
413                                 return -1;
414                         }
415                 }
416
417                 (int, bool) InternalRead (AsyncProtocolRequest asyncRequest, BufferOffsetSize internalBuffer, byte[] buffer, int offset, int size)
418                 {
419                         if (asyncRequest == null)
420                                 throw new InvalidOperationException ();
421
422                         Debug ("InternalRead: {0} {1} {2}", internalBuffer, offset, size);
423
424                         /*
425                          * One of Apple's native functions wants to read 'size' bytes of data.
426                          *
427                          * First, we check whether we already have enough in the internal buffer.
428                          *
429                          * If the internal buffer is empty (it will be the first time we're called), we save
430                          * the amount of bytes that were requested and return 'SslStatus.WouldBlock' to our
431                          * native caller.  This native function will then return this code to managed code,
432                          * where we read the requested amount of data into the internal buffer, then call the
433                          * native function again.
434                          */
435                         if (internalBuffer.Size == 0 && !internalBuffer.Complete) {
436                                 Debug ("InternalRead #1: {0} {1} {2}", internalBuffer.Offset, internalBuffer.TotalBytes, size);
437                                 internalBuffer.Offset = internalBuffer.Size = 0;
438                                 asyncRequest.RequestRead (size);
439                                 return (0, true);
440                         }
441
442                         /*
443                          * The second time we're called, the native buffer will contain the exact amount of data that the
444                          * previous call requested from us, so we should be able to return it all here.  However, just in
445                          * case that Apple's native function changed its mind, we can also return less.
446                          *
447                          * In either case, if we have any data buffered, then we return as much of it as possible - if the
448                          * native code isn't satisfied, then it will call us again to request more.
449                          */
450                         var len = System.Math.Min (internalBuffer.Size, size);
451                         Buffer.BlockCopy (internalBuffer.Buffer, internalBuffer.Offset, buffer, offset, len);
452                         internalBuffer.Offset += len;
453                         internalBuffer.Size -= len;
454                         return (len, !internalBuffer.Complete && len < size);
455                 }
456
457                 /*
458                  * We may get called from SSLWrite(), SSLHandshake() or SSLClose().
459                  */
460                 internal bool InternalWrite (byte[] buffer, int offset, int size)
461                 {
462                         try {
463                                 Debug ("InternalWrite: {0} {1}", offset, size);
464                                 var asyncRequest = asyncHandshakeRequest ?? asyncWriteRequest;
465                                 return InternalWrite (asyncRequest, writeBuffer, buffer, offset, size);
466                         } catch (Exception ex) {
467                                 Debug ("InternalWrite failed: {0}", ex);
468                                 SetException (GetIOException (ex, "InternalWrite() failed"));
469                                 return false;
470                         }
471                 }
472
473                 bool InternalWrite (AsyncProtocolRequest asyncRequest, BufferOffsetSize2 internalBuffer, byte[] buffer, int offset, int size)
474                 {
475                         Debug ("InternalWrite: {0} {1} {2} {3}", asyncRequest != null, internalBuffer, offset, size);
476
477                         if (asyncRequest == null) {
478                                 /*
479                                  * The only situation where 'asyncRequest' could possibly be 'null' is when we're called
480                                  * from within SSLClose() - which might attempt to send the close_notity notification.
481                                  * Since this notification message is very small, it should definitely fit into our internal
482                                  * buffer, so we just save it in there and after SSLClose() returns, the final call to
483                                  * InternalFlush() - just before closing the underlying stream - will send it out.
484                                  */
485                                 if (lastException != null)
486                                         return false;
487
488                                 if (Interlocked.Exchange (ref closeRequested, 1) == 0)
489                                         internalBuffer.Reset ();
490                                 else if (internalBuffer.Remaining == 0)
491                                         throw new InvalidOperationException ();
492                         }
493
494                         /*
495                          * Normal write - can be either SSLWrite() or SSLHandshake().
496                          *
497                          * It is important that we always accept all the data and queue it.
498                          */
499
500                         internalBuffer.AppendData (buffer, offset, size);
501
502                         /*
503                          * Calling 'asyncRequest.RequestWrite()' here ensures that ProcessWrite() is called next
504                          * time we regain control from native code.
505                          *
506                          * During the handshake, the native code won't actually realize (unless if attempts to send
507                          * so much that the write buffer gets full) that we only buffered the data.
508                          *
509                          * However, it doesn't matter because it will either return with a completed handshake
510                          * (and doesn't care whether the remote actually received the data) or it will expect more
511                          * data from the remote and request a read.  In either case, we regain control in managed
512                          * code and can flush out the data.
513                          *
514                          * Note that a calling RequestWrite() followed by RequestRead() will first flush the write
515                          * queue once we return to managed code - before attempting to read anything.
516                          */
517                         if (asyncRequest != null)
518                                 asyncRequest.RequestWrite ();
519
520                         return true;
521                 }
522
523 #endregion
524
525 #region Inner Stream
526
527                 /*
528                  * Read / write data from the inner stream; we're only called from managed code and only manipulate
529                  * the internal buffers.
530                  */
531                 internal async Task<int> InnerRead (bool sync, int requestedSize, CancellationToken cancellationToken)
532                 {
533                         cancellationToken.ThrowIfCancellationRequested ();
534                         Debug ("InnerRead: {0} {1} {2} {3} {4}", sync, readBuffer.Offset, readBuffer.Size, readBuffer.Remaining, requestedSize);
535
536                         var len = System.Math.Min (readBuffer.Remaining, requestedSize);
537                         if (len == 0)
538                                 throw new InvalidOperationException ();
539
540                         Task<int> task;
541                         if (sync)
542                                 task = Task.Run (() => InnerStream.Read (readBuffer.Buffer, readBuffer.EndOffset, len));
543                         else
544                                 task = InnerStream.ReadAsync (readBuffer.Buffer, readBuffer.EndOffset, len, cancellationToken);
545
546                         var ret = await task.ConfigureAwait (false);
547                         Debug ("InnerRead done: {0} {1} - {2}", readBuffer.Remaining, len, ret);
548
549                         if (ret >= 0) {
550                                 readBuffer.Size += ret;
551                                 readBuffer.TotalBytes += ret;
552                         }
553
554                         if (ret == 0) {
555                                 readBuffer.Complete = true;
556                                 Debug ("InnerRead - end of stream!");
557                                 /*
558                                  * Try to distinguish between a graceful close - first Read() returned 0 - and
559                                  * the remote prematurely closing the connection without sending us all data.
560                                  */
561                                 if (readBuffer.TotalBytes > 0)
562                                         ret = -1;
563                         }
564
565                         Debug ("InnerRead done: {0} - {1} {2}", readBuffer, len, ret);
566                         return ret;
567                 }
568
569                 internal async Task InnerWrite (bool sync, CancellationToken cancellationToken)
570                 {
571                         cancellationToken.ThrowIfCancellationRequested ();
572                         Debug ("InnerWrite: {0} {1}", writeBuffer.Offset, writeBuffer.Size);
573
574                         if (writeBuffer.Size == 0)
575                                 return;
576
577                         Task task;
578                         if (sync)
579                                 task = Task.Run (() => InnerStream.Write (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size));
580                         else
581                                 task = InnerStream.WriteAsync (writeBuffer.Buffer, writeBuffer.Offset, writeBuffer.Size);
582
583                         await task.ConfigureAwait (false);
584
585                         writeBuffer.TotalBytes += writeBuffer.Size;
586                         writeBuffer.Offset = writeBuffer.Size = 0;
587                 }
588
589 #endregion
590
591 #region Main async I/O loop
592
593                 internal AsyncOperationStatus ProcessHandshake (AsyncOperationStatus status)
594                 {
595                         Debug ("ProcessHandshake: {0}", status);
596
597                         lock (ioLock) {
598                                 /*
599                                  * The first time we're called (AsyncOperationStatus.Initialize), we need to setup the SslContext and
600                                  * start the handshake.
601                                 */
602                                 if (status == AsyncOperationStatus.Initialize) {
603                                         xobileTlsContext.StartHandshake ();
604                                         return AsyncOperationStatus.Continue;
605                                 } else if (status == AsyncOperationStatus.ReadDone) {
606                                         throw new IOException (SR.net_auth_eof);
607                                 } else if (status != AsyncOperationStatus.Continue) {
608                                         throw new InvalidOperationException ();
609                                 }
610
611                                 /*
612                                  * SSLHandshake() will return repeatedly with 'SslStatus.WouldBlock', we then need
613                                  * to take care of I/O and call it again.
614                                 */
615                                 var newStatus = AsyncOperationStatus.Continue;
616                                 if (xobileTlsContext.ProcessHandshake ()) {
617                                         xobileTlsContext.FinishHandshake ();
618                                         newStatus = AsyncOperationStatus.Complete;
619                                 }
620
621                                 if (lastException != null)
622                                         lastException.Throw ();
623
624                                 return newStatus;
625                         }
626                 }
627
628                 internal (int, bool) ProcessRead (BufferOffsetSize userBuffer)
629                 {
630                         lock (ioLock) {
631                                 // This operates on the internal buffer and will never block.
632                                 var ret = xobileTlsContext.Read (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size);
633                                 if (lastException != null)
634                                         lastException.Throw ();
635                                 return ret;
636                         }
637                 }
638
639                 internal (int, bool) ProcessWrite (BufferOffsetSize userBuffer)
640                 {
641                         lock (ioLock) {
642                                 // This operates on the internal buffer and will never block.
643                                 var ret = xobileTlsContext.Write (userBuffer.Buffer, userBuffer.Offset, userBuffer.Size);
644                                 if (lastException != null)
645                                         lastException.Throw ();
646                                 return ret;
647                         }
648                 }
649
650                 internal AsyncOperationStatus ProcessShutdown (AsyncOperationStatus status)
651                 {
652                         Debug ("ProcessShutdown: {0}", status);
653
654                         lock (ioLock) {
655                                 xobileTlsContext.Shutdown ();
656                                 shutdown = true;
657                                 return AsyncOperationStatus.Complete;
658                         }
659                 }
660
661 #endregion
662
663                 public override bool IsServer {
664                         get {
665                                 CheckThrow (false);
666                                 return xobileTlsContext != null && xobileTlsContext.IsServer;
667                         }
668                 }
669
670                 public override bool IsAuthenticated {
671                         get {
672                                 lock (ioLock) {
673                                         // Don't use CheckThrow(), we want to return false if we're not authenticated.
674                                         return xobileTlsContext != null && lastException == null && xobileTlsContext.IsAuthenticated;
675                                 }
676                         }
677                 }
678
679                 public override bool IsMutuallyAuthenticated {
680                         get {
681                                 lock (ioLock) {
682                                         // Don't use CheckThrow() here.
683                                         if (!IsAuthenticated)
684                                                 return false;
685                                         if ((xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate) == null)
686                                                 return false;
687                                         return xobileTlsContext.IsRemoteCertificateAvailable;
688                                 }
689                         }
690                 }
691
692                 protected override void Dispose (bool disposing)
693                 {
694                         try {
695                                 lock (ioLock) {
696                                         Debug ("Dispose: {0}", xobileTlsContext != null);
697                                         lastException = ExceptionDispatchInfo.Capture (new ObjectDisposedException ("MobileAuthenticatedStream"));
698                                         if (xobileTlsContext != null) {
699                                                 xobileTlsContext.Dispose ();
700                                                 xobileTlsContext = null;
701                                         }
702                                 }
703                         } finally {
704                                 base.Dispose (disposing);
705                         }
706                 }
707
708                 public override void Flush ()
709                 {
710                         // Write() automatically flushes the underlying stream.
711                 }
712
713                 public SslProtocols SslProtocol {
714                         get {
715                                 lock (ioLock) {
716                                         CheckThrow (true);
717                                         return (SslProtocols)xobileTlsContext.NegotiatedProtocol;
718                                 }
719                         }
720                 }
721
722                 public X509Certificate RemoteCertificate {
723                         get {
724                                 lock (ioLock) {
725                                         CheckThrow (true);
726                                         return xobileTlsContext.RemoteCertificate;
727                                 }
728                         }
729                 }
730
731                 public X509Certificate LocalCertificate {
732                         get {
733                                 lock (ioLock) {
734                                         CheckThrow (true);
735                                         return InternalLocalCertificate;
736                                 }
737                         }
738                 }
739
740                 public X509Certificate InternalLocalCertificate {
741                         get {
742                                 lock (ioLock) {
743                                         CheckThrow (false);
744                                         if (xobileTlsContext == null)
745                                                 return null;
746                                         return xobileTlsContext.IsServer ? xobileTlsContext.LocalServerCertificate : xobileTlsContext.LocalClientCertificate;
747                                 }
748                         }
749                 }
750
751                 public MSI.MonoTlsConnectionInfo GetConnectionInfo ()
752                 {
753                         lock (ioLock) {
754                                 CheckThrow (true);
755                                 return xobileTlsContext.ConnectionInfo;
756                         }
757                 }
758
759                 //
760                 // 'xobileTlsContext' must not be accessed below this point.
761                 //
762
763                 public override long Seek (long offset, SeekOrigin origin)
764                 {
765                         throw new NotSupportedException ();
766                 }
767
768                 public override void SetLength (long value)
769                 {
770                         InnerStream.SetLength (value);
771                 }
772
773                 public TransportContext TransportContext {
774                         get { throw new NotSupportedException (); }
775                 }
776
777                 public override bool CanRead {
778                         get { return IsAuthenticated && InnerStream.CanRead; }
779                 }
780
781                 public override bool CanTimeout {
782                         get { return InnerStream.CanTimeout; }
783                 }
784
785                 public override bool CanWrite {
786                         get { return IsAuthenticated & InnerStream.CanWrite && !shutdown; }
787                 }
788
789                 public override bool CanSeek {
790                         get { return false; }
791                 }
792
793                 public override long Length {
794                         get { return InnerStream.Length; }
795                 }
796
797                 public override long Position {
798                         get { return InnerStream.Position; }
799                         set { throw new NotSupportedException (); }
800                 }
801
802                 public override bool IsEncrypted {
803                         get { return IsAuthenticated; }
804                 }
805
806                 public override bool IsSigned {
807                         get { return IsAuthenticated; }
808                 }
809
810                 public override int ReadTimeout {
811                         get { return InnerStream.ReadTimeout; }
812                         set { InnerStream.ReadTimeout = value; }
813                 }
814
815                 public override int WriteTimeout {
816                         get { return InnerStream.WriteTimeout; }
817                         set { InnerStream.WriteTimeout = value; }
818                 }
819
820                 public SSA.CipherAlgorithmType CipherAlgorithm {
821                         get {
822                                 CheckThrow (true);
823                                 var info = GetConnectionInfo ();
824                                 if (info == null)
825                                         return SSA.CipherAlgorithmType.None;
826                                 switch (info.CipherAlgorithmType) {
827                                 case MSI.CipherAlgorithmType.Aes128:
828                                 case MSI.CipherAlgorithmType.AesGcm128:
829                                         return SSA.CipherAlgorithmType.Aes128;
830                                 case MSI.CipherAlgorithmType.Aes256:
831                                 case MSI.CipherAlgorithmType.AesGcm256:
832                                         return SSA.CipherAlgorithmType.Aes256;
833                                 default:
834                                         return SSA.CipherAlgorithmType.None;
835                                 }
836                         }
837                 }
838
839                 public SSA.HashAlgorithmType HashAlgorithm {
840                         get {
841                                 CheckThrow (true);
842                                 var info = GetConnectionInfo ();
843                                 if (info == null)
844                                         return SSA.HashAlgorithmType.None;
845                                 switch (info.HashAlgorithmType) {
846                                 case MSI.HashAlgorithmType.Md5:
847                                 case MSI.HashAlgorithmType.Md5Sha1:
848                                         return SSA.HashAlgorithmType.Md5;
849                                 case MSI.HashAlgorithmType.Sha1:
850                                 case MSI.HashAlgorithmType.Sha224:
851                                 case MSI.HashAlgorithmType.Sha256:
852                                 case MSI.HashAlgorithmType.Sha384:
853                                 case MSI.HashAlgorithmType.Sha512:
854                                         return SSA.HashAlgorithmType.Sha1;
855                                 default:
856                                         return SSA.HashAlgorithmType.None;
857                                 }
858                         }
859                 }
860
861                 public SSA.ExchangeAlgorithmType KeyExchangeAlgorithm {
862                         get {
863                                 CheckThrow (true);
864                                 var info = GetConnectionInfo ();
865                                 if (info == null)
866                                         return SSA.ExchangeAlgorithmType.None;
867                                 switch (info.ExchangeAlgorithmType) {
868                                 case MSI.ExchangeAlgorithmType.Rsa:
869                                         return SSA.ExchangeAlgorithmType.RsaSign;
870                                 case MSI.ExchangeAlgorithmType.Dhe:
871                                 case MSI.ExchangeAlgorithmType.EcDhe:
872                                         return SSA.ExchangeAlgorithmType.DiffieHellman;
873                                 default:
874                                         return SSA.ExchangeAlgorithmType.None;
875                                 }
876                         }
877                 }
878
879 #region Need to Implement
880                 public int CipherStrength {
881                         get {
882                                 throw new NotImplementedException ();
883                         }
884                 }
885                 public int HashStrength {
886                         get {
887                                 throw new NotImplementedException ();
888                         }
889                 }
890                 public int KeyExchangeStrength {
891                         get {
892                                 throw new NotImplementedException ();
893                         }
894                 }
895                 public bool CheckCertRevocationStatus {
896                         get {
897                                 throw new NotImplementedException ();
898                         }
899                 }
900
901 #endregion
902         }
903 }
904 #endif