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