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