[System] Use 'ObjCRuntimeInternal' as the namespace instead of 'ObjCRuntime'. (#4820)
[mono.git] / mcs / class / System / Mono.AppleTls / Items.cs
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
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
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
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
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
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
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
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)]
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
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)]
195                 extern static /* OSStatus */ SecStatusCode SecKeychainOpen (/* const char * */ IntPtr pathName, /* SecKeychainRef  _Nullable * */ out IntPtr keychain);
196
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
213                 internal static SecKeyChain OpenSystemRootCertificates ()
214                 {
215                         return Open ("/System/Library/Keychains/SystemRootCertificates.keychain");
216                 }
217
218                 ~SecKeyChain ()
219                 {
220                         Dispose (false);
221                 }
222
223                 public IntPtr Handle {
224                         get {
225                                 return handle;
226                         }
227                 }
228
229                 public void Dispose ()
230                 {
231                         Dispose (true);
232                         GC.SuppressFinalize (this);
233                 }
234
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;
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
268                 internal void SetValue (IntPtr key, IntPtr value)
269                 {
270                         _queryDict.SetValue (key, value);
271                 }
272
273                 public SecRecord (SecKind secKind)
274                 {
275                         var kind = SecClass.FromSecKind (secKind);
276                         _queryDict = CFMutableDictionary.Create ();
277                         _queryDict.SetValue (SecClassKey, kind);
278                 }
279
280                 public void Dispose ()
281                 {
282                         Dispose (true);
283                         GC.SuppressFinalize (this);
284                 }
285
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
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                 
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)]
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                 
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
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