1 #if SECURITY_DEP && MONO_FEATURE_APPLETLS 2 // 3 // Items.cs: Implements the KeyChain query access APIs 4 // 5 // We use strong types and a helper SecQuery class to simplify the 6 // creation of the dictionary used to query the Keychain 7 // 8 // Authors: 9 // Miguel de Icaza 10 // Sebastien Pouliot 11 // 12 // Copyright 2010 Novell, Inc 13 // Copyright 2011-2016 Xamarin Inc 14 // 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: 22 // 23 // The above copyright notice and this permission notice shall be 24 // included in all copies or substantial portions of the Software. 25 // 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. 33 // 34 using System; 35 using System.Collections; 36 using System.Runtime.InteropServices; 37 using ObjCRuntimeInternal; 38 using Mono.Net; 39 40 namespace Mono.AppleTls { 41 42 enum SecKind { 43 Identity, 44 Certificate 45 } 46 47 #if MONOTOUCH 48 static class SecKeyChain { 49 #else 50 class SecKeyChain : INativeObject, IDisposable { 51 #endif 52 internal static readonly IntPtr MatchLimitAll; 53 internal static readonly IntPtr MatchLimitOne; 54 internal static readonly IntPtr MatchLimit; 55 56 #if !MONOTOUCH 57 IntPtr handle; 58 SecKeyChain(IntPtr handle, bool owns = false)59 internal SecKeyChain (IntPtr handle, bool owns = false) 60 { 61 if (handle == IntPtr.Zero) 62 throw new ArgumentException ("Invalid handle"); 63 64 this.handle = handle; 65 if (!owns) 66 CFObject.CFRetain (handle); 67 } 68 #endif 69 SecKeyChain()70 static SecKeyChain () 71 { 72 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0); 73 if (handle == IntPtr.Zero) 74 return; 75 76 try { 77 MatchLimit = CFObject.GetIntPtr (handle, "kSecMatchLimit"); 78 MatchLimitAll = CFObject.GetIntPtr (handle, "kSecMatchLimitAll"); 79 MatchLimitOne = CFObject.GetIntPtr (handle, "kSecMatchLimitOne"); 80 } finally { 81 CFObject.dlclose (handle); 82 } 83 } 84 FindIdentity(SecCertificate certificate, bool throwOnError = false)85 public static SecIdentity FindIdentity (SecCertificate certificate, bool throwOnError = false) 86 { 87 if (certificate == null) 88 throw new ArgumentNullException ("certificate"); 89 var identity = FindIdentity (cert => SecCertificate.Equals (certificate, cert)); 90 if (!throwOnError || identity != null) 91 return identity; 92 93 throw new InvalidOperationException (string.Format ("Could not find SecIdentity for certificate '{0}' in keychain.", certificate.SubjectSummary)); 94 } 95 FindIdentity(Predicate<SecCertificate> filter)96 static SecIdentity FindIdentity (Predicate<SecCertificate> filter) 97 { 98 /* 99 * Unfortunately, SecItemCopyMatching() does not allow any search 100 * filters when looking up an identity. 101 * 102 * The following lookup will return all identities from the keychain - 103 * we then need need to find the right one. 104 */ 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) 109 return null; 110 111 for (int i = 0; i < result.Length; i++) { 112 var identity = (SecIdentity)result [i]; 113 if (filter (identity.Certificate)) 114 return identity; 115 } 116 } 117 118 return null; 119 } 120 QueryAsReference(SecRecord query, int max, out SecStatusCode result)121 static INativeObject [] QueryAsReference (SecRecord query, int max, out SecStatusCode result) 122 { 123 if (query == null) { 124 result = SecStatusCode.Param; 125 return null; 126 } 127 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); 132 } 133 } 134 QueryAsReference(CFDictionary query, out SecStatusCode result)135 static INativeObject [] QueryAsReference (CFDictionary query, out SecStatusCode result) 136 { 137 if (query == null) { 138 result = SecStatusCode.Param; 139 return null; 140 } 141 142 IntPtr ptr; 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)); 154 }); 155 return array; 156 } 157 return null; 158 } 159 SetLimit(CFMutableDictionary dict, int max)160 internal static CFNumber SetLimit (CFMutableDictionary dict, int max) 161 { 162 CFNumber n = null; 163 IntPtr val; 164 if (max == -1) 165 val = MatchLimitAll; 166 else if (max == 1) 167 val = MatchLimitOne; 168 else { 169 n = CFNumber.FromInt32 (max); 170 val = n.Handle; 171 } 172 173 dict.SetValue (val, SecKeyChain.MatchLimit); 174 return n; 175 } 176 177 #if !MONOTOUCH 178 [DllImport (AppleTlsContext.SecurityLibrary)] SecKeychainCreate( IntPtr pathName, uint passwordLength, IntPtr password, bool promptUser, IntPtr initialAccess, out IntPtr keychain)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); 182 Create(string pathName, string password)183 internal static SecKeyChain Create (string pathName, string password) 184 { 185 IntPtr handle; 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); 192 } 193 194 [DllImport (AppleTlsContext.SecurityLibrary)] SecKeychainOpen( IntPtr pathName, out IntPtr keychain)195 extern static /* OSStatus */ SecStatusCode SecKeychainOpen (/* const char * */ IntPtr pathName, /* SecKeychainRef _Nullable * */ out IntPtr keychain); 196 Open(string pathName)197 internal static SecKeyChain Open (string pathName) 198 { 199 IntPtr handle; 200 IntPtr pathNamePtr = IntPtr.Zero; 201 try { 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); 207 } finally { 208 if (pathNamePtr != IntPtr.Zero) 209 Marshal.FreeHGlobal (pathNamePtr); 210 } 211 } 212 OpenSystemRootCertificates()213 internal static SecKeyChain OpenSystemRootCertificates () 214 { 215 return Open ("/System/Library/Keychains/SystemRootCertificates.keychain"); 216 } 217 ~SecKeyChain()218 ~SecKeyChain () 219 { 220 Dispose (false); 221 } 222 223 public IntPtr Handle { 224 get { 225 return handle; 226 } 227 } 228 Dispose()229 public void Dispose () 230 { 231 Dispose (true); 232 GC.SuppressFinalize (this); 233 } 234 Dispose(bool disposing)235 protected virtual void Dispose (bool disposing) 236 { 237 if (handle != IntPtr.Zero) { 238 CFObject.CFRelease (handle); 239 handle = IntPtr.Zero; 240 } 241 } 242 #endif 243 } 244 245 class SecRecord : IDisposable { 246 247 internal static readonly IntPtr SecClassKey; SecRecord()248 static SecRecord () 249 { 250 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0); 251 if (handle == IntPtr.Zero) 252 return; 253 254 try { 255 SecClassKey = CFObject.GetIntPtr (handle, "kSecClass"); 256 } finally { 257 CFObject.dlclose (handle); 258 } 259 } 260 261 CFMutableDictionary _queryDict; 262 internal CFMutableDictionary QueryDict { 263 get { 264 return _queryDict; 265 } 266 } 267 SetValue(IntPtr key, IntPtr value)268 internal void SetValue (IntPtr key, IntPtr value) 269 { 270 _queryDict.SetValue (key, value); 271 } 272 SecRecord(SecKind secKind)273 public SecRecord (SecKind secKind) 274 { 275 var kind = SecClass.FromSecKind (secKind); 276 _queryDict = CFMutableDictionary.Create (); 277 _queryDict.SetValue (SecClassKey, kind); 278 } 279 Dispose()280 public void Dispose () 281 { 282 Dispose (true); 283 GC.SuppressFinalize (this); 284 } 285 Dispose(bool disposing)286 protected virtual void Dispose (bool disposing) 287 { 288 if (_queryDict != null){ 289 if (disposing){ 290 _queryDict.Dispose (); 291 _queryDict = null; 292 } 293 } 294 } 295 ~SecRecord()296 ~SecRecord () 297 { 298 Dispose (false); 299 } 300 } 301 302 partial class SecItem { 303 public static readonly IntPtr ReturnRef; 304 public static readonly IntPtr MatchSearchList; 305 SecItem()306 static SecItem () 307 { 308 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0); 309 if (handle == IntPtr.Zero) 310 return; 311 312 try { 313 ReturnRef = CFObject.GetIntPtr (handle, "kSecReturnRef"); 314 MatchSearchList = CFObject.GetIntPtr (handle, "kSecMatchSearchList"); 315 } finally { 316 CFObject.dlclose (handle); 317 } 318 } 319 320 [DllImport (AppleTlsContext.SecurityLibrary)] SecItemCopyMatching( IntPtr query, out IntPtr result)321 internal extern static SecStatusCode SecItemCopyMatching (/* CFDictionaryRef */ IntPtr query, /* CFTypeRef* */ out IntPtr result); 322 } 323 324 static partial class SecClass { 325 326 public static readonly IntPtr Identity; 327 public static readonly IntPtr Certificate; 328 SecClass()329 static SecClass () 330 { 331 var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0); 332 if (handle == IntPtr.Zero) 333 return; 334 335 try { 336 Identity = CFObject.GetIntPtr (handle, "kSecClassIdentity"); 337 Certificate = CFObject.GetIntPtr (handle, "kSecClassCertificate"); 338 } finally { 339 CFObject.dlclose (handle); 340 } 341 } 342 FromSecKind(SecKind secKind)343 public static IntPtr FromSecKind (SecKind secKind) 344 { 345 switch (secKind){ 346 case SecKind.Identity: 347 return Identity; 348 case SecKind.Certificate: 349 return Certificate; 350 default: 351 throw new ArgumentException ("secKind"); 352 } 353 } 354 } 355 } 356 #endif 357