Merge pull request #4503 from BrzVlad/fix-appdomain-unload
[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 ObjCRuntime;
38 using Mono.Net;
39
40 namespace Mono.AppleTls {
41
42         enum SecKind {
43                 Identity
44         }
45
46         static class SecKeyChain {
47                 static readonly IntPtr MatchLimitAll;
48                 static readonly IntPtr MatchLimitOne;
49                 static readonly IntPtr MatchLimit;
50
51                 static SecKeyChain ()
52                 {
53                         var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
54                         if (handle == IntPtr.Zero)
55                                 return;
56
57                         try {           
58                                 MatchLimit = CFObject.GetIntPtr (handle, "kSecMatchLimit");
59                                 MatchLimitAll = CFObject.GetIntPtr (handle, "kSecMatchLimitAll");
60                                 MatchLimitOne = CFObject.GetIntPtr (handle, "kSecMatchLimitOne");
61                         } finally {
62                                 CFObject.dlclose (handle);
63                         }
64                 }
65
66                 public static SecIdentity FindIdentity (SecCertificate certificate, bool throwOnError = false)
67                 {
68                         if (certificate == null)
69                                 throw new ArgumentNullException ("certificate");
70                         var identity = FindIdentity (cert => SecCertificate.Equals (certificate, cert));
71                         if (!throwOnError || identity != null)
72                                 return identity;
73
74                         throw new InvalidOperationException (string.Format ("Could not find SecIdentity for certificate '{0}' in keychain.", certificate.SubjectSummary));
75                 }
76
77                 static SecIdentity FindIdentity (Predicate<SecCertificate> filter)
78                 {
79                         /*
80                          * Unfortunately, SecItemCopyMatching() does not allow any search
81                          * filters when looking up an identity.
82                          * 
83                          * The following lookup will return all identities from the keychain -
84                          * we then need need to find the right one.
85                          */
86                         using (var record = new SecRecord (SecKind.Identity)) {
87                                 SecStatusCode status;
88                                 var result = SecKeyChain.QueryAsReference (record, -1, out status);
89                                 if (status != SecStatusCode.Success || result == null)
90                                         return null;
91
92                                 for (int i = 0; i < result.Length; i++) {
93                                         var identity = (SecIdentity)result [i];
94                                         if (filter (identity.Certificate))
95                                                 return identity;
96                                 }
97                         }
98
99                         return null;
100                 }
101                 
102                 public static INativeObject[] QueryAsReference (SecRecord query, int max, out SecStatusCode result)
103                 {
104                         if (query == null){
105                                 result = SecStatusCode.Param;
106                                 return null;
107                         }
108
109                         using (var copy = query.queryDict.MutableCopy ()) {
110                                 copy.SetValue (CFBoolean.True.Handle, SecItem.ReturnRef);
111                                 SetLimit (copy, max);
112
113                                 IntPtr ptr;
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);
124                                                 else
125                                                         throw new Exception (String.Format ("Unexpected type: 0x{0:x}", cfType));
126                                         });
127                                         return array;
128                                 }
129                                 return null;
130                         }
131                 }
132
133                 static CFNumber SetLimit (CFMutableDictionary dict, int max)
134                 {
135                         CFNumber n = null;
136                         IntPtr val;
137                         if (max == -1)
138                                 val = MatchLimitAll;
139                         else if (max == 1)
140                                 val = MatchLimitOne;
141                         else {
142                                 n = CFNumber.FromInt32 (max);
143                                 val = n.Handle;
144                         }
145                         
146                         dict.SetValue (val, SecKeyChain.MatchLimit);
147                         return n;
148                 }
149         }
150         
151         class SecRecord : IDisposable {
152
153                 static readonly IntPtr SecClassKey;
154                 static SecRecord ()
155                 {
156                         var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
157                         if (handle == IntPtr.Zero)
158                                 return;
159
160                         try {           
161                                 SecClassKey = CFObject.GetIntPtr (handle, "kSecClassKey");
162                         } finally {
163                                 CFObject.dlclose (handle);
164                         }
165                 }
166
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 
173                 { 
174                         get {
175                                 return _queryDict;
176                         }
177                         set {
178                                 _queryDict = value != null ? value.Copy () : null;
179                         }
180                 }
181
182                 public SecRecord (SecKind secKind)
183                 {
184                         var kind = SecClass.FromSecKind (secKind);
185                         queryDict = CFDictionary.FromObjectAndKey (kind, SecClassKey);
186                 }
187
188                 public void Dispose ()
189                 {
190                         Dispose (true);
191                         GC.SuppressFinalize (this);
192                 }
193
194                 protected virtual void Dispose (bool disposing)
195                 {
196                         if (queryDict != null){
197                                 if (disposing){
198                                         queryDict.Dispose ();
199                                         queryDict = null;
200                                 }
201                         }
202                 }
203
204                 ~SecRecord ()
205                 {
206                         Dispose (false);
207                 }
208         }
209         
210         partial class SecItem {
211                 public static readonly IntPtr ReturnRef;
212                 
213                 static SecItem ()
214                 {
215                         var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
216                         if (handle == IntPtr.Zero)
217                                 return;
218
219                         try {           
220                                 ReturnRef = CFObject.GetIntPtr (handle, "kSecReturnRef");
221                         } finally {
222                                 CFObject.dlclose (handle);
223                         }
224                 }
225
226                 [DllImport (AppleTlsContext.SecurityLibrary)]
227                 internal extern static SecStatusCode SecItemCopyMatching (/* CFDictionaryRef */ IntPtr query, /* CFTypeRef* */ out IntPtr result);
228         }
229
230         static partial class SecClass {
231         
232                 public static readonly IntPtr Identity;
233                 
234                 static SecClass ()
235                 {
236                         var handle = CFObject.dlopen (AppleTlsContext.SecurityLibrary, 0);
237                         if (handle == IntPtr.Zero)
238                                 return;
239
240                         try {           
241                                 Identity = CFObject.GetIntPtr (handle, "kSecClassIdentity");
242                         } finally {
243                                 CFObject.dlclose (handle);
244                         }
245                 }
246
247                 public static IntPtr FromSecKind (SecKind secKind)
248                 {
249                         switch (secKind){
250                         case SecKind.Identity:
251                                 return Identity;
252                         default:
253                                 throw new ArgumentException ("secKind");
254                         }
255                 }
256         }
257 }
258 #endif