借助网络配置,控制Modbus工具的日志显示,避免卡死界面
智能大石头 authored at 2021-12-11 22:09:19
4.58 KiB
XCoder
using System.Runtime.InteropServices;
using System.Text;

namespace XCoderMac.Services;

/// <summary>调用系统钥匙串,不经 shell/argv 暴露密码,密钥只用于数据库连接。</summary>
public static class MacKeychainSecrets
{
    private const string SecurityFramework = "/System/Library/Frameworks/Security.framework/Security";
    private const string CoreFoundationFramework = "/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation";
    private const int NotFound = -25300;
    public const string Service = "Newlife.XCoder.Database";

    [DllImport(SecurityFramework, EntryPoint = "SecKeychainAddGenericPassword")]
    private static extern int Add(IntPtr keychain, uint serviceLen, byte[] serviceName,
        uint accountLen, byte[] accountName, uint passwordLen, byte[] passwordData, out IntPtr itemRef);

    [DllImport(SecurityFramework, EntryPoint = "SecKeychainFindGenericPassword")]
    private static extern int Find(IntPtr keychainOrArray, uint serviceLen, byte[] serviceName,
        uint accountLen, byte[] accountName, out uint passwordLen, out IntPtr passwordData, out IntPtr itemRef);

    [DllImport(SecurityFramework, EntryPoint = "SecKeychainItemModifyAttributesAndData")]
    private static extern int Modify(IntPtr itemRef, IntPtr attrList, uint passwordLen, byte[] passwordData);

    [DllImport(SecurityFramework, EntryPoint = "SecKeychainItemDelete")]
    private static extern int Delete(IntPtr itemRef);

    [DllImport(SecurityFramework, EntryPoint = "SecKeychainItemFreeContent")]
    private static extern int FreeContent(IntPtr attrList, IntPtr passwordData);

    [DllImport(CoreFoundationFramework, EntryPoint = "CFRelease")]
    private static extern void Release(IntPtr handle);

    private static (byte[] Service, byte[] Account) Keys(string name) =>
        (Encoding.UTF8.GetBytes(Service), Encoding.UTF8.GetBytes(name));

    public static string? Read(string name)
    {
        var (service, account) = Keys(name);
        var status = Find(IntPtr.Zero, (uint)service.Length, service,
            (uint)account.Length, account, out var size, out var data, out var item);
        if (status == NotFound) return null;
        if (status != 0) throw new InvalidOperationException($"macOS 钥匙串读取失败 (OSStatus={status})");
        try
        {
            if (size == 0) return string.Empty;
            var buffer = new byte[checked((int)size)];
            Marshal.Copy(data, buffer, 0, buffer.Length);
            return Encoding.UTF8.GetString(buffer);
        }
        finally
        {
            if (data != IntPtr.Zero) FreeContent(IntPtr.Zero, data);
            if (item != IntPtr.Zero) Release(item);
        }
    }

    public static void Write(string name, string password)
    {
        var (service, account) = Keys(name);
        var bytes = Encoding.UTF8.GetBytes(password);
        var found = Find(IntPtr.Zero, (uint)service.Length, service,
            (uint)account.Length, account, out _, out var oldData, out var item);
        if (oldData != IntPtr.Zero) FreeContent(IntPtr.Zero, oldData);
        try
        {
            int status;
            if (found == NotFound)
            {
                status = Add(IntPtr.Zero, (uint)service.Length, service,
                    (uint)account.Length, account, (uint)bytes.Length, bytes, out var created);
                if (created != IntPtr.Zero) Release(created);
            }
            else if (found == 0)
                status = Modify(item, IntPtr.Zero, (uint)bytes.Length, bytes);
            else
                throw new InvalidOperationException($"macOS 钥匙串查询失败 (OSStatus={found})");

            if (status != 0) throw new InvalidOperationException($"macOS 钥匙串保存失败 (OSStatus={status})");
        }
        finally
        {
            if (item != IntPtr.Zero) Release(item);
            Array.Clear(bytes);
        }
    }

    public static void Remove(string name)
    {
        var (service, account) = Keys(name);
        var found = Find(IntPtr.Zero, (uint)service.Length, service,
            (uint)account.Length, account, out _, out var oldData, out var item);
        if (oldData != IntPtr.Zero) FreeContent(IntPtr.Zero, oldData);
        try
        {
            if (found == NotFound) return;
            if (found != 0) throw new InvalidOperationException($"macOS 钥匙串查询失败 (OSStatus={found})");
            var status = Delete(item);
            if (status != 0) throw new InvalidOperationException($"macOS 钥匙串删除失败 (OSStatus={status})");
        }
        finally
        {
            if (item != IntPtr.Zero) Release(item);
        }
    }
}