Imported Upstream version 5.0.0.42

Former-commit-id: fd56571888259555122d8a0f58c68838229cea2b
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2017-04-10 11:41:01 +00:00
parent 1190d13a04
commit 6bdd276d05
19939 changed files with 3099680 additions and 93811 deletions

View File

@@ -0,0 +1,106 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// AppleCertificateHelper.cs
//
// Author:
// Martin Baulig <martin.baulig@xamarin.com>
//
// Copyright (c) 2015 Xamarin, Inc.
//
#if MONO_SECURITY_ALIAS
extern alias MonoSecurity;
#endif
using System;
using System.Collections;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
#if MONO_SECURITY_ALIAS
using MonoSecurity::Mono.Security.Interface;
#else
using Mono.Security.Interface;
#endif
namespace Mono.AppleTls
{
static class AppleCertificateHelper
{
public static SecIdentity GetIdentity (X509Certificate certificate)
{
/*
* If we got an 'X509Certificate2', then we require it to have a private key
* and import it.
*/
var certificate2 = certificate as X509Certificate2;
if (certificate2 != null)
return SecIdentity.Import (certificate2);
/*
* Otherwise, we require the private key to be in the keychain.
*/
using (var secCert = new SecCertificate (certificate)) {
return SecKeyChain.FindIdentity (secCert, true);
}
}
public static SecIdentity GetIdentity (X509Certificate certificate, out SecCertificate[] intermediateCerts)
{
var identity = GetIdentity (certificate);
var impl2 = certificate.Impl as X509Certificate2Impl;
if (impl2 == null || impl2.IntermediateCertificates == null) {
intermediateCerts = new SecCertificate [0];
return identity;
}
try {
intermediateCerts = new SecCertificate [impl2.IntermediateCertificates.Count];
for (int i = 0; i < intermediateCerts.Length; i++)
intermediateCerts [i] = new SecCertificate (impl2.IntermediateCertificates [i]);
return identity;
} catch {
identity.Dispose ();
throw;
}
}
public static bool InvokeSystemCertificateValidator (
ICertificateValidator2 validator, string targetHost, bool serverMode,
X509CertificateCollection certificates,
ref MonoSslPolicyErrors errors, ref int status11)
{
if (certificates == null) {
errors |= MonoSslPolicyErrors.RemoteCertificateNotAvailable;
return false;
}
if (!string.IsNullOrEmpty (targetHost)) {
var pos = targetHost.IndexOf (':');
if (pos > 0)
targetHost = targetHost.Substring (0, pos);
}
var policy = SecPolicy.CreateSslPolicy (!serverMode, targetHost);
var trust = new SecTrust (certificates, policy);
if (validator.Settings.TrustAnchors != null) {
var status = trust.SetAnchorCertificates (validator.Settings.TrustAnchors);
if (status != SecStatusCode.Success)
throw new InvalidOperationException (status.ToString ());
trust.SetAnchorCertificatesOnly (false);
}
var result = trust.Evaluate ();
if (result == SecTrustResult.Unspecified)
return true;
errors |= MonoSslPolicyErrors.RemoteCertificateChainErrors;
return false;
}
}
}
#endif

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,77 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// AppleTlsProvider.cs
//
// Author:
// Martin Baulig <martin.baulig@xamarin.com>
//
// Copyright (c) 2015 Xamarin, Inc.
//
#if MONO_SECURITY_ALIAS
extern alias MonoSecurity;
#endif
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MNS = Mono.Net.Security;
#if MONO_SECURITY_ALIAS
using MonoSecurity::Mono.Security.Interface;
#else
using Mono.Security.Interface;
#endif
namespace Mono.AppleTls
{
class AppleTlsProvider : MonoTlsProvider
{
static readonly Guid id = new Guid ("981af8af-a3a3-419a-9f01-a518e3a17c1c");
public override string Name {
get { return "apple-tls"; }
}
public override Guid ID {
get { return id; }
}
public override IMonoSslStream CreateSslStream (
Stream innerStream, bool leaveInnerStreamOpen,
MonoTlsSettings settings = null)
{
return new AppleTlsStream (innerStream, leaveInnerStreamOpen, settings, this);
}
public override bool SupportsSslStream {
get { return true; }
}
public override bool SupportsMonoExtensions {
get { return true; }
}
public override bool SupportsConnectionInfo {
get { return true; }
}
public override SslProtocols SupportedProtocols {
get { return SslProtocols.Tls12 | SslProtocols.Tls11 | SslProtocols.Tls; }
}
internal override bool ValidateCertificate (
ICertificateValidator2 validator, string targetHost, bool serverMode,
X509CertificateCollection certificates, bool wantsChain, ref X509Chain chain,
ref MonoSslPolicyErrors errors, ref int status11)
{
if (wantsChain)
chain = MNS.SystemCertificateValidator.CreateX509Chain (certificates);
return AppleCertificateHelper.InvokeSystemCertificateValidator (validator, targetHost, serverMode, certificates, ref errors, ref status11);
}
}
}
#endif

View File

@@ -0,0 +1,50 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// AppleTlsStream.cs
//
// Author:
// Martin Baulig <martin.baulig@xamarin.com>
//
// Copyright (c) 2016 Xamarin, Inc.
//
#if MONO_SECURITY_ALIAS
extern alias MonoSecurity;
#endif
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using MNS = Mono.Net.Security;
#if MONO_SECURITY_ALIAS
using MonoSecurity::Mono.Security.Interface;
#else
using Mono.Security.Interface;
#endif
namespace Mono.AppleTls
{
class AppleTlsStream : MNS.MobileAuthenticatedStream
{
public AppleTlsStream (Stream innerStream, bool leaveInnerStreamOpen, MonoTlsSettings settings, MonoTlsProvider provider)
: base (innerStream, leaveInnerStreamOpen, settings, provider)
{
}
protected override MNS.MobileTlsContext CreateContext (
MNS.MobileAuthenticatedStream parent, bool serverMode, string targetHost,
SslProtocols enabledProtocols, X509Certificate serverCertificate,
X509CertificateCollection clientCertificates, bool askForClientCert)
{
return new AppleTlsContext (
parent, serverMode, targetHost,
enabledProtocols, serverCertificate,
clientCertificates, askForClientCert);
}
}
}
#endif

View File

@@ -0,0 +1,341 @@
//
// Certificate.cs: Implements the managed SecCertificate wrapper.
//
// Authors:
// Miguel de Icaza
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2010 Novell, Inc
// Copyright 2012-2013 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
using System;
using System.Runtime.InteropServices;
using System.Security.Cryptography.X509Certificates;
using Mono.Net;
using ObjCRuntime;
namespace Mono.AppleTls {
partial class SecCertificate : INativeObject, IDisposable {
internal IntPtr handle;
internal SecCertificate (IntPtr handle, bool owns = false)
{
if (handle == IntPtr.Zero)
throw new Exception ("Invalid handle");
this.handle = handle;
if (!owns)
CFObject.CFRetain (handle);
}
[DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecCertificateGetTypeID")]
public extern static IntPtr GetTypeID ();
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static IntPtr SecCertificateCreateWithData (IntPtr allocator, IntPtr cfData);
public SecCertificate (X509Certificate certificate)
{
if (certificate == null)
throw new ArgumentNullException ("certificate");
handle = certificate.Impl.GetNativeAppleCertificate ();
if (handle != IntPtr.Zero) {
CFObject.CFRetain (handle);
return;
}
using (CFData cert = CFData.FromData (certificate.GetRawCertData ())) {
Initialize (cert);
}
}
internal SecCertificate (X509CertificateImpl impl)
{
handle = impl.GetNativeAppleCertificate ();
if (handle != IntPtr.Zero) {
CFObject.CFRetain (handle);
return;
}
using (CFData cert = CFData.FromData (impl.GetRawCertData ())) {
Initialize (cert);
}
}
void Initialize (CFData data)
{
handle = SecCertificateCreateWithData (IntPtr.Zero, data.Handle);
if (handle == IntPtr.Zero)
throw new ArgumentException ("Not a valid DER-encoded X.509 certificate");
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static IntPtr SecCertificateCopySubjectSummary (IntPtr cert);
public string SubjectSummary {
get {
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecCertificate");
IntPtr subjectSummaryHandle = IntPtr.Zero;
try {
subjectSummaryHandle = SecCertificateCopySubjectSummary (handle);
CFString subjectSummary = CFString.AsString (subjectSummaryHandle);
return subjectSummary;
}
finally {
if (subjectSummaryHandle != IntPtr.Zero)
CFObject.CFRelease (subjectSummaryHandle);
}
}
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static /* CFDataRef */ IntPtr SecCertificateCopyData (/* SecCertificateRef */ IntPtr cert);
public CFData DerData {
get {
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecCertificate");
IntPtr data = SecCertificateCopyData (handle);
if (data == IntPtr.Zero)
throw new ArgumentException ("Not a valid certificate");
return new CFData (data, true);
}
}
public X509Certificate ToX509Certificate ()
{
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecCertificate");
return new X509Certificate (handle);
}
internal static bool Equals (SecCertificate first, SecCertificate second)
{
/*
* This is a little bit expensive, but unfortunately there is no better API to compare two
* SecCertificateRef's for equality.
*/
if (first == null)
throw new ArgumentNullException ("first");
if (second == null)
throw new ArgumentNullException ("second");
if (first.Handle == second.Handle)
return true;
using (var firstData = first.DerData)
using (var secondData = second.DerData) {
if (firstData.Handle == secondData.Handle)
return true;
if (firstData.Length != secondData.Length)
return false;
IntPtr length = (IntPtr)firstData.Length;
for (long i = 0; i < (long)length; i++) {
if (firstData [i] != secondData [i])
return false;
}
return true;
}
}
~SecCertificate ()
{
Dispose (false);
}
public IntPtr Handle {
get {
return handle;
}
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero){
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
}
}
partial class SecIdentity : INativeObject, IDisposable {
static readonly CFString ImportExportPassphase;
static readonly CFString ImportItemIdentity;
static SecIdentity ()
{
var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
if (handle == IntPtr.Zero)
return;
try {
ImportExportPassphase = CFObject.GetStringConstant (handle, "kSecImportExportPassphrase");
ImportItemIdentity = CFObject.GetStringConstant (handle, "kSecImportItemIdentity");
} finally {
CFObject.dlclose (handle);
}
}
internal IntPtr handle;
internal SecIdentity (IntPtr handle, bool owns = false)
{
this.handle = handle;
if (!owns)
CFObject.CFRetain (handle);
}
[DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecIdentityGetTypeID")]
public extern static IntPtr GetTypeID ();
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static /* OSStatus */ SecStatusCode SecIdentityCopyCertificate (/* SecIdentityRef */ IntPtr identityRef, /* SecCertificateRef* */ out IntPtr certificateRef);
public SecCertificate Certificate {
get {
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecIdentity");
IntPtr cert;
SecStatusCode result = SecIdentityCopyCertificate (handle, out cert);
if (result != SecStatusCode.Success)
throw new InvalidOperationException (result.ToString ());
return new SecCertificate (cert, true);
}
}
public static SecIdentity Import (byte[] data, string password)
{
if (data == null)
throw new ArgumentNullException ("data");
if (string.IsNullOrEmpty (password)) // SecPKCS12Import() doesn't allow empty passwords.
throw new ArgumentException ("password");
using (var pwstring = CFString.Create (password))
using (var options = CFDictionary.FromObjectAndKey (pwstring.Handle, ImportExportPassphase.Handle)) {
CFDictionary [] array;
SecStatusCode result = SecImportExport.ImportPkcs12 (data, options, out array);
if (result != SecStatusCode.Success)
throw new InvalidOperationException (result.ToString ());
return new SecIdentity (array [0].GetValue (ImportItemIdentity.Handle));
}
}
public static SecIdentity Import (X509Certificate2 certificate)
{
if (certificate == null)
throw new ArgumentNullException ("certificate");
if (!certificate.HasPrivateKey)
throw new InvalidOperationException ("Need X509Certificate2 with a private key.");
/*
* SecPSK12Import does not allow any empty passwords, so let's generate
* a semi-random one here.
*/
var password = Guid.NewGuid ().ToString ();
var pkcs12 = certificate.Export (X509ContentType.Pfx, password);
return Import (pkcs12, password);
}
~SecIdentity ()
{
Dispose (false);
}
public IntPtr Handle {
get {
return handle;
}
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero){
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
}
}
partial class SecKey : INativeObject, IDisposable {
internal IntPtr handle;
public SecKey (IntPtr handle, bool owns = false)
{
this.handle = handle;
if (!owns)
CFObject.CFRetain (handle);
}
[DllImport (AppleTlsContext.SecurityLibrary, EntryPoint="SecKeyGetTypeID")]
public extern static IntPtr GetTypeID ();
~SecKey ()
{
Dispose (false);
}
public IntPtr Handle {
get {
return handle;
}
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero){
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
}
}
}
#endif

View File

@@ -0,0 +1,30 @@
#if MONO_FEATURE_APPLETLS
// Copyright 2011-2015 Xamarin Inc. All rights reserved.
using ObjCRuntime;
namespace Mono.AppleTls {
// this is a subset of OSStatus -> SInt32 -> signed int - see CoreFoundation.framework/Headers/CFBase.h
// values are defined in Security.framework/Headers/SecBase.h
enum SecStatusCode {
Success = 0,
DuplicateItem = -25299,
Param = -50,
}
// typedef uint32_t SecTrustResultType;
// values are defined in Security.framework/Headers/SecTrust.h
enum SecTrustResult {
Invalid,
Proceed,
Confirm,
Deny,
Unspecified,
RecoverableTrustFailure,
FatalTrustFailure,
ResultOtherError,
}
}
#endif

View File

@@ -0,0 +1,21 @@
using System;
namespace ObjCRuntime {
internal interface INativeObject {
IntPtr Handle {
get;
}
}
static class NativeObjectHelper {
// help to avoid the (too common pattern)
// var p = x == null ? IntPtr.Zero : x.Handle;
static public IntPtr GetHandle (this INativeObject self)
{
return self == null ? IntPtr.Zero : self.Handle;
}
}
}

View File

@@ -0,0 +1,62 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// ImportExport.cs
//
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2011-2014 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Runtime.InteropServices;
using ObjCRuntime;
using Mono.Net;
namespace Mono.AppleTls {
internal partial class SecImportExport {
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static SecStatusCode SecPKCS12Import (IntPtr pkcs12_data, IntPtr options, out IntPtr items);
static public SecStatusCode ImportPkcs12 (byte[] buffer, CFDictionary options, out CFDictionary[] array)
{
using (CFData data = CFData.FromData (buffer)) {
return ImportPkcs12 (data, options, out array);
}
}
static public SecStatusCode ImportPkcs12 (CFData data, CFDictionary options, out CFDictionary [] array)
{
if (options == null)
throw new ArgumentNullException ("options");
IntPtr handle;
SecStatusCode code = SecPKCS12Import (data.Handle, options.Handle, out handle);
array = CFArray.ArrayFromHandle <CFDictionary> (handle, h => new CFDictionary (h, false));
CFObject.CFRelease (handle);
return code;
}
}
}
#endif

View File

@@ -0,0 +1,258 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// Items.cs: Implements the KeyChain query access APIs
//
// We use strong types and a helper SecQuery class to simplify the
// creation of the dictionary used to query the Keychain
//
// Authors:
// Miguel de Icaza
// Sebastien Pouliot
//
// Copyright 2010 Novell, Inc
// Copyright 2011-2016 Xamarin Inc
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Collections;
using System.Runtime.InteropServices;
using ObjCRuntime;
using Mono.Net;
namespace Mono.AppleTls {
enum SecKind {
Identity
}
static class SecKeyChain {
static readonly IntPtr MatchLimitAll;
static readonly IntPtr MatchLimitOne;
static readonly IntPtr MatchLimit;
static SecKeyChain ()
{
var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
if (handle == IntPtr.Zero)
return;
try {
MatchLimit = CFObject.GetIntPtr (handle, "kSecMatchLimit");
MatchLimitAll = CFObject.GetIntPtr (handle, "kSecMatchLimitAll");
MatchLimitOne = CFObject.GetIntPtr (handle, "kSecMatchLimitOne");
} finally {
CFObject.dlclose (handle);
}
}
public static SecIdentity FindIdentity (SecCertificate certificate, bool throwOnError = false)
{
if (certificate == null)
throw new ArgumentNullException ("certificate");
var identity = FindIdentity (cert => SecCertificate.Equals (certificate, cert));
if (!throwOnError || identity != null)
return identity;
throw new InvalidOperationException (string.Format ("Could not find SecIdentity for certificate '{0}' in keychain.", certificate.SubjectSummary));
}
static SecIdentity FindIdentity (Predicate<SecCertificate> filter)
{
/*
* Unfortunately, SecItemCopyMatching() does not allow any search
* filters when looking up an identity.
*
* The following lookup will return all identities from the keychain -
* we then need need to find the right one.
*/
using (var record = new SecRecord (SecKind.Identity)) {
SecStatusCode status;
var result = SecKeyChain.QueryAsReference (record, -1, out status);
if (status != SecStatusCode.Success || result == null)
return null;
for (int i = 0; i < result.Length; i++) {
var identity = (SecIdentity)result [i];
if (filter (identity.Certificate))
return identity;
}
}
return null;
}
public static INativeObject[] QueryAsReference (SecRecord query, int max, out SecStatusCode result)
{
if (query == null){
result = SecStatusCode.Param;
return null;
}
using (var copy = query.queryDict.MutableCopy ()) {
copy.SetValue (CFBoolean.True.Handle, SecItem.ReturnRef);
SetLimit (copy, max);
IntPtr ptr;
result = SecItem.SecItemCopyMatching (copy.Handle, out ptr);
if ((result == SecStatusCode.Success) && (ptr != IntPtr.Zero)) {
var array = CFArray.ArrayFromHandle<INativeObject> (ptr, p => {
IntPtr cfType = CFType.GetTypeID (p);
if (cfType == SecCertificate.GetTypeID ())
return new SecCertificate (p, true);
else if (cfType == SecKey.GetTypeID ())
return new SecKey (p, true);
else if (cfType == SecIdentity.GetTypeID ())
return new SecIdentity (p, true);
else
throw new Exception (String.Format ("Unexpected type: 0x{0:x}", cfType));
});
return array;
}
return null;
}
}
static CFNumber SetLimit (CFMutableDictionary dict, int max)
{
CFNumber n = null;
IntPtr val;
if (max == -1)
val = MatchLimitAll;
else if (max == 1)
val = MatchLimitOne;
else {
n = CFNumber.FromInt32 (max);
val = n.Handle;
}
dict.SetValue (val, SecKeyChain.MatchLimit);
return n;
}
}
class SecRecord : IDisposable {
static readonly IntPtr SecClassKey;
static SecRecord ()
{
var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
if (handle == IntPtr.Zero)
return;
try {
SecClassKey = CFObject.GetIntPtr (handle, "kSecClassKey");
} finally {
CFObject.dlclose (handle);
}
}
// Fix <= iOS 6 Behaviour - Desk #83099
// NSCFDictionary: mutating method sent to immutable object
// iOS 6 returns an inmutable NSDictionary handle and when we try to set its values it goes kaboom
// By explicitly calling `MutableCopy` we ensure we always have a mutable reference we expect that.
CFDictionary _queryDict;
internal CFDictionary queryDict
{
get {
return _queryDict;
}
set {
_queryDict = value != null ? value.Copy () : null;
}
}
public SecRecord (SecKind secKind)
{
var kind = SecClass.FromSecKind (secKind);
queryDict = CFDictionary.FromObjectAndKey (kind, SecClassKey);
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
protected virtual void Dispose (bool disposing)
{
if (queryDict != null){
if (disposing){
queryDict.Dispose ();
queryDict = null;
}
}
}
~SecRecord ()
{
Dispose (false);
}
}
partial class SecItem {
public static readonly IntPtr ReturnRef;
static SecItem ()
{
var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
if (handle == IntPtr.Zero)
return;
try {
ReturnRef = CFObject.GetIntPtr (handle, "kSecReturnRef");
} finally {
CFObject.dlclose (handle);
}
}
[DllImport (AppleTlsContext.SecurityLibrary)]
internal extern static SecStatusCode SecItemCopyMatching (/* CFDictionaryRef */ IntPtr query, /* CFTypeRef* */ out IntPtr result);
}
static partial class SecClass {
public static readonly IntPtr Identity;
static SecClass ()
{
var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
if (handle == IntPtr.Zero)
return;
try {
Identity = CFObject.GetIntPtr (handle, "kSecClassIdentity");
} finally {
CFObject.dlclose (handle);
}
}
public static IntPtr FromSecKind (SecKind secKind)
{
switch (secKind){
case SecKind.Identity:
return Identity;
default:
throw new ArgumentException ("secKind");
}
}
}
}
#endif

View File

@@ -0,0 +1,87 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// Policy.cs: Implements the managed SecPolicy wrapper.
//
// Authors:
// Miguel de Icaza
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2010 Novell, Inc
// Copyright 2012-2014 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Runtime.InteropServices;
using ObjCRuntime;
using Mono.Net;
namespace Mono.AppleTls {
partial class SecPolicy : INativeObject, IDisposable {
IntPtr handle;
internal SecPolicy (IntPtr handle, bool owns = false)
{
if (handle == IntPtr.Zero)
throw new Exception ("Invalid handle");
this.handle = handle;
if (!owns)
CFObject.CFRetain (handle);
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static IntPtr /* SecPolicyRef */ SecPolicyCreateSSL (bool server, IntPtr /* CFStringRef */ hostname);
static public SecPolicy CreateSslPolicy (bool server, string hostName)
{
CFString host = hostName == null ? null : CFString.Create (hostName);
IntPtr handle = host == null ? IntPtr.Zero : host.Handle;
SecPolicy policy = new SecPolicy (SecPolicyCreateSSL (server, handle), true);
if (host != null)
host.Dispose ();
return policy;
}
~SecPolicy ()
{
Dispose (false);
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
public IntPtr Handle {
get { return handle; }
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero){
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
}
}
}
#endif

View File

@@ -0,0 +1,251 @@
#if MONO_FEATURE_APPLETLS
// Copyright 2014 Xamarin Inc. All rights reserved.
namespace Mono.AppleTls {
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslProtocol {
Unknown = 0,
// Ssl_3_0 = 2,
Tls_1_0 = 4,
Tls_1_1 = 7,
Tls_1_2 = 8,
// Dtls_1_0 = 9,
/* Obsolete on iOS */
// Ssl_2_0 = 1,
// Ssl_3_0_only = 3,
// Tls_1_0_only = 5,
// All = 6,
}
// subset of OSStatus (int)
enum SslStatus {
Success = 0, // errSecSuccess in SecBase.h
Protocol = -9800,
Negotiation = -9801,
FatalAlert = -9802,
WouldBlock = -9803,
SessionNotFound = -9804,
ClosedGraceful = -9805,
ClosedAbort = -9806,
XCertChainInvalid = -9807,
BadCert = -9808,
Crypto = -9809,
Internal = -9810,
ModuleAttach = -9811,
UnknownRootCert = -9812,
NoRootCert = -9813,
CertExpired = -9814,
CertNotYetValid = -9815,
ClosedNotNotified = -9816,
BufferOverflow = -9817,
BadCipherSuite = -9818,
PeerUnexpectedMsg = -9819,
PeerBadRecordMac = -9820,
PeerDecryptionFail = -9821,
PeerRecordOverflow = -9822,
PeerDecompressFail = -9823,
PeerHandshakeFail = -9824,
PeerBadCert = -9825,
PeerUnsupportedCert = -9826,
PeerCertRevoked = -9827,
PeerCertExpired = -9828,
PeerCertUnknown = -9829,
IllegalParam = -9830,
PeerUnknownCA = -9831,
PeerAccessDenied = -9832,
PeerDecodeError = -9833,
PeerDecryptError = -9834,
PeerExportRestriction = -9835,
PeerProtocolVersion = -9836,
PeerInsufficientSecurity = -9837,
PeerInternalError = -9838,
PeerUserCancelled = -9839,
PeerNoRenegotiation = -9840,
PeerAuthCompleted = -9841, // non fatal
PeerClientCertRequested = -9842, // non fatal
HostNameMismatch = -9843,
ConnectionRefused = -9844,
DecryptionFail = -9845,
BadRecordMac = -9846,
RecordOverflow = -9847,
BadConfiguration = -9848,
UnexpectedRecord = -9849,
SSLWeakPeerEphemeralDHKey = -9850,
SSLClientHelloReceived = -9851 // non falta
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslSessionOption {
BreakOnServerAuth,
BreakOnCertRequested,
BreakOnClientAuth,
// FalseStart,
// SendOneByteRecord,
// AllowServerIdentityChange = 5,
// Fallback = 6,
// BreakOnClientHello = 7,
// AllowRenegotiation = 8,
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslAuthenticate {
// Never,
// Always,
Try = 2,
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslProtocolSide {
Server,
Client,
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslConnectionType {
Stream,
// Datagram
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslSessionState {
Invalid = -1,
// Idle,
// Handshake,
// Connected,
// Closed,
// Aborted
}
// Security.framework/Headers/SecureTransport.h
// untyped enum
enum SslClientCertificateState {
None,
Requested,
Sent,
Rejected
}
// Security.framework/Headers/CipherSuite.h
// 32 bits (uint32_t) on OSX, 16 bits (uint16_t) on iOS
#if XAMMAC || XAMMAC_4_5
enum SslCipherSuite : uint {
#else
enum SslCipherSuite : ushort {
#endif
// DO NOT RENAME VALUES - they don't look good but we need them to keep compatibility with our System.dll code
// it's how it's defined across most SSL/TLS implementation (from RFC)
SSL_NULL_WITH_NULL_NULL = 0x0000, // value used before (not after) negotiation
TLS_NULL_WITH_NULL_NULL = 0x0000,
// Not the whole list (too much unneeed metadata) but only what's supported
// FIXME needs to be expended with OSX 10.9
SSL_RSA_WITH_NULL_MD5 = 0x0001,
SSL_RSA_WITH_NULL_SHA = 0x0002,
SSL_RSA_EXPORT_WITH_RC4_40_MD5 = 0x0003, // iOS 5.1 only
SSL_RSA_WITH_RC4_128_MD5 = 0x0004,
SSL_RSA_WITH_RC4_128_SHA = 0x0005,
SSL_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A,
SSL_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016,
SSL_DH_anon_EXPORT_WITH_RC4_40_MD5 = 0x0017, // iOS 5.1 only
SSL_DH_anon_WITH_RC4_128_MD5 = 0x0018,
SSL_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B,
// TLS - identical values to SSL (above)
TLS_RSA_WITH_NULL_MD5 = 0x0001,
TLS_RSA_WITH_NULL_SHA = 0x0002,
TLS_RSA_WITH_RC4_128_MD5 = 0x0004,
TLS_RSA_WITH_RC4_128_SHA = 0x0005,
TLS_RSA_WITH_3DES_EDE_CBC_SHA = 0x000A,
TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA = 0x0016,
TLS_DH_anon_WITH_RC4_128_MD5 = 0x0018,
TLS_DH_anon_WITH_3DES_EDE_CBC_SHA = 0x001B,
// TLS specific
TLS_PSK_WITH_NULL_SHA = 0x002C,
TLS_RSA_WITH_AES_128_CBC_SHA = 0x002F,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA = 0x0033,
TLS_DH_anon_WITH_AES_128_CBC_SHA = 0x0034,
TLS_RSA_WITH_AES_256_CBC_SHA = 0x0035,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA = 0x0039,
TLS_DH_anon_WITH_AES_256_CBC_SHA = 0x003A,
TLS_RSA_WITH_NULL_SHA256 = 0x003B,
TLS_RSA_WITH_AES_128_CBC_SHA256 = 0x003C,
TLS_RSA_WITH_AES_256_CBC_SHA256 = 0x003D,
TLS_DHE_RSA_WITH_AES_128_CBC_SHA256 = 0x0067,
TLS_DHE_RSA_WITH_AES_256_CBC_SHA256 = 0x006B,
TLS_DH_anon_WITH_AES_128_CBC_SHA256 = 0x006C,
TLS_DH_anon_WITH_AES_256_CBC_SHA256 = 0x006D,
TLS_PSK_WITH_RC4_128_SHA = 0x008A,
TLS_PSK_WITH_3DES_EDE_CBC_SHA = 0x008B,
TLS_PSK_WITH_AES_128_CBC_SHA = 0x008C,
TLS_PSK_WITH_AES_256_CBC_SHA = 0x008D,
TLS_RSA_WITH_AES_128_GCM_SHA256 = 0x009C, // iOS 9+
TLS_RSA_WITH_AES_256_GCM_SHA384 = 0x009D, // iOS 9+
TLS_DHE_RSA_WITH_AES_128_GCM_SHA256 = 0x009E, // iOS 9+
TLS_DHE_RSA_WITH_AES_256_GCM_SHA384 = 0x009F, // iOS 9+
TLS_DH_anon_WITH_AES_128_GCM_SHA256 = 0x00A6, // iOS 5.1 only
TLS_DH_anon_WITH_AES_256_GCM_SHA384 = 0x00A7, // iOS 5.1 only
TLS_PSK_WITH_AES_128_CBC_SHA256 = 0x00AE,
TLS_PSK_WITH_AES_256_CBC_SHA384 = 0x00AF,
TLS_PSK_WITH_NULL_SHA256 = 0x00B0,
TLS_PSK_WITH_NULL_SHA384 = 0x00B1,
TLS_ECDH_ECDSA_WITH_NULL_SHA = 0xC001,
TLS_ECDH_ECDSA_WITH_RC4_128_SHA = 0xC002,
TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC003,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA = 0xC004,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA = 0xC005,
TLS_ECDHE_ECDSA_WITH_NULL_SHA = 0xC006,
TLS_ECDHE_ECDSA_WITH_RC4_128_SHA = 0xC007,
TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA = 0xC008,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA = 0xC009,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA = 0xC00A,
TLS_ECDH_RSA_WITH_NULL_SHA = 0xC00B,
TLS_ECDH_RSA_WITH_RC4_128_SHA = 0xC00C,
TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA = 0xC00D,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA = 0xC00E,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA = 0xC00F,
TLS_ECDHE_RSA_WITH_NULL_SHA = 0xC010,
TLS_ECDHE_RSA_WITH_RC4_128_SHA = 0xC011,
TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA = 0xC012,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA = 0xC013,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA = 0xC014,
TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC023,
TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC024,
TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA256 = 0xC025,
TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA384 = 0xC026,
TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256 = 0xC027,
TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA384 = 0xC028,
TLS_ECDH_RSA_WITH_AES_128_CBC_SHA256 = 0xC029,
TLS_ECDH_RSA_WITH_AES_256_CBC_SHA384 = 0xC02A,
TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02B, // iOS 9+
TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02C, // iOS 9+
TLS_ECDH_ECDSA_WITH_AES_128_GCM_SHA256 = 0xC02D, // iOS 9+
TLS_ECDH_ECDSA_WITH_AES_256_GCM_SHA384 = 0xC02E, // iOS 9+
TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 = 0xC02F, // iOS 9+
TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 = 0xC030, // iOS 9+
TLS_ECDH_RSA_WITH_AES_128_GCM_SHA256 = 0xC031, // iOS 9+
TLS_ECDH_RSA_WITH_AES_256_GCM_SHA384 = 0xC032, // iOS 9+
}
}
#endif

View File

@@ -0,0 +1,18 @@
#if MONO_FEATURE_APPLETLS
//
// SslConnection
//
// Authors:
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2014 Xamarin Inc.
//
using System;
namespace Mono.AppleTls
{
delegate SslStatus SslReadFunc (IntPtr connection, IntPtr data, /* size_t* */ ref IntPtr dataLength);
delegate SslStatus SslWriteFunc (IntPtr connection, IntPtr data, /* size_t* */ ref IntPtr dataLength);
}
#endif

View File

@@ -0,0 +1,186 @@
#if SECURITY_DEP && MONO_FEATURE_APPLETLS
//
// Trust.cs: Implements the managed SecTrust wrapper.
//
// Authors:
// Miguel de Icaza
// Sebastien Pouliot <sebastien@xamarin.com>
//
// Copyright 2010 Novell, Inc
// Copyright 2012-2014 Xamarin Inc.
//
// Permission is hereby granted, free of charge, to any person obtaining
// a copy of this software and associated documentation files (the
// "Software"), to deal in the Software without restriction, including
// without limitation the rights to use, copy, modify, merge, publish,
// distribute, sublicense, and/or sell copies of the Software, and to
// permit persons to whom the Software is furnished to do so, subject to
// the following conditions:
//
// The above copyright notice and this permission notice shall be
// included in all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
//
using System;
using System.Runtime.InteropServices;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using ObjCRuntime;
using Mono.Net;
namespace Mono.AppleTls {
partial class SecTrust : INativeObject, IDisposable {
IntPtr handle;
internal SecTrust (IntPtr handle, bool owns = false)
{
if (handle == IntPtr.Zero)
throw new Exception ("Invalid handle");
this.handle = handle;
if (!owns)
CFObject.CFRetain (handle);
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static SecStatusCode SecTrustCreateWithCertificates (
/* CFTypeRef */ IntPtr certOrCertArray,
/* CFTypeRef __nullable */ IntPtr policies,
/* SecTrustRef *__nonull */ out IntPtr sectrustref);
public SecTrust (X509CertificateCollection certificates, SecPolicy policy)
{
if (certificates == null)
throw new ArgumentNullException ("certificates");
SecCertificate[] array = new SecCertificate [certificates.Count];
int i = 0;
foreach (var certificate in certificates)
array [i++] = new SecCertificate (certificate);
Initialize (array, policy);
}
void Initialize (SecCertificate[] array, SecPolicy policy)
{
using (var certs = CFArray.CreateArray (array)) {
Initialize (certs.Handle, policy);
}
}
void Initialize (IntPtr certHandle, SecPolicy policy)
{
SecStatusCode result = SecTrustCreateWithCertificates (certHandle, policy == null ? IntPtr.Zero : policy.Handle, out handle);
if (result != SecStatusCode.Success)
throw new ArgumentException (result.ToString ());
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static SecStatusCode /* OSStatus */ SecTrustEvaluate (IntPtr /* SecTrustRef */ trust, out /* SecTrustResultType */ SecTrustResult result);
public SecTrustResult Evaluate ()
{
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecTrust");
SecTrustResult trust;
SecStatusCode result = SecTrustEvaluate (handle, out trust);
if (result != SecStatusCode.Success)
throw new InvalidOperationException (result.ToString ());
return trust;
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static IntPtr /* CFIndex */ SecTrustGetCertificateCount (IntPtr /* SecTrustRef */ trust);
public int Count {
get {
if (handle == IntPtr.Zero)
return 0;
return (int) SecTrustGetCertificateCount (handle);
}
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static IntPtr /* SecCertificateRef */ SecTrustGetCertificateAtIndex (IntPtr /* SecTrustRef */ trust, IntPtr /* CFIndex */ ix);
public SecCertificate this [IntPtr index] {
get {
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecTrust");
if (((long)index < 0) || ((long)index >= Count))
throw new ArgumentOutOfRangeException ("index");
return new SecCertificate (SecTrustGetCertificateAtIndex (handle, index));
}
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static SecStatusCode /* OSStatus */ SecTrustSetAnchorCertificates (IntPtr /* SecTrustRef */ trust, IntPtr /* CFArrayRef */ anchorCertificates);
public SecStatusCode SetAnchorCertificates (X509CertificateCollection certificates)
{
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecTrust");
if (certificates == null)
return SecTrustSetAnchorCertificates (handle, IntPtr.Zero);
SecCertificate[] array = new SecCertificate [certificates.Count];
int i = 0;
foreach (var certificate in certificates)
array [i++] = new SecCertificate (certificate);
return SetAnchorCertificates (array);
}
public SecStatusCode SetAnchorCertificates (SecCertificate[] array)
{
if (array == null)
return SecTrustSetAnchorCertificates (handle, IntPtr.Zero);
using (var certs = CFArray.FromNativeObjects (array)) {
return SecTrustSetAnchorCertificates (handle, certs.Handle);
}
}
[DllImport (AppleTlsContext.SecurityLibrary)]
extern static SecStatusCode /* OSStatus */ SecTrustSetAnchorCertificatesOnly (IntPtr /* SecTrustRef */ trust, bool anchorCertificatesOnly);
public SecStatusCode SetAnchorCertificatesOnly (bool anchorCertificatesOnly)
{
if (handle == IntPtr.Zero)
throw new ObjectDisposedException ("SecTrust");
return SecTrustSetAnchorCertificatesOnly (handle, anchorCertificatesOnly);
}
~SecTrust ()
{
Dispose (false);
}
protected virtual void Dispose (bool disposing)
{
if (handle != IntPtr.Zero) {
CFObject.CFRelease (handle);
handle = IntPtr.Zero;
}
}
public void Dispose ()
{
Dispose (true);
GC.SuppressFinalize (this);
}
public IntPtr Handle {
get { return handle; }
}
}
}
#endif