File: SafeKeychainHandle.cs
Web Access
Project: src\src\System.Private.ServiceModel\tests\Common\Infrastructure\Infrastructure.Common.csproj (Infrastructure.Common)
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
 
using System;
using System.Text;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;
 
namespace Infrastructure.Common
{
    // This class facilitates using a private keychain on OSX
    public class SafeKeychainHandle : SafeHandle
    {
        private const string CoreFoundation = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
        private const string SecurityFramework = "/System/Library/Frameworks/Security.framework/Security";
 
        [DllImport(SecurityFramework)]
        private static extern int SecKeychainUnlock(SafeKeychainHandle handle, int passphraseLength, byte[] passphraseUtf8, bool usePassword);
 
        [DllImport(SecurityFramework)]
        private static extern int SecKeychainOpen(string path, out SafeKeychainHandle keychain);
 
        [DllImport(SecurityFramework)]
        private static extern int SecKeychainCreate(string pathName, int passphraseLength, byte[] passphraseUtf8, bool promptUser, IntPtr initialAccessNull, out SafeKeychainHandle keychain);
 
        [DllImport(SecurityFramework)]
        private static extern int SecKeychainDelete(SafeKeychainHandle handle);
 
        [DllImport(CoreFoundation)]
        private static extern void CFRelease(IntPtr ptr);
 
        internal SafeKeychainHandle()
            : base(IntPtr.Zero, ownsHandle: true)
        {
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        protected override bool ReleaseHandle()
        {
            CFRelease(handle);
            SetHandle(IntPtr.Zero);
            return true;
        }
 
        public override bool IsInvalid => handle == IntPtr.Zero;
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static SafeKeychainHandle Create(string pathName, string passphrase)
        {
            byte[] utf8Passphrase = Encoding.UTF8.GetBytes(passphrase);
            SafeKeychainHandle keychain;
            int osStatus = SecKeychainCreate(pathName, utf8Passphrase.Length, utf8Passphrase, false, IntPtr.Zero, out keychain);
 
            if (osStatus != 0)
            {
                keychain.Dispose();
                throw new InvalidOperationException($"OSStatus={osStatus}");
            }
 
            return keychain;
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static SafeKeychainHandle Open(string pathName, string passphrase)
        {
            SafeKeychainHandle keychain;
            int osStatus = SecKeychainOpen(pathName, out keychain);
 
            if (osStatus != 0)
            {
                keychain.Dispose();
                throw new InvalidOperationException($"OSStatus={osStatus}");
            }
 
            if(!string.IsNullOrEmpty(passphrase))
            {
                byte[] utf8Passphrase = Encoding.UTF8.GetBytes(passphrase);
                osStatus = SecKeychainUnlock(keychain, utf8Passphrase.Length, utf8Passphrase, true);
 
                if (osStatus != 0)
                {
                    keychain.Dispose();
                    throw new InvalidOperationException($"OSStatus={osStatus}");
                }
            }
 
            return keychain;
        }
 
        [MethodImpl(MethodImplOptions.NoInlining)]
        public static void Delete(SafeKeychainHandle handle)
        {
            if (handle.IsInvalid)
                return;
 
            int osStatus = SecKeychainDelete(handle);
            handle.Dispose();
 
            if (osStatus != 0)
            {
                throw new InvalidOperationException($"OSStatus={osStatus}");
            }
        }
    }
}