1 // Licensed to the .NET Foundation under one or more agreements.
2 // The .NET Foundation licenses this file to you under the MIT license.
3 // See the LICENSE file in the project root for more information.
4 
5 using System;
6 using System.Collections.Generic;
7 using System.Diagnostics;
8 using System.IO;
9 using System.Runtime.InteropServices;
10 using System.Security.Cryptography;
11 using System.Security.Cryptography.Apple;
12 
13 using Microsoft.Win32.SafeHandles;
14 
15 internal static partial class Interop
16 {
17     internal static partial class AppleCrypto
18     {
19         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainItemCopyKeychain( IntPtr item, out SafeKeychainHandle keychain)20         private static extern int AppleCryptoNative_SecKeychainItemCopyKeychain(
21             IntPtr item,
22             out SafeKeychainHandle keychain);
23 
24         [DllImport(Libraries.AppleCryptoNative, EntryPoint = "AppleCryptoNative_SecKeychainCreate")]
AppleCryptoNative_SecKeychainCreateTemporary( string path, int utf8PassphraseLength, byte[] utf8Passphrase, out SafeTemporaryKeychainHandle keychain)25         private static extern int AppleCryptoNative_SecKeychainCreateTemporary(
26             string path,
27             int utf8PassphraseLength,
28             byte[] utf8Passphrase,
29             out SafeTemporaryKeychainHandle keychain);
30 
31         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainDelete(IntPtr keychain)32         private static extern int AppleCryptoNative_SecKeychainDelete(IntPtr keychain);
33 
34         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainCopyDefault(out SafeKeychainHandle keychain)35         private static extern int AppleCryptoNative_SecKeychainCopyDefault(out SafeKeychainHandle keychain);
36 
37         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainOpen( string keychainPath, out SafeKeychainHandle keychain)38         private static extern int AppleCryptoNative_SecKeychainOpen(
39             string keychainPath,
40             out SafeKeychainHandle keychain);
41 
42         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SetKeychainNeverLock(SafeKeychainHandle keychain)43         private static extern int AppleCryptoNative_SetKeychainNeverLock(SafeKeychainHandle keychain);
44 
45         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainEnumerateCerts( SafeKeychainHandle keychain, out SafeCFArrayHandle matches, out int pOSStatus)46         private static extern int AppleCryptoNative_SecKeychainEnumerateCerts(
47             SafeKeychainHandle keychain,
48             out SafeCFArrayHandle matches,
49             out int pOSStatus);
50 
51         [DllImport(Libraries.AppleCryptoNative)]
AppleCryptoNative_SecKeychainEnumerateIdentities( SafeKeychainHandle keychain, out SafeCFArrayHandle matches, out int pOSStatus)52         private static extern int AppleCryptoNative_SecKeychainEnumerateIdentities(
53             SafeKeychainHandle keychain,
54             out SafeCFArrayHandle matches,
55             out int pOSStatus);
56 
SecKeychainItemCopyKeychain(SafeKeychainItemHandle item)57         internal static SafeKeychainHandle SecKeychainItemCopyKeychain(SafeKeychainItemHandle item)
58         {
59             bool addedRef = false;
60 
61             try
62             {
63                 item.DangerousAddRef(ref addedRef);
64                 var handle = SecKeychainItemCopyKeychain(item.DangerousGetHandle());
65                 return handle;
66             }
67             finally
68             {
69                 if (addedRef)
70                 {
71                     item.DangerousRelease();
72                 }
73             }
74         }
75 
SecKeychainItemCopyKeychain(IntPtr item)76         internal static SafeKeychainHandle SecKeychainItemCopyKeychain(IntPtr item)
77         {
78             SafeKeychainHandle keychain;
79             int osStatus = AppleCryptoNative_SecKeychainItemCopyKeychain(item, out keychain);
80 
81             // A whole lot of NULL is expected from this.
82             // Any key or cert which isn't keychain-backed, and this is the primary way we'd find that out.
83             if (keychain.IsInvalid)
84             {
85                 GC.SuppressFinalize(keychain);
86             }
87 
88             if (osStatus == 0)
89             {
90                 return keychain;
91             }
92 
93             throw CreateExceptionForOSStatus(osStatus);
94         }
95 
SecKeychainCopyDefault()96         internal static SafeKeychainHandle SecKeychainCopyDefault()
97         {
98             SafeKeychainHandle keychain;
99             int osStatus = AppleCryptoNative_SecKeychainCopyDefault(out keychain);
100 
101             if (osStatus == 0)
102             {
103                 return keychain;
104             }
105 
106             keychain.Dispose();
107             throw CreateExceptionForOSStatus(osStatus);
108         }
109 
SecKeychainOpen(string keychainPath)110         internal static SafeKeychainHandle SecKeychainOpen(string keychainPath)
111         {
112             SafeKeychainHandle keychain;
113             int osStatus = AppleCryptoNative_SecKeychainOpen(keychainPath, out keychain);
114 
115             if (osStatus == 0)
116             {
117                 return keychain;
118             }
119 
120             keychain.Dispose();
121             throw CreateExceptionForOSStatus(osStatus);
122         }
123 
KeychainEnumerateCerts(SafeKeychainHandle keychainHandle)124         internal static SafeCFArrayHandle KeychainEnumerateCerts(SafeKeychainHandle keychainHandle)
125         {
126             SafeCFArrayHandle matches;
127             int osStatus;
128             int result = AppleCryptoNative_SecKeychainEnumerateCerts(keychainHandle, out matches, out osStatus);
129 
130             if (result == 1)
131             {
132                 return matches;
133             }
134 
135             matches.Dispose();
136 
137             if (result == 0)
138                 throw CreateExceptionForOSStatus(osStatus);
139 
140             Debug.Fail($"Unexpected result from AppleCryptoNative_SecKeychainEnumerateCerts: {result}");
141             throw new CryptographicException();
142         }
143 
KeychainEnumerateIdentities(SafeKeychainHandle keychainHandle)144         internal static SafeCFArrayHandle KeychainEnumerateIdentities(SafeKeychainHandle keychainHandle)
145         {
146             SafeCFArrayHandle matches;
147             int osStatus;
148             int result = AppleCryptoNative_SecKeychainEnumerateIdentities(keychainHandle, out matches, out osStatus);
149 
150             if (result == 1)
151             {
152                 return matches;
153             }
154 
155             matches.Dispose();
156 
157             if (result == 0)
158                 throw CreateExceptionForOSStatus(osStatus);
159 
160             Debug.Fail($"Unexpected result from AppleCryptoNative_SecKeychainEnumerateCerts: {result}");
161             throw new CryptographicException();
162         }
163 
CreateTemporaryKeychain()164         internal static SafeTemporaryKeychainHandle CreateTemporaryKeychain()
165         {
166             string tmpKeychainPath = Path.Combine(
167                 Path.GetTempPath(),
168                 Guid.NewGuid().ToString("N") + ".keychain");
169 
170             // Use a distinct GUID so that if a keychain is abandoned it isn't recoverable.
171             string tmpKeychainPassphrase = Guid.NewGuid().ToString("N");
172 
173             byte[] utf8Passphrase = System.Text.Encoding.UTF8.GetBytes(tmpKeychainPassphrase);
174 
175             SafeTemporaryKeychainHandle keychain;
176 
177             int osStatus = AppleCryptoNative_SecKeychainCreateTemporary(
178                 tmpKeychainPath,
179                 utf8Passphrase.Length,
180                 utf8Passphrase,
181                 out keychain);
182 
183             SafeTemporaryKeychainHandle.TrackKeychain(keychain);
184 
185             if (osStatus == 0)
186             {
187                 osStatus = AppleCryptoNative_SetKeychainNeverLock(keychain);
188             }
189 
190             if (osStatus != 0)
191             {
192                 keychain.Dispose();
193                 throw CreateExceptionForOSStatus(osStatus);
194             }
195 
196             return keychain;
197         }
198 
SecKeychainDelete(IntPtr handle, bool throwOnError=true)199         internal static void SecKeychainDelete(IntPtr handle, bool throwOnError=true)
200         {
201             int osStatus = AppleCryptoNative_SecKeychainDelete(handle);
202 
203             if (throwOnError && osStatus != 0)
204             {
205                 throw CreateExceptionForOSStatus(osStatus);
206             }
207         }
208     }
209 }
210 
211 namespace System.Security.Cryptography.Apple
212 {
213     internal class SafeKeychainItemHandle : SafeHandle
214     {
SafeKeychainItemHandle()215         internal SafeKeychainItemHandle()
216             : base(IntPtr.Zero, ownsHandle: true)
217         {
218         }
219 
ReleaseHandle()220         protected override bool ReleaseHandle()
221         {
222             SafeTemporaryKeychainHandle.UntrackItem(handle);
223             Interop.CoreFoundation.CFRelease(handle);
224             SetHandle(IntPtr.Zero);
225             return true;
226         }
227 
228         public override bool IsInvalid => handle == IntPtr.Zero;
229     }
230 
231     internal class SafeKeychainHandle : SafeHandle
232     {
SafeKeychainHandle()233         internal SafeKeychainHandle()
234             : base(IntPtr.Zero, ownsHandle: true)
235         {
236         }
237 
SafeKeychainHandle(IntPtr handle)238         internal SafeKeychainHandle(IntPtr handle)
239             : base(handle, ownsHandle: true)
240         {
241         }
242 
ReleaseHandle()243         protected override bool ReleaseHandle()
244         {
245             Interop.CoreFoundation.CFRelease(handle);
246             SetHandle(IntPtr.Zero);
247             return true;
248         }
249 
250         public override bool IsInvalid => handle == IntPtr.Zero;
251     }
252 
253     internal sealed class SafeTemporaryKeychainHandle : SafeKeychainHandle
254     {
255         private static readonly Dictionary<IntPtr, SafeTemporaryKeychainHandle> s_lookup =
256             new Dictionary<IntPtr, SafeTemporaryKeychainHandle>();
257 
SafeTemporaryKeychainHandle()258         internal SafeTemporaryKeychainHandle()
259         {
260         }
261 
ReleaseHandle()262         protected override bool ReleaseHandle()
263         {
264             lock (s_lookup)
265             {
266                 s_lookup.Remove(handle);
267             }
268 
269             Interop.AppleCrypto.SecKeychainDelete(handle, throwOnError: false);
270             return base.ReleaseHandle();
271         }
272 
Dispose(bool disposing)273         protected override void Dispose(bool disposing)
274         {
275             if (disposing && SafeHandleCache<SafeTemporaryKeychainHandle>.IsCachedInvalidHandle(this))
276             {
277                 return;
278             }
279 
280             base.Dispose(disposing);
281         }
282 
283         public static SafeTemporaryKeychainHandle InvalidHandle =>
284             SafeHandleCache<SafeTemporaryKeychainHandle>.GetInvalidHandle(() => new SafeTemporaryKeychainHandle());
285 
TrackKeychain(SafeTemporaryKeychainHandle toTrack)286         internal static void TrackKeychain(SafeTemporaryKeychainHandle toTrack)
287         {
288             if (toTrack.IsInvalid)
289             {
290                 return;
291             }
292 
293             lock (s_lookup)
294             {
295                 Debug.Assert(!s_lookup.ContainsKey(toTrack.handle));
296 
297                 s_lookup[toTrack.handle] = toTrack;
298             }
299         }
300 
TrackItem(SafeKeychainItemHandle keychainItem)301         internal static void TrackItem(SafeKeychainItemHandle keychainItem)
302         {
303             if (keychainItem.IsInvalid)
304                 return;
305 
306             using (SafeKeychainHandle keychain = Interop.AppleCrypto.SecKeychainItemCopyKeychain(keychainItem))
307             {
308                 if (keychain.IsInvalid)
309                 {
310                     return;
311                 }
312 
313                 lock (s_lookup)
314                 {
315                     SafeTemporaryKeychainHandle temporaryHandle;
316 
317                     if (s_lookup.TryGetValue(keychain.DangerousGetHandle(), out temporaryHandle))
318                     {
319                         bool ignored = false;
320                         temporaryHandle.DangerousAddRef(ref ignored);
321                     }
322                 }
323             }
324         }
325 
UntrackItem(IntPtr keychainItem)326         internal static void UntrackItem(IntPtr keychainItem)
327         {
328             using (SafeKeychainHandle keychain = Interop.AppleCrypto.SecKeychainItemCopyKeychain(keychainItem))
329             {
330                 if (keychain.IsInvalid)
331                 {
332                     return;
333                 }
334 
335                 lock (s_lookup)
336                 {
337                     SafeTemporaryKeychainHandle temporaryHandle;
338 
339                     if (s_lookup.TryGetValue(keychain.DangerousGetHandle(), out temporaryHandle))
340                     {
341                         temporaryHandle.DangerousRelease();
342                     }
343                 }
344             }
345         }
346     }
347 }
348