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