using System.Runtime.InteropServices; using System.Text; namespace Lskj.AgentPet.Host; internal static class WindowsCredentialStore { internal const string DefaultAstrBotTarget = "Langsu.Lserp.AstrBot.ApiKey"; private const uint CredentialTypeGeneric = 1; private const int MaximumSecretBytes = 2048; public static void PopulateAstrBotApiKey(IDictionary environment) { ArgumentNullException.ThrowIfNull(environment); if (environment.TryGetValue("LSERP_ASTRBOT_API_KEY", out string? configured) && !string.IsNullOrWhiteSpace(configured)) return; string target = environment.TryGetValue("LSERP_ASTRBOT_CREDENTIAL_TARGET", out string? custom) && !string.IsNullOrWhiteSpace(custom) ? custom.Trim() : DefaultAstrBotTarget; if (target.Length is < 8 or > 128) return; string? secret = ReadGenericSecret(target); if (!string.IsNullOrWhiteSpace(secret)) environment["LSERP_ASTRBOT_API_KEY"] = secret; } private static string? ReadGenericSecret(string target) { if (!OperatingSystem.IsWindows()) return null; if (!CredRead(target, CredentialTypeGeneric, 0, out nint address) || address == 0) return null; try { NativeCredential credential = Marshal.PtrToStructure(address); if (credential.CredentialBlob == 0 || credential.CredentialBlobSize is 0 or > MaximumSecretBytes) return null; byte[] bytes = new byte[credential.CredentialBlobSize]; Marshal.Copy(credential.CredentialBlob, bytes, 0, bytes.Length); return Encoding.Unicode.GetString(bytes).TrimEnd('\0').Trim(); } finally { CredFree(address); } } [DllImport("advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool CredRead( string target, uint type, int reservedFlag, out nint credential); [DllImport("advapi32.dll", SetLastError = false)] private static extern void CredFree(nint buffer); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] private struct NativeCredential { public uint Flags; public uint Type; public nint TargetName; public nint Comment; public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; public uint CredentialBlobSize; public nint CredentialBlob; public uint Persist; public uint AttributeCount; public nint Attributes; public nint TargetAlias; public nint UserName; } }