From 61fedfc0e05a9502dac97e783091d1577331b960 Mon Sep 17 00:00:00 2001 From: NextTurn <45985406+nxtn@users.noreply.github.com> Date: Mon, 7 Sep 2020 00:00:00 +0800 Subject: [PATCH] Save credentials --- src/WinSW.Core/Native/ConsoleEx.cs | 1 + src/WinSW.Core/Native/CredentialApis.cs | 31 +++++++++++ src/WinSW.Core/Native/Credentials.cs | 58 ++++++++++++++++++-- src/WinSW.Core/Native/FileTime.cs | 12 ++++ src/WinSW.Core/Native/RegistryApis.cs | 10 +--- src/WinSW.Core/Util/RegistryKeyExtensions.cs | 2 +- src/WinSW/Program.cs | 23 ++++++-- 7 files changed, 117 insertions(+), 20 deletions(-) create mode 100644 src/WinSW.Core/Native/FileTime.cs diff --git a/src/WinSW.Core/Native/ConsoleEx.cs b/src/WinSW.Core/Native/ConsoleEx.cs index b67f3f3..49725ab 100644 --- a/src/WinSW.Core/Native/ConsoleEx.cs +++ b/src/WinSW.Core/Native/ConsoleEx.cs @@ -75,6 +75,7 @@ namespace WinSW.Native } else if (key == '\r') { + Write(consoleOutput, Environment.NewLine); break; } else if (key == '\b') diff --git a/src/WinSW.Core/Native/CredentialApis.cs b/src/WinSW.Core/Native/CredentialApis.cs index 0fb06d3..13faaf6 100644 --- a/src/WinSW.Core/Native/CredentialApis.cs +++ b/src/WinSW.Core/Native/CredentialApis.cs @@ -7,7 +7,15 @@ namespace WinSW.Native { internal static class CredentialApis { + internal const uint CRED_PERSIST_LOCAL_MACHINE = 2; + + internal const uint CRED_TYPE_GENERIC = 1; + internal const uint CREDUIWIN_GENERIC = 0x00000001; + internal const uint CREDUIWIN_CHECKBOX = 0x00000002; + + [DllImport(Libraries.Advapi32, SetLastError = false)] + internal static extern unsafe void CredFree(CREDENTIALW* buffer); [DllImport(Libraries.CredUI, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CredPackAuthenticationBufferW")] internal static extern bool CredPackAuthenticationBuffer( @@ -17,6 +25,9 @@ namespace WinSW.Native IntPtr packedCredentials, ref int packedCredentialsSize); + [DllImport(Libraries.Advapi32, SetLastError = true, CharSet = CharSet.Unicode)] + internal static extern unsafe bool CredReadW(string targetName, uint type, uint flags, out CREDENTIALW* credential); + [DllImport(Libraries.CredUI, SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "CredUIPromptForWindowsCredentialsW")] internal static extern int CredUIPromptForWindowsCredentials( in CREDUI_INFO uiInfo, @@ -41,6 +52,26 @@ namespace WinSW.Native string? password, ref int maxPassword); + [DllImport(Libraries.Advapi32, SetLastError = true)] + internal static extern bool CredWriteW(in CREDENTIALW credential, uint flags); + + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] + internal unsafe struct CREDENTIALW + { + internal uint Flags; + internal uint Type; + internal char* TargetName; + internal char* Comment; + internal FileTime LastWritten; + internal int CredentialBlobSize; + internal IntPtr CredentialBlob; + internal uint Persist; + internal uint AttributeCount; + internal uint Attributes; + internal char* TargetAlias; + internal char* UserName; + } + [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] internal struct CREDUI_INFO { diff --git a/src/WinSW.Core/Native/Credentials.cs b/src/WinSW.Core/Native/Credentials.cs index fd2fe94..31b3fc3 100644 --- a/src/WinSW.Core/Native/Credentials.cs +++ b/src/WinSW.Core/Native/Credentials.cs @@ -7,6 +7,52 @@ namespace WinSW.Native { internal static class Credentials { + internal static unsafe bool Load(string targetName, out string? userName, out string? password) + { + if (!CredReadW(targetName, CRED_TYPE_GENERIC, 0, out var credential)) + { + userName = null; + password = null; + return false; + } + + try + { + userName = Marshal.PtrToStringUni((IntPtr)credential->UserName); + password = Marshal.PtrToStringUni(credential->CredentialBlob, credential->CredentialBlobSize); + return true; + } + finally + { + CredFree(credential); + } + } + + internal static unsafe void Save(string targetName, string? userName, string? password) + { +#pragma warning disable SA1519 // Braces should not be omitted from multi-line child statement + fixed (char* targetNamePtr = targetName) + fixed (char* userNamePtr = userName) + fixed (char* passwordPtr = password) + { + var credential = new CREDENTIALW + { + Type = CRED_TYPE_GENERIC, + TargetName = targetNamePtr, + CredentialBlobSize = password?.Length * sizeof(char) ?? 0, + CredentialBlob = (IntPtr)passwordPtr, + Persist = CRED_PERSIST_LOCAL_MACHINE, + UserName = userNamePtr, + }; + + if (!CredWriteW(credential, 0)) + { + Throw.Command.Win32Exception("Failed to save credential."); + } + } +#pragma warning restore SA1519 // Braces should not be omitted from multi-line child statement + } + internal static void PromptForCredentialsConsole(ref string? userName, ref string? password) { using var consoleOutput = ConsoleEx.OpenConsoleOutput(); @@ -24,7 +70,7 @@ namespace WinSW.Native } } - internal static void PromptForCredentialsDialog(ref string? userName, ref string? password, string caption, string message) + internal static void PromptForCredentialsDialog(ref string? userName, ref string? password, string caption, string message, ref bool save) { userName ??= string.Empty; password ??= string.Empty; @@ -52,12 +98,11 @@ namespace WinSW.Native var info = new CREDUI_INFO { - Size = Marshal.SizeOf(typeof(CREDUI_INFO)), + Size = Marshal.SizeOf(), CaptionText = caption, MessageText = message, }; uint authPackage = 0; - bool save = false; int error = CredUIPromptForWindowsCredentials( info, 0, @@ -67,10 +112,15 @@ namespace WinSW.Native out var outBuffer, out uint outBufferSize, ref save, - CREDUIWIN_GENERIC); + CREDUIWIN_GENERIC | CREDUIWIN_CHECKBOX); if (error != Errors.ERROR_SUCCESS) { + if (error == Errors.ERROR_CANCELLED) + { + Throw.Command.Win32Exception(error); + } + throw new Win32Exception(error); } diff --git a/src/WinSW.Core/Native/FileTime.cs b/src/WinSW.Core/Native/FileTime.cs new file mode 100644 index 0000000..ab6139e --- /dev/null +++ b/src/WinSW.Core/Native/FileTime.cs @@ -0,0 +1,12 @@ +using System; + +namespace WinSW.Native +{ + internal struct FileTime + { + internal int LowDateTime; + internal int HighDateTime; + + public DateTime ToDateTime() => DateTime.FromFileTime(((long)this.HighDateTime << 32) + this.LowDateTime); + } +} diff --git a/src/WinSW.Core/Native/RegistryApis.cs b/src/WinSW.Core/Native/RegistryApis.cs index 81ced09..e9bb35f 100644 --- a/src/WinSW.Core/Native/RegistryApis.cs +++ b/src/WinSW.Core/Native/RegistryApis.cs @@ -18,14 +18,6 @@ namespace WinSW.Native int* maxValueNameLength, int* maxValueLength, int* securityDescriptorLength, - out FILETIME lastWriteTime); - - internal struct FILETIME - { - internal int LowDateTime; - internal int HighDateTime; - - public long ToTicks() => ((long)this.HighDateTime << 32) + this.LowDateTime; - } + out FileTime lastWriteTime); } } diff --git a/src/WinSW.Core/Util/RegistryKeyExtensions.cs b/src/WinSW.Core/Util/RegistryKeyExtensions.cs index 759e48d..9f78494 100644 --- a/src/WinSW.Core/Util/RegistryKeyExtensions.cs +++ b/src/WinSW.Core/Util/RegistryKeyExtensions.cs @@ -16,7 +16,7 @@ namespace WinSW.Util Throw.Command.Win32Exception(error, "Failed to query registry key."); } - return DateTime.FromFileTime(lastWriteTime.ToTicks()); + return lastWriteTime.ToDateTime(); } } } diff --git a/src/WinSW/Program.cs b/src/WinSW/Program.cs index 5086f26..fd4532f 100644 --- a/src/WinSW/Program.cs +++ b/src/WinSW/Program.cs @@ -434,6 +434,7 @@ namespace WinSW Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service."); } + bool saveCredential = false; if (config.HasServiceAccount()) { username = config.ServiceAccountUserName ?? username; @@ -444,11 +445,16 @@ namespace WinSW switch (config.ServiceAccountPrompt) { case "dialog": - Credentials.PromptForCredentialsDialog( - ref username, - ref password, - "Windows Service Wrapper", - "Enter the service account credentials"); + if (!Credentials.Load($"WinSW:{config.Name}", out username, out password)) + { + Credentials.PromptForCredentialsDialog( + ref username, + ref password, + "Windows Service Wrapper", + "Enter the service account credentials", + ref saveCredential); + } + break; case "console": @@ -472,6 +478,11 @@ namespace WinSW username, password); + if (saveCredential) + { + Credentials.Save($"WinSW:{config.Name}", username, password); + } + string description = config.Description; if (description.Length != 0) { @@ -508,7 +519,7 @@ namespace WinSW EventLog.CreateEventSource(eventLogSource, "Application"); } - Log.Info($"Service '{config.Format()}' was installed successfully."); + Log.Info($"Service '{config.Format()}' was installed successfully."); } void Uninstall(string? pathToConfig, bool noElevate)