bb42f79e6ee38fc1979c95963c16e77771fc57d3
[mono.git] / mcs / class / System / Mono.AppleTls / AppleTlsContext.cs
1 #if SECURITY_DEP && MONO_FEATURE_APPLETLS
2 //
3 // AppleTlsContext.cs
4 //
5 // Author:
6 //       Martin Baulig <martin.baulig@xamarin.com>
7 //
8 // Copyright (c) 2015 Xamarin, Inc.
9 //
10
11 #if MONO_SECURITY_ALIAS
12 extern alias MonoSecurity;
13 #endif
14
15 using System;
16 using System.IO;
17 using System.Net;
18 using System.Text;
19 using System.Globalization;
20 using System.Collections;
21 using System.Collections.Generic;
22 using System.Threading;
23 using System.Threading.Tasks;
24 using System.Runtime.InteropServices;
25 using SSA = System.Security.Authentication;
26 using System.Security.Cryptography.X509Certificates;
27
28 #if MONO_SECURITY_ALIAS
29 using MonoSecurity::Mono.Security.Interface;
30 #else
31 using Mono.Security.Interface;
32 #endif
33
34 using Mono.Net;
35 using Mono.Net.Security;
36
37 using ObjCRuntimeInternal;
38
39 namespace Mono.AppleTls
40 {
41         class AppleTlsContext : MobileTlsContext
42         {
43                 public const string SecurityLibrary = "/System/Library/Frameworks/Security.framework/Security";
44
45                 GCHandle handle;
46                 IntPtr context;
47
48                 SslReadFunc readFunc;
49                 SslWriteFunc writeFunc;
50
51                 SecIdentity serverIdentity;
52                 SecIdentity clientIdentity;
53
54                 X509Certificate remoteCertificate;
55                 X509Certificate localClientCertificate;
56                 MonoTlsConnectionInfo connectionInfo;
57                 bool havePeerTrust;
58                 bool isAuthenticated;
59                 bool handshakeFinished;
60                 int handshakeStarted;
61
62                 bool closed;
63                 bool disposed;
64                 bool closedGraceful;
65                 int pendingIO;
66
67                 Exception lastException;
68
69                 public AppleTlsContext (
70                         MobileAuthenticatedStream parent, bool serverMode, string targetHost,
71                         SSA.SslProtocols enabledProtocols, X509Certificate serverCertificate,
72                         X509CertificateCollection clientCertificates, bool askForClientCert)
73                         : base (parent, serverMode, targetHost, enabledProtocols,
74                                 serverCertificate, clientCertificates, askForClientCert)
75                 {
76                         handle = GCHandle.Alloc (this, GCHandleType.Weak);
77                         readFunc = NativeReadCallback;
78                         writeFunc = NativeWriteCallback;
79
80                         if (IsServer) {
81                                 if (serverCertificate == null)
82                                         throw new ArgumentNullException ("serverCertificate");
83                         }
84                 }
85
86                 public IntPtr Handle {
87                         get {
88                                 if (!HasContext)
89                                         throw new ObjectDisposedException ("AppleTlsContext");
90                                 return context;
91                         }
92                 }
93
94                 public override bool HasContext {
95                         get { return !disposed && context != IntPtr.Zero; }
96                 }
97
98                 [System.Diagnostics.Conditional ("APPLE_TLS_DEBUG")]
99                 protected new void Debug (string message, params object[] args)
100                 {
101                         Console.Error.WriteLine ("MobileTlsStream({0}): {1}", Parent.ID, string.Format (message, args));
102                 }
103
104                 void CheckStatusAndThrow (SslStatus status, params SslStatus[] acceptable)
105                 {
106                         var last = Interlocked.Exchange (ref lastException, null);
107                         if (last != null)
108                                 throw last;
109
110                         if (status == SslStatus.Success || Array.IndexOf (acceptable, status) > -1)
111                                 return;
112
113                         switch (status) {
114                         case SslStatus.ClosedAbort:
115                                 throw new IOException ("Connection closed.");
116
117                         case SslStatus.BadCert:
118                                 throw new TlsException (AlertDescription.BadCertificate);
119
120                         case SslStatus.UnknownRootCert:
121                         case SslStatus.NoRootCert:
122                         case SslStatus.XCertChainInvalid:
123                                 throw new TlsException (AlertDescription.CertificateUnknown, status.ToString ());
124
125                         case SslStatus.CertExpired:
126                         case SslStatus.CertNotYetValid:
127                                 throw new TlsException (AlertDescription.CertificateExpired);
128
129                         case SslStatus.Protocol:
130                                 throw new TlsException (AlertDescription.ProtocolVersion);
131
132                         default:
133                                 throw new TlsException (AlertDescription.InternalError, "Unknown Secure Transport error `{0}'.", status);
134                         }
135                 }
136
137                 #region Handshake
138
139                 public override bool IsAuthenticated {
140                         get { return isAuthenticated; }
141                 }
142
143                 public override void StartHandshake ()
144                 {
145                         Debug ("StartHandshake: {0}", IsServer);
146
147                         if (Interlocked.CompareExchange (ref handshakeStarted, 1, 1) != 0)
148                                 throw new InvalidOperationException ();
149
150                         InitializeConnection ();
151
152                         SetSessionOption (SslSessionOption.BreakOnCertRequested, true);
153                         SetSessionOption (SslSessionOption.BreakOnClientAuth, true);
154                         SetSessionOption (SslSessionOption.BreakOnServerAuth, true);
155
156                         if (IsServer) {
157                                 SecCertificate[] intermediateCerts;
158                                 serverIdentity = AppleCertificateHelper.GetIdentity (LocalServerCertificate, out intermediateCerts);
159                                 if (serverIdentity == null)
160                                         throw new SSA.AuthenticationException ("Unable to get server certificate from keychain.");
161
162                                 SetCertificate (serverIdentity, intermediateCerts);
163                                 for (int i = 0; i < intermediateCerts.Length; i++)
164                                         intermediateCerts [i].Dispose ();
165                         }
166                 }
167
168                 public override void FinishHandshake ()
169                 {
170                         InitializeSession ();
171
172                         isAuthenticated = true;
173                 }
174
175                 public override void Flush ()
176                 {
177                 }
178
179                 public override bool ProcessHandshake ()
180                 {
181                         if (handshakeFinished)
182                                 throw new NotSupportedException ("Handshake already finished.");
183
184                         while (true) {
185                                 lastException = null;
186                                 var status = SSLHandshake (Handle);
187                                 Debug ("Handshake: {0} - {0:x}", status);
188
189                                 CheckStatusAndThrow (status, SslStatus.WouldBlock, SslStatus.PeerAuthCompleted, SslStatus.PeerClientCertRequested);
190
191                                 if (status == SslStatus.PeerAuthCompleted) {
192                                         RequirePeerTrust ();
193                                 } else if (status == SslStatus.PeerClientCertRequested) {
194                                         RequirePeerTrust ();
195                                         if (remoteCertificate == null)
196                                                 throw new TlsException (AlertDescription.InternalError, "Cannot request client certificate before receiving one from the server.");
197                                         localClientCertificate = SelectClientCertificate (remoteCertificate, null);
198                                         if (localClientCertificate == null)
199                                                 continue;
200                                         clientIdentity = AppleCertificateHelper.GetIdentity (localClientCertificate);
201                                         if (clientIdentity == null)
202                                                 throw new TlsException (AlertDescription.CertificateUnknown);
203                                         SetCertificate (clientIdentity, new SecCertificate [0]);
204                                 } else if (status == SslStatus.WouldBlock) {
205                                         return false;
206                                 } else if (status == SslStatus.Success) {
207                                         handshakeFinished = true;
208                                         return true;
209                                 }
210                         }
211                 }
212
213                 void RequirePeerTrust ()
214                 {
215                         if (!havePeerTrust) {
216                                 EvaluateTrust ();
217                                 havePeerTrust = true;
218                         }
219                 }
220
221                 void EvaluateTrust ()
222                 {
223                         InitializeSession ();
224
225                         /*
226                          * We're using .NET's SslStream semantics here.
227                          * 
228                          * A server must always provide a valid certificate.
229                          * 
230                          * However, in server mode, "ask for client certificate" means that
231                          * we ask the client to provide a certificate, then invoke the client
232                          * certificate validator - passing 'null' if the client didn't provide
233                          * any.
234                          * 
235                          */
236
237                         var trust = GetPeerTrust (!IsServer);
238                         X509CertificateCollection certificates;
239
240                         if (trust == null || trust.Count == 0) {
241                                 remoteCertificate = null;
242                                 if (!IsServer)
243                                         throw new TlsException (AlertDescription.CertificateUnknown);
244                                 certificates = null;
245                         } else {
246                                 if (trust.Count > 1)
247                                         Debug ("WARNING: Got multiple certificates in SecTrust!");
248
249                                 certificates = new X509CertificateCollection ();
250                                 for (int i = 0; i < trust.Count; i++)
251                                         certificates.Add (trust [(IntPtr)i].ToX509Certificate ());
252
253                                 remoteCertificate = certificates [0];
254                                 Debug ("Got peer trust: {0}", remoteCertificate);
255                         }
256
257                         bool ok;
258                         try {
259                                 ok = ValidateCertificate (certificates);
260                         } catch (Exception ex) {
261                                 Debug ("Certificate validation failed: {0}", ex);
262                                 throw new TlsException (AlertDescription.CertificateUnknown, "Certificate validation threw exception.");
263                         }
264
265                         if (!ok)
266                                 throw new TlsException (AlertDescription.CertificateUnknown);
267                 }
268
269                 void InitializeConnection ()
270                 {
271                         context = SSLCreateContext (IntPtr.Zero, IsServer ? SslProtocolSide.Server : SslProtocolSide.Client, SslConnectionType.Stream);
272
273                         var result = SSLSetIOFuncs (Handle, readFunc, writeFunc);
274                         CheckStatusAndThrow (result);
275
276                         result = SSLSetConnection (Handle, GCHandle.ToIntPtr (handle));
277                         CheckStatusAndThrow (result);
278
279                         if ((EnabledProtocols & SSA.SslProtocols.Tls) != 0)
280                                 MinProtocol = SslProtocol.Tls_1_0;
281                         else if ((EnabledProtocols & SSA.SslProtocols.Tls11) != 0)
282                                 MinProtocol = SslProtocol.Tls_1_1;
283                         else
284                                 MinProtocol = SslProtocol.Tls_1_2;
285
286                         if ((EnabledProtocols & SSA.SslProtocols.Tls12) != 0)
287                                 MaxProtocol = SslProtocol.Tls_1_2;
288                         else if ((EnabledProtocols & SSA.SslProtocols.Tls11) != 0)
289                                 MaxProtocol = SslProtocol.Tls_1_1;
290                         else
291                                 MaxProtocol = SslProtocol.Tls_1_0;
292
293 #if APPLE_TLS_DEBUG
294                         foreach (var c in GetSupportedCiphers ())
295                                 Debug ("  {0} SslCipherSuite.{1} {2:x} {3}", IsServer ? "Server" : "Client", c, (int)c, (CipherSuiteCode)c);
296 #endif
297
298                         if (Settings != null && Settings.EnabledCiphers != null) {
299                                 SslCipherSuite [] ciphers = new SslCipherSuite [Settings.EnabledCiphers.Length];
300                                 for (int i = 0 ; i < Settings.EnabledCiphers.Length; ++i)
301                                         ciphers [i] = (SslCipherSuite)Settings.EnabledCiphers[i];
302                                 SetEnabledCiphers (ciphers);
303                         }
304
305                         if (AskForClientCertificate)
306                                 SetClientSideAuthenticate (SslAuthenticate.Try);
307
308                         IPAddress address;
309                         if (!IsServer && !string.IsNullOrEmpty (TargetHost) &&
310                             !IPAddress.TryParse (TargetHost, out address)) {
311                                 PeerDomainName = ServerName;
312                         }
313                 }
314
315                 void InitializeSession ()
316                 {
317                         if (connectionInfo != null)
318                                 return;
319
320                         var cipher = NegotiatedCipher;
321                         var protocol = GetNegotiatedProtocolVersion ();
322                         Debug ("GET CONNECTION INFO: {0:x}:{0} {1:x}:{1} {2}", cipher, protocol, (TlsProtocolCode)protocol);
323
324                         connectionInfo = new MonoTlsConnectionInfo {
325                                 CipherSuiteCode = (CipherSuiteCode)cipher,
326                                 ProtocolVersion = GetProtocol (protocol),
327                                 PeerDomainName = PeerDomainName
328                         };
329                 }
330
331                 static TlsProtocols GetProtocol (SslProtocol protocol)
332                 {
333                         switch (protocol) {
334                         case SslProtocol.Tls_1_0:
335                                 return TlsProtocols.Tls10;
336                         case SslProtocol.Tls_1_1:
337                                 return TlsProtocols.Tls11;
338                         case SslProtocol.Tls_1_2:
339                                 return TlsProtocols.Tls12;
340                         default:
341                                 throw new NotSupportedException ();
342                         }
343                 }
344
345                 public override MonoTlsConnectionInfo ConnectionInfo {
346                         get { return connectionInfo; }
347                 }
348
349                 internal override bool IsRemoteCertificateAvailable {
350                         get { return remoteCertificate != null; }
351                 }
352
353                 internal override X509Certificate LocalClientCertificate {
354                         get { return localClientCertificate; }
355                 }
356
357                 public override X509Certificate RemoteCertificate {
358                         get { return remoteCertificate; }
359                 }
360
361                 public override TlsProtocols NegotiatedProtocol {
362                         get { return connectionInfo.ProtocolVersion; }
363                 }
364
365                 #endregion
366
367                 #region General P/Invokes
368
369                 [DllImport (SecurityLibrary )]
370                 extern static /* OSStatus */ SslStatus SSLGetProtocolVersionMax (/* SSLContextRef */ IntPtr context, out SslProtocol maxVersion);
371
372                 [DllImport (SecurityLibrary)]
373                 extern static /* OSStatus */ SslStatus SSLSetProtocolVersionMax (/* SSLContextRef */ IntPtr context, SslProtocol maxVersion);
374
375                 public SslProtocol MaxProtocol {
376                         get {
377                                 SslProtocol value;
378                                 var result = SSLGetProtocolVersionMax (Handle, out value);
379                                 CheckStatusAndThrow (result);
380                                 return value;
381                         }
382                         set {
383                                 var result = SSLSetProtocolVersionMax (Handle, value);
384                                 CheckStatusAndThrow (result);
385                         }
386                 }
387
388                 [DllImport (SecurityLibrary)]
389                 extern static /* OSStatus */ SslStatus SSLGetProtocolVersionMin (/* SSLContextRef */ IntPtr context, out SslProtocol minVersion);
390
391                 [DllImport (SecurityLibrary)]
392                 extern static /* OSStatus */ SslStatus SSLSetProtocolVersionMin (/* SSLContextRef */ IntPtr context, SslProtocol minVersion);
393
394                 public SslProtocol MinProtocol {
395                         get {
396                                 SslProtocol value;
397                                 var result = SSLGetProtocolVersionMin (Handle, out value);
398                                 CheckStatusAndThrow (result);
399                                 return value;
400                         }
401                         set {
402                                 var result = SSLSetProtocolVersionMin (Handle, value);
403                                 CheckStatusAndThrow (result);
404                         }
405                 }
406
407                 [DllImport (SecurityLibrary)]
408                 extern static /* OSStatus */ SslStatus SSLGetNegotiatedProtocolVersion (/* SSLContextRef */ IntPtr context, out SslProtocol protocol);
409
410                 public SslProtocol GetNegotiatedProtocolVersion ()
411                 {
412                         SslProtocol value;
413                         var result = SSLGetNegotiatedProtocolVersion (Handle, out value);
414                         CheckStatusAndThrow (result);
415                         return value;
416                 }
417
418                 [DllImport (SecurityLibrary)]
419                 extern static /* OSStatus */ SslStatus SSLGetSessionOption (/* SSLContextRef */ IntPtr context, SslSessionOption option, out bool value);
420
421                 public bool GetSessionOption (SslSessionOption option)
422                 {
423                         bool value;
424                         var result = SSLGetSessionOption (Handle, option, out value);
425                         CheckStatusAndThrow (result);
426                         return value;
427                 }
428
429                 [DllImport (SecurityLibrary)]
430                 extern static /* OSStatus */ SslStatus SSLSetSessionOption (/* SSLContextRef */ IntPtr context, SslSessionOption option, bool value);
431
432                 public void SetSessionOption (SslSessionOption option, bool value)
433                 {
434                         var result = SSLSetSessionOption (Handle, option, value);
435                         CheckStatusAndThrow (result);
436                 }
437
438                 [DllImport (SecurityLibrary)]
439                 extern static /* OSStatus */ SslStatus SSLSetClientSideAuthenticate (/* SSLContextRef */ IntPtr context, SslAuthenticate auth);
440
441                 public void SetClientSideAuthenticate (SslAuthenticate auth)
442                 {
443                         var result = SSLSetClientSideAuthenticate (Handle, auth);
444                         CheckStatusAndThrow (result);
445                 }
446
447                 [DllImport (SecurityLibrary)]
448                 extern static /* OSStatus */ SslStatus SSLHandshake (/* SSLContextRef */ IntPtr context);
449
450                 [DllImport (SecurityLibrary)]
451                 extern static /* OSStatus */ SslStatus SSLGetSessionState (/* SSLContextRef */ IntPtr context, ref SslSessionState state);
452
453                 public SslSessionState SessionState {
454                         get {
455                                 var value = SslSessionState.Invalid;
456                                 var result = SSLGetSessionState (Handle, ref value);
457                                 CheckStatusAndThrow (result);
458                                 return value;
459                         }
460                 }
461
462                 [DllImport (SecurityLibrary)]
463                 extern unsafe static /* OSStatus */ SslStatus SSLGetPeerID (/* SSLContextRef */ IntPtr context, /* const void** */ out IntPtr peerID, /* size_t* */ out IntPtr peerIDLen);
464
465                 [DllImport (SecurityLibrary)]
466                 extern unsafe static /* OSStatus */ SslStatus SSLSetPeerID (/* SSLContextRef */ IntPtr context, /* const void* */ byte* peerID, /* size_t */ IntPtr peerIDLen);
467
468                 public unsafe byte[] PeerId {
469                         get {
470                                 IntPtr length;
471                                 IntPtr id;
472                                 var result = SSLGetPeerID (Handle, out id, out length);
473                                 CheckStatusAndThrow (result);
474                                 if ((result != SslStatus.Success) || ((int)length == 0))
475                                         return null;
476                                 var data = new byte [(int)length];
477                                 Marshal.Copy (id, data, 0, (int) length);
478                                 return data;
479                         }
480                         set {
481                                 SslStatus result;
482                                 IntPtr length = (value == null) ? IntPtr.Zero : (IntPtr)value.Length;
483                                 fixed (byte *p = value) {
484                                         result = SSLSetPeerID (Handle, p, length);
485                                 }
486                                 CheckStatusAndThrow (result);
487                         }
488                 }
489
490                 [DllImport (SecurityLibrary)]
491                 extern unsafe static /* OSStatus */ SslStatus SSLGetBufferedReadSize (/* SSLContextRef */ IntPtr context, /* size_t* */ out IntPtr bufSize);
492
493                 public IntPtr BufferedReadSize {
494                         get {
495                                 IntPtr value;
496                                 var result = SSLGetBufferedReadSize (Handle, out value);
497                                 CheckStatusAndThrow (result);
498                                 return value;
499                         }
500                 }
501
502                 [DllImport (SecurityLibrary)]
503                 extern unsafe static /* OSStatus */ SslStatus SSLGetNumberSupportedCiphers (/* SSLContextRef */ IntPtr context, /* size_t* */ out IntPtr numCiphers);
504
505                 [DllImport (SecurityLibrary)]
506                 extern unsafe static /* OSStatus */ SslStatus SSLGetSupportedCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref IntPtr numCiphers);
507
508                 public unsafe IList<SslCipherSuite> GetSupportedCiphers ()
509                 {
510                         IntPtr n;
511                         var result = SSLGetNumberSupportedCiphers (Handle, out n);
512                         CheckStatusAndThrow (result);
513                         if ((result != SslStatus.Success) || ((int)n <= 0))
514                                 return null;
515
516                         var ciphers = new SslCipherSuite [(int)n];
517                         fixed (SslCipherSuite *p = ciphers) {
518                                 result = SSLGetSupportedCiphers (Handle, p, ref n);
519                         }
520                         CheckStatusAndThrow (result);
521                         return ciphers;
522                 }
523
524                 [DllImport (SecurityLibrary)]
525                 extern unsafe static /* OSStatus */ SslStatus SSLGetNumberEnabledCiphers (/* SSLContextRef */ IntPtr context, /* size_t* */ out IntPtr numCiphers);
526
527                 [DllImport (SecurityLibrary)]
528                 extern unsafe static /* OSStatus */ SslStatus SSLGetEnabledCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t* */ ref IntPtr numCiphers);
529
530                 public unsafe IList<SslCipherSuite> GetEnabledCiphers ()
531                 {
532                         IntPtr n;
533                         var result = SSLGetNumberEnabledCiphers (Handle, out n);
534                         CheckStatusAndThrow (result);
535                         if ((result != SslStatus.Success) || ((int)n <= 0))
536                                 return null;
537
538                         var ciphers = new SslCipherSuite [(int)n];
539                         fixed (SslCipherSuite *p = ciphers) {
540                                 result = SSLGetEnabledCiphers (Handle, p, ref n);
541                         }
542                         CheckStatusAndThrow (result);
543                         return ciphers;
544                 }
545
546                 [DllImport (SecurityLibrary)]
547                 extern unsafe static /* OSStatus */ SslStatus SSLSetEnabledCiphers (/* SSLContextRef */ IntPtr context, SslCipherSuite *ciphers, /* size_t */ IntPtr numCiphers);
548
549                 public unsafe void SetEnabledCiphers (SslCipherSuite [] ciphers)
550                 {
551                         if (ciphers == null)
552                                 throw new ArgumentNullException ("ciphers");
553
554                         SslStatus result;
555
556                         fixed (SslCipherSuite *p = ciphers)
557                                 result = SSLSetEnabledCiphers (Handle, p, (IntPtr)ciphers.Length);
558                         CheckStatusAndThrow (result);
559                 }
560
561                 [DllImport (SecurityLibrary)]
562                 extern unsafe static /* OSStatus */ SslStatus SSLGetNegotiatedCipher (/* SSLContextRef */ IntPtr context, /* SslCipherSuite* */ out SslCipherSuite cipherSuite);
563
564                 public SslCipherSuite NegotiatedCipher {
565                         get {
566                                 SslCipherSuite value;
567                                 var result = SSLGetNegotiatedCipher (Handle, out value);
568                                 CheckStatusAndThrow (result);
569                                 return value;
570                         }
571                 }
572
573                 [DllImport (SecurityLibrary)]
574                 extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainNameLength (/* SSLContextRef */ IntPtr context, /* size_t* */ out IntPtr peerNameLen);
575
576                 [DllImport (SecurityLibrary)]
577                 extern unsafe static /* OSStatus */ SslStatus SSLGetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[] peerName, /* size_t */ ref IntPtr peerNameLen);
578
579                 [DllImport (SecurityLibrary)]
580                 extern unsafe static /* OSStatus */ SslStatus SSLSetPeerDomainName (/* SSLContextRef */ IntPtr context, /* char* */ byte[] peerName, /* size_t */ IntPtr peerNameLen);
581
582                 public string PeerDomainName {
583                         get {
584                                 IntPtr length;
585                                 var result = SSLGetPeerDomainNameLength (Handle, out length);
586                                 CheckStatusAndThrow (result);
587                                 if (result != SslStatus.Success || (int)length == 0)
588                                         return String.Empty;
589                                 var bytes = new byte [(int)length];
590                                 result = SSLGetPeerDomainName (Handle, bytes, ref length);
591                                 CheckStatusAndThrow (result);
592
593                                 int peerDomainLength = (int)length;
594
595                                 if (result != SslStatus.Success)
596                                         return string.Empty;
597                                 if (peerDomainLength > 0 && bytes [peerDomainLength-1] == 0)
598                                         peerDomainLength = peerDomainLength - 1;
599                                 return Encoding.UTF8.GetString (bytes, 0, peerDomainLength);
600                         }
601                         set {
602                                 SslStatus result;
603                                 if (value == null) {
604                                         result = SSLSetPeerDomainName (Handle, null, (IntPtr)0);
605                                 } else {
606                                         var bytes = Encoding.UTF8.GetBytes (value);
607                                         result = SSLSetPeerDomainName (Handle, bytes, (IntPtr)bytes.Length);
608                                 }
609                                 CheckStatusAndThrow (result);
610                         }
611                 }
612
613                 [DllImport (SecurityLibrary)]
614                 extern unsafe static /* OSStatus */ SslStatus SSLSetCertificate (/* SSLContextRef */ IntPtr context, /* CFArrayRef */ IntPtr certRefs);
615
616                 CFArray Bundle (SecIdentity identity, IEnumerable<SecCertificate> certificates)
617                 {
618                         if (identity == null)
619                                 throw new ArgumentNullException ("identity");
620                         int i = 0;
621
622                         int n = 0;
623                         if (certificates != null) {
624                                 foreach (var obj in certificates)
625                                         n++;
626                         }
627
628                         var ptrs = new IntPtr [n + 1];
629                         ptrs [0] = identity.Handle;
630                         foreach (var certificate in certificates)
631                                 ptrs [++i] = certificate.Handle;
632                         return CFArray.CreateArray (ptrs);
633                 }
634
635                 public void SetCertificate (SecIdentity identify, IEnumerable<SecCertificate> certificates)
636                 {
637                         using (var array = Bundle (identify, certificates)) {
638                                 var result = SSLSetCertificate (Handle, array.Handle);
639                                 CheckStatusAndThrow (result);
640                         }
641                 }
642
643                 [DllImport (SecurityLibrary)]
644                 extern unsafe static /* OSStatus */ SslStatus SSLGetClientCertificateState (/* SSLContextRef */ IntPtr context, out SslClientCertificateState clientState);
645
646                 public SslClientCertificateState ClientCertificateState {
647                         get {
648                                 SslClientCertificateState value;
649                                 var result = SSLGetClientCertificateState (Handle, out value);
650                                 CheckStatusAndThrow (result);
651                                 return value;
652                         }
653                 }
654
655                 [DllImport (SecurityLibrary)]
656                 extern unsafe static /* OSStatus */ SslStatus SSLCopyPeerTrust (/* SSLContextRef */ IntPtr context, /* SecTrustRef */ out IntPtr trust);
657
658                 public SecTrust GetPeerTrust (bool requireTrust)
659                 {
660                         IntPtr value;
661                         var result = SSLCopyPeerTrust (Handle, out value);
662                         if (requireTrust) {
663                                 CheckStatusAndThrow (result);
664                                 if (value == IntPtr.Zero)
665                                         throw new TlsException (AlertDescription.CertificateUnknown);
666                         }
667                         return (value == IntPtr.Zero) ? null : new SecTrust (value, true);
668                 }
669
670                 #endregion
671
672                 #region IO Functions
673
674                 [DllImport (SecurityLibrary)]
675                 extern static /* SSLContextRef */ IntPtr SSLCreateContext (/* CFAllocatorRef */ IntPtr alloc, SslProtocolSide protocolSide, SslConnectionType connectionType);
676
677                 [DllImport (SecurityLibrary)]
678                 extern static /* OSStatus */ SslStatus SSLSetConnection (/* SSLContextRef */ IntPtr context, /* SSLConnectionRef */ IntPtr connection);
679
680                 [DllImport (SecurityLibrary)]
681                 extern static /* OSStatus */ SslStatus SSLSetIOFuncs (/* SSLContextRef */ IntPtr context, /* SSLReadFunc */ SslReadFunc readFunc, /* SSLWriteFunc */ SslWriteFunc writeFunc);
682
683                 [Mono.Util.MonoPInvokeCallback (typeof (SslReadFunc))]
684                 static SslStatus NativeReadCallback (IntPtr ptr, IntPtr data, ref IntPtr dataLength)
685                 {
686                         AppleTlsContext context = null;
687                         try {
688                                 var weakHandle = GCHandle.FromIntPtr (ptr);
689                                 if (!weakHandle.IsAllocated)
690                                         return SslStatus.Internal;
691
692                                 context = (AppleTlsContext) weakHandle.Target;
693                                 if (context == null || context.disposed)
694                                         return SslStatus.ClosedAbort;
695
696                                 return context.NativeReadCallback (data, ref dataLength);
697                         } catch (Exception ex) {
698                                 if (context != null && context.lastException == null)
699                                         context.lastException = ex;
700                                 return SslStatus.Internal;
701                         }
702                 }
703
704                 [Mono.Util.MonoPInvokeCallback (typeof (SslWriteFunc))]
705                 static SslStatus NativeWriteCallback (IntPtr ptr, IntPtr data, ref IntPtr dataLength)
706                 {
707                         AppleTlsContext context = null;
708                         try {
709                                 var weakHandle = GCHandle.FromIntPtr (ptr);
710                                 if (!weakHandle.IsAllocated)
711                                         return SslStatus.Internal;
712
713                                 context = (AppleTlsContext) weakHandle.Target;
714                                 if (context == null || context.disposed)
715                                         return SslStatus.ClosedAbort;
716
717                                 return context.NativeWriteCallback (data, ref dataLength);
718                         } catch (Exception ex) {
719                                 if (context != null && context.lastException == null)
720                                         context.lastException = ex;
721                                 return SslStatus.Internal;
722                         }
723                 }
724
725                 SslStatus NativeReadCallback (IntPtr data, ref IntPtr dataLength)
726                 {
727                         if (closed || disposed || Parent == null)
728                                 return SslStatus.ClosedAbort;
729
730                         var len = (int)dataLength;
731                         var readBuffer = new byte [len];
732
733                         Debug ("NativeReadCallback: {0} {1}", dataLength, len);
734
735                         bool wantMore;
736                         var ret = Parent.InternalRead (readBuffer, 0, len, out wantMore);
737                         dataLength = (IntPtr)ret;
738
739                         Debug ("NativeReadCallback #1: {0} - {1} {2}", len, ret, wantMore);
740
741                         if (ret < 0)
742                                 return SslStatus.ClosedAbort;
743
744                         Marshal.Copy (readBuffer, 0, data, ret);
745
746                         if (ret > 0)
747                                 return SslStatus.Success;
748                         else if (wantMore)
749                                 return SslStatus.WouldBlock;
750                         else if (ret == 0) {
751                                 closedGraceful = true;
752                                 return SslStatus.ClosedGraceful;
753                         } else {
754                                 return SslStatus.Success;
755                         }
756                 }
757
758                 SslStatus NativeWriteCallback (IntPtr data, ref IntPtr dataLength)
759                 {
760                         if (closed || disposed || Parent == null)
761                                 return SslStatus.ClosedAbort;
762
763                         var len = (int)dataLength;
764                         var writeBuffer = new byte [len];
765
766                         Marshal.Copy (data, writeBuffer, 0, len);
767
768                         Debug ("NativeWriteCallback: {0}", len);
769
770                         var ok = Parent.InternalWrite (writeBuffer, 0, len);
771
772                         Debug ("NativeWriteCallback done: {0} {1}", len, ok);
773
774                         return ok ? SslStatus.Success : SslStatus.ClosedAbort;
775                 }
776
777                 [DllImport (SecurityLibrary)]
778                 extern unsafe static /* OSStatus */ SslStatus SSLRead (/* SSLContextRef */ IntPtr context, /* const void* */ byte* data, /* size_t */ IntPtr dataLength, /* size_t* */ out IntPtr processed);
779
780                 public override unsafe int Read (byte[] buffer, int offset, int count, out bool wantMore)
781                 {
782                         if (Interlocked.Exchange (ref pendingIO, 1) == 1)
783                                 throw new InvalidOperationException ();
784
785                         Debug ("Read: {0},{1}", offset, count);
786
787                         lastException = null;
788
789                         try {
790                                 IntPtr processed;
791                                 SslStatus status;
792
793                                 fixed (byte *d = &buffer [offset])
794                                         status = SSLRead (Handle, d, (IntPtr)count, out processed);
795
796                                 Debug ("Read done: {0} {1} {2}", status, count, processed);
797
798                                 if (closedGraceful && (status == SslStatus.ClosedAbort || status == SslStatus.ClosedGraceful)) {
799                                         /*
800                                          * This is really ugly, but unfortunately SSLRead() also returns 'SslStatus.ClosedAbort'
801                                          * when the first inner Read() returns 0.  MobileAuthenticatedStream.InnerRead() attempts
802                                          * to distinguish between a graceful close and abnormal termination of connection.
803                                          */
804                                         wantMore = false;
805                                         return 0;
806                                 }
807
808                                 CheckStatusAndThrow (status, SslStatus.WouldBlock, SslStatus.ClosedGraceful);
809                                 wantMore = status == SslStatus.WouldBlock;
810                                 return (int)processed;
811                         } catch (Exception ex) {
812                                 Debug ("Read error: {0}", ex);
813                                 throw;
814                         } finally {
815                                 pendingIO = 0;
816                         }
817                 }
818
819                 [DllImport (SecurityLibrary)]
820                 extern unsafe static /* OSStatus */ SslStatus SSLWrite (/* SSLContextRef */ IntPtr context, /* const void* */ byte* data, /* size_t */ IntPtr dataLength, /* size_t* */ out IntPtr processed);
821
822                 public override unsafe int Write (byte[] buffer, int offset, int count, out bool wantMore)
823                 {
824                         if (Interlocked.Exchange (ref pendingIO, 1) == 1)
825                                 throw new InvalidOperationException ();
826
827                         Debug ("Write: {0},{1}", offset, count);
828
829                         lastException = null;
830
831                         try {
832                                 SslStatus status = SslStatus.ClosedAbort;
833                                 IntPtr processed = (IntPtr)(-1);
834
835                                 fixed (byte *d = &buffer [offset])
836                                         status = SSLWrite (Handle, d, (IntPtr)count, out processed);
837
838                                 Debug ("Write done: {0} {1}", status, processed);
839
840                                 CheckStatusAndThrow (status, SslStatus.WouldBlock);
841
842                                 wantMore = status == SslStatus.WouldBlock;
843                                 return (int)processed;
844                         } finally {
845                                 pendingIO = 0;
846                         }
847                 }
848
849                 [DllImport (SecurityLibrary)]
850                 extern static /* OSStatus */ SslStatus SSLClose (/* SSLContextRef */ IntPtr context);
851
852                 public override void Shutdown ()
853                 {
854                         if (Interlocked.Exchange (ref pendingIO, 1) == 1)
855                                 throw new InvalidOperationException ();
856
857                         Debug ("Shutdown");
858
859                         lastException = null;
860
861                         try {
862                                 if (closed || disposed)
863                                         return;
864
865                                 var status = SSLClose (Handle);
866                                 Debug ("Shutdown done: {0}", status);
867                                 CheckStatusAndThrow (status);
868                         } finally {
869                                 closed = true;
870                                 pendingIO = 0;
871                         }
872                 }
873
874                 #endregion
875
876                 protected override void Dispose (bool disposing)
877                 {
878                         try {
879                                 if (disposed)
880                                         return;
881                                 if (disposing) {
882                                         disposed = true;
883                                         if (serverIdentity != null) {
884                                                 serverIdentity.Dispose ();
885                                                 serverIdentity = null;
886                                         }
887                                         if (clientIdentity != null) {
888                                                 clientIdentity.Dispose ();
889                                                 clientIdentity = null;
890                                         }
891                                         if (remoteCertificate != null) {
892                                                 remoteCertificate.Dispose ();
893                                                 remoteCertificate = null;
894                                         }
895                                 }
896                         } finally {
897                                 disposed = true;
898                                 if (context != IntPtr.Zero) {
899                                         CFObject.CFRelease (context);
900                                         context = IntPtr.Zero;
901                                 }
902                                 base.Dispose (disposing);
903                         }
904                 }
905         }
906 }
907 #endif