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