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;
37 using ObjCRuntimeInternal;
40 namespace Mono.AppleTls {
48 static class SecKeyChain {
50 class SecKeyChain : INativeObject, IDisposable {
52 internal static readonly IntPtr MatchLimitAll;
53 internal static readonly IntPtr MatchLimitOne;
54 internal static readonly IntPtr MatchLimit;
59 internal SecKeyChain (IntPtr handle, bool owns = false)
61 if (handle == IntPtr.Zero)
62 throw new ArgumentException ("Invalid handle");
66 CFObject.CFRetain (handle);
72 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
73 if (handle == IntPtr.Zero)
77 MatchLimit = CFObject.GetIntPtr (handle, "kSecMatchLimit");
78 MatchLimitAll = CFObject.GetIntPtr (handle, "kSecMatchLimitAll");
79 MatchLimitOne = CFObject.GetIntPtr (handle, "kSecMatchLimitOne");
81 CFObject.dlclose (handle);
85 public static SecIdentity FindIdentity (SecCertificate certificate, bool throwOnError = false)
87 if (certificate == null)
88 throw new ArgumentNullException ("certificate");
89 var identity = FindIdentity (cert => SecCertificate.Equals (certificate, cert));
90 if (!throwOnError || identity != null)
93 throw new InvalidOperationException (string.Format ("Could not find SecIdentity for certificate '{0}' in keychain.", certificate.SubjectSummary));
96 static SecIdentity FindIdentity (Predicate<SecCertificate> filter)
99 * Unfortunately, SecItemCopyMatching() does not allow any search
100 * filters when looking up an identity.
102 * The following lookup will return all identities from the keychain -
103 * we then need need to find the right one.
105 using (var record = new SecRecord (SecKind.Identity)) {
106 SecStatusCode status;
107 var result = SecKeyChain.QueryAsReference (record, -1, out status);
108 if (status != SecStatusCode.Success || result == null)
111 for (int i = 0; i < result.Length; i++) {
112 var identity = (SecIdentity)result [i];
113 if (filter (identity.Certificate))
121 static INativeObject [] QueryAsReference (SecRecord query, int max, out SecStatusCode result)
124 result = SecStatusCode.Param;
128 using (var copy = query.QueryDict.MutableCopy ()) {
129 copy.SetValue (CFBoolean.True.Handle, SecItem.ReturnRef);
130 SetLimit (copy, max);
131 return QueryAsReference (copy, out result);
135 static INativeObject [] QueryAsReference (CFDictionary query, out SecStatusCode result)
138 result = SecStatusCode.Param;
143 result = SecItem.SecItemCopyMatching (query.Handle, out ptr);
144 if (result == SecStatusCode.Success && ptr != IntPtr.Zero) {
145 var array = CFArray.ArrayFromHandle<INativeObject> (ptr, p => {
146 IntPtr cfType = CFType.GetTypeID (p);
147 if (cfType == SecCertificate.GetTypeID ())
148 return new SecCertificate (p, true);
149 if (cfType == SecKey.GetTypeID ())
150 return new SecKey (p, true);
151 if (cfType == SecIdentity.GetTypeID ())
152 return new SecIdentity (p, true);
153 throw new Exception (String.Format ("Unexpected type: 0x{0:x}", cfType));
160 internal static CFNumber SetLimit (CFMutableDictionary dict, int max)
169 n = CFNumber.FromInt32 (max);
173 dict.SetValue (val, SecKeyChain.MatchLimit);
178 [DllImport (AppleTlsContext.SecurityLibrary)]
179 extern static /* OSStatus */ SecStatusCode SecKeychainCreate (/* const char * */ IntPtr pathName, uint passwordLength, /* const void * */ IntPtr password,
180 bool promptUser, /* SecAccessRef */ IntPtr initialAccess,
181 /* SecKeychainRef _Nullable * */ out IntPtr keychain);
183 internal static SecKeyChain Create (string pathName, string password)
186 var pathNamePtr = Marshal.StringToHGlobalAnsi (pathName);
187 var passwordPtr = Marshal.StringToHGlobalAnsi (password);
188 var result = SecKeychainCreate (pathNamePtr, (uint)password.Length, passwordPtr, false, IntPtr.Zero, out handle);
189 if (result != SecStatusCode.Success)
190 throw new InvalidOperationException (result.ToString ());
191 return new SecKeyChain (handle, true);
194 [DllImport (AppleTlsContext.SecurityLibrary)]
195 extern static /* OSStatus */ SecStatusCode SecKeychainOpen (/* const char * */ IntPtr pathName, /* SecKeychainRef _Nullable * */ out IntPtr keychain);
197 internal static SecKeyChain Open (string pathName)
200 IntPtr pathNamePtr = IntPtr.Zero;
202 pathNamePtr = Marshal.StringToHGlobalAnsi (pathName);
203 var result = SecKeychainOpen (pathNamePtr, out handle);
204 if (result != SecStatusCode.Success)
205 throw new InvalidOperationException (result.ToString ());
206 return new SecKeyChain (handle, true);
208 if (pathNamePtr != IntPtr.Zero)
209 Marshal.FreeHGlobal (pathNamePtr);
213 internal static SecKeyChain OpenSystemRootCertificates ()
215 return Open ("/System/Library/Keychains/SystemRootCertificates.keychain");
223 public IntPtr Handle {
229 public void Dispose ()
232 GC.SuppressFinalize (this);
235 protected virtual void Dispose (bool disposing)
237 if (handle != IntPtr.Zero) {
238 CFObject.CFRelease (handle);
239 handle = IntPtr.Zero;
245 class SecRecord : IDisposable {
247 internal static readonly IntPtr SecClassKey;
250 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
251 if (handle == IntPtr.Zero)
255 SecClassKey = CFObject.GetIntPtr (handle, "kSecClass");
257 CFObject.dlclose (handle);
261 CFMutableDictionary _queryDict;
262 internal CFMutableDictionary QueryDict {
268 internal void SetValue (IntPtr key, IntPtr value)
270 _queryDict.SetValue (key, value);
273 public SecRecord (SecKind secKind)
275 var kind = SecClass.FromSecKind (secKind);
276 _queryDict = CFMutableDictionary.Create ();
277 _queryDict.SetValue (SecClassKey, kind);
280 public void Dispose ()
283 GC.SuppressFinalize (this);
286 protected virtual void Dispose (bool disposing)
288 if (_queryDict != null){
290 _queryDict.Dispose ();
302 partial class SecItem {
303 public static readonly IntPtr ReturnRef;
304 public static readonly IntPtr MatchSearchList;
308 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
309 if (handle == IntPtr.Zero)
313 ReturnRef = CFObject.GetIntPtr (handle, "kSecReturnRef");
314 MatchSearchList = CFObject.GetIntPtr (handle, "kSecMatchSearchList");
316 CFObject.dlclose (handle);
320 [DllImport (AppleTlsContext.SecurityLibrary)]
321 internal extern static SecStatusCode SecItemCopyMatching (/* CFDictionaryRef */ IntPtr query, /* CFTypeRef* */ out IntPtr result);
324 static partial class SecClass {
326 public static readonly IntPtr Identity;
327 public static readonly IntPtr Certificate;
331 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
332 if (handle == IntPtr.Zero)
336 Identity = CFObject.GetIntPtr (handle, "kSecClassIdentity");
337 Certificate = CFObject.GetIntPtr (handle, "kSecClassCertificate");
339 CFObject.dlclose (handle);
343 public static IntPtr FromSecKind (SecKind secKind)
346 case SecKind.Identity:
348 case SecKind.Certificate:
351 throw new ArgumentException ("secKind");