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