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