using System.Security.AccessControl; using System.Security.Principal; using System.Text.Json; using System.Text.Json.Serialization; using WxAgent.Core; namespace WxAgent.Windows; public static class DatabaseKeyStore { private static readonly JsonSerializerOptions JsonOptions = CreateJsonOptions(); public static string DefaultPath => Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "WxAgent", "database-keys.json"); public static async Task SaveAsync(IReadOnlyList accounts, string? path, CancellationToken cancellationToken) { var fullPath = Path.GetFullPath(path ?? DefaultPath); Directory.CreateDirectory(Path.GetDirectoryName(fullPath)!); var temporary = fullPath + ".tmp-" + Guid.NewGuid().ToString("N"); try { await using (new FileStream(temporary, FileMode.CreateNew, FileAccess.Write, FileShare.None)) { } RestrictToCurrentUser(temporary); await using (var stream = new FileStream(temporary, FileMode.Truncate, FileAccess.Write, FileShare.None, 81920, FileOptions.Asynchronous | FileOptions.WriteThrough)) { await JsonSerializer.SerializeAsync(stream, accounts, JsonOptions, cancellationToken); await stream.FlushAsync(cancellationToken); } File.Move(temporary, fullPath, true); RestrictToCurrentUser(fullPath); } finally { if (File.Exists(temporary)) { File.Delete(temporary); } } } public static async Task> LoadAsync(string? path, CancellationToken cancellationToken) { var fullPath = Path.GetFullPath(path ?? DefaultPath); cancellationToken.ThrowIfCancellationRequested(); if (!File.Exists(fullPath)) { return []; } await using var stream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.Read, 81920, FileOptions.Asynchronous); return await JsonSerializer.DeserializeAsync>(stream, JsonOptions, cancellationToken) ?? []; } private static JsonSerializerOptions CreateJsonOptions() { var options = new JsonSerializerOptions { WriteIndented = true }; options.Converters.Add(new JsonStringEnumConverter()); return options; } internal static void RestrictToCurrentUser(string path) { if (!OperatingSystem.IsWindows()) { return; } var identity = WindowsIdentity.GetCurrent().User ?? throw new InvalidOperationException("Current Windows user SID is unavailable."); var security = new FileSecurity(); security.SetOwner(identity); security.SetAccessRuleProtection(isProtected: true, preserveInheritance: false); security.AddAccessRule(new FileSystemAccessRule(identity, FileSystemRights.FullControl, AccessControlType.Allow)); new FileInfo(path).SetAccessControl(security); } }