using System.Security.AccessControl; using System.Security.Cryptography; using System.Text; using System.Text.Json; namespace PatchProbe.Cli.Auth; internal sealed class DpapiCredentialStore : IDeviceCredentialStore { // %ProgramData%\PatchProbe\device.cred — machine-scoped, survives user changes internal static readonly string StorePath = Path.Combine( Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData), "PatchProbe", "device.cred"); public bool IsEnrolled => File.Exists(StorePath); public DeviceCredentials Load() { var cipher = File.ReadAllBytes(StorePath); var plain = ProtectedData.Unprotect(cipher, null, DataProtectionScope.LocalMachine); return JsonSerializer.Deserialize(Encoding.UTF8.GetString(plain)) ?? throw new InvalidOperationException("Credential file is corrupt or empty."); } public void Save(DeviceCredentials credentials) { var dir = Path.GetDirectoryName(StorePath)!; Directory.CreateDirectory(dir); var plain = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(credentials)); var cipher = ProtectedData.Protect(plain, null, DataProtectionScope.LocalMachine); File.WriteAllBytes(StorePath, cipher); RestrictToAdmins(StorePath); } public void Delete() { if (File.Exists(StorePath)) File.Delete(StorePath); } private static void RestrictToAdmins(string path) { var fi = new FileInfo(path); var acl = fi.GetAccessControl(); // Break inheritance; grant only SYSTEM and Administrators full control acl.SetAccessRuleProtection(isProtected: true, preserveInheritance: false); acl.AddAccessRule(new FileSystemAccessRule( "SYSTEM", FileSystemRights.FullControl, AccessControlType.Allow)); acl.AddAccessRule(new FileSystemAccessRule( "Administrators", FileSystemRights.FullControl, AccessControlType.Allow)); fi.SetAccessControl(acl); } }