1 #if SECURITY_DEP && MONO_FEATURE_APPLETLS
3 // Items.cs: Implements the KeyChain query access APIs
5 // We use strong types and a helper SecQuery class to simplify the
6 // creation of the dictionary used to query the Keychain
12 // Copyright 2010 Novell, Inc
13 // Copyright 2011-2016 Xamarin Inc
15 // Permission is hereby granted, free of charge, to any person obtaining
16 // a copy of this software and associated documentation files (the
17 // "Software"), to deal in the Software without restriction, including
18 // without limitation the rights to use, copy, modify, merge, publish,
19 // distribute, sublicense, and/or sell copies of the Software, and to
20 // permit persons to whom the Software is furnished to do so, subject to
21 // the following conditions:
23 // The above copyright notice and this permission notice shall be
24 // included in all copies or substantial portions of the Software.
26 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
27 // EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
28 // MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
29 // NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
30 // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
31 // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
32 // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
35 using System.Collections;
36 using System.Runtime.InteropServices;
40 namespace Mono.AppleTls {
46 static class SecKeyChain {
47 static readonly IntPtr MatchLimitAll;
48 static readonly IntPtr MatchLimitOne;
49 static readonly IntPtr MatchLimit;
53 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
54 if (handle == IntPtr.Zero)
58 MatchLimit = CFObject.GetIntPtr (handle, "kSecMatchLimit");
59 MatchLimitAll = CFObject.GetIntPtr (handle, "kSecMatchLimitAll");
60 MatchLimitOne = CFObject.GetIntPtr (handle, "kSecMatchLimitOne");
62 CFObject.dlclose (handle);
66 public static SecIdentity FindIdentity (SecCertificate certificate, bool throwOnError = false)
68 if (certificate == null)
69 throw new ArgumentNullException ("certificate");
70 var identity = FindIdentity (cert => SecCertificate.Equals (certificate, cert));
71 if (!throwOnError || identity != null)
74 throw new InvalidOperationException (string.Format ("Could not find SecIdentity for certificate '{0}' in keychain.", certificate.SubjectSummary));
77 static SecIdentity FindIdentity (Predicate<SecCertificate> filter)
80 * Unfortunately, SecItemCopyMatching() does not allow any search
81 * filters when looking up an identity.
83 * The following lookup will return all identities from the keychain -
84 * we then need need to find the right one.
86 using (var record = new SecRecord (SecKind.Identity)) {
88 var result = SecKeyChain.QueryAsReference (record, -1, out status);
89 if (status != SecStatusCode.Success || result == null)
92 for (int i = 0; i < result.Length; i++) {
93 var identity = (SecIdentity)result [i];
94 if (filter (identity.Certificate))
102 public static INativeObject[] QueryAsReference (SecRecord query, int max, out SecStatusCode result)
105 result = SecStatusCode.Param;
109 using (var copy = query.queryDict.MutableCopy ()) {
110 copy.SetValue (CFBoolean.True.Handle, SecItem.ReturnRef);
111 SetLimit (copy, max);
114 result = SecItem.SecItemCopyMatching (copy.Handle, out ptr);
115 if ((result == SecStatusCode.Success) && (ptr != IntPtr.Zero)) {
116 var array = CFArray.ArrayFromHandle<INativeObject> (ptr, p => {
117 IntPtr cfType = CFType.GetTypeID (p);
118 if (cfType == SecCertificate.GetTypeID ())
119 return new SecCertificate (p, true);
120 else if (cfType == SecKey.GetTypeID ())
121 return new SecKey (p, true);
122 else if (cfType == SecIdentity.GetTypeID ())
123 return new SecIdentity (p, true);
125 throw new Exception (String.Format ("Unexpected type: 0x{0:x}", cfType));
133 static CFNumber SetLimit (CFMutableDictionary dict, int max)
142 n = CFNumber.FromInt32 (max);
146 dict.SetValue (val, SecKeyChain.MatchLimit);
151 class SecRecord : IDisposable {
153 static readonly IntPtr SecClassKey;
156 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
157 if (handle == IntPtr.Zero)
161 SecClassKey = CFObject.GetIntPtr (handle, "kSecClassKey");
163 CFObject.dlclose (handle);
167 // Fix <= iOS 6 Behaviour - Desk #83099
168 // NSCFDictionary: mutating method sent to immutable object
169 // iOS 6 returns an inmutable NSDictionary handle and when we try to set its values it goes kaboom
170 // By explicitly calling `MutableCopy` we ensure we always have a mutable reference we expect that.
171 CFDictionary _queryDict;
172 internal CFDictionary queryDict
178 _queryDict = value != null ? value.Copy () : null;
182 public SecRecord (SecKind secKind)
184 var kind = SecClass.FromSecKind (secKind);
185 queryDict = CFDictionary.FromObjectAndKey (kind, SecClassKey);
188 public void Dispose ()
191 GC.SuppressFinalize (this);
194 protected virtual void Dispose (bool disposing)
196 if (queryDict != null){
198 queryDict.Dispose ();
210 partial class SecItem {
211 public static readonly IntPtr ReturnRef;
215 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
216 if (handle == IntPtr.Zero)
220 ReturnRef = CFObject.GetIntPtr (handle, "kSecReturnRef");
222 CFObject.dlclose (handle);
226 [DllImport (AppleTlsContext.SecurityLibrary)]
227 internal extern static SecStatusCode SecItemCopyMatching (/* CFDictionaryRef */ IntPtr query, /* CFTypeRef* */ out IntPtr result);
230 static partial class SecClass {
232 public static readonly IntPtr Identity;
236 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
237 if (handle == IntPtr.Zero)
241 Identity = CFObject.GetIntPtr (handle, "kSecClassIdentity");
243 CFObject.dlclose (handle);
247 public static IntPtr FromSecKind (SecKind secKind)
250 case SecKind.Identity:
253 throw new ArgumentException ("secKind");