Save credentials

pull/762/head
NextTurn 2020-09-07 00:00:00 +08:00 committed by Next Turn
parent 576cf6be55
commit 61fedfc0e0
7 changed files with 117 additions and 20 deletions

View File

@ -75,6 +75,7 @@ namespace WinSW.Native
} }
else if (key == '\r') else if (key == '\r')
{ {
Write(consoleOutput, Environment.NewLine);
break; break;
} }
else if (key == '\b') else if (key == '\b')

View File

@ -7,7 +7,15 @@ namespace WinSW.Native
{ {
internal static class CredentialApis 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_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")] [DllImport(Libraries.CredUI, SetLastError = true, CharSet = CharSet.Unicode, EntryPoint = "CredPackAuthenticationBufferW")]
internal static extern bool CredPackAuthenticationBuffer( internal static extern bool CredPackAuthenticationBuffer(
@ -17,6 +25,9 @@ namespace WinSW.Native
IntPtr packedCredentials, IntPtr packedCredentials,
ref int packedCredentialsSize); 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")] [DllImport(Libraries.CredUI, SetLastError = false, CharSet = CharSet.Unicode, EntryPoint = "CredUIPromptForWindowsCredentialsW")]
internal static extern int CredUIPromptForWindowsCredentials( internal static extern int CredUIPromptForWindowsCredentials(
in CREDUI_INFO uiInfo, in CREDUI_INFO uiInfo,
@ -41,6 +52,26 @@ namespace WinSW.Native
string? password, string? password,
ref int maxPassword); 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)] [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
internal struct CREDUI_INFO internal struct CREDUI_INFO
{ {

View File

@ -7,6 +7,52 @@ namespace WinSW.Native
{ {
internal static class Credentials 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) internal static void PromptForCredentialsConsole(ref string? userName, ref string? password)
{ {
using var consoleOutput = ConsoleEx.OpenConsoleOutput(); 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; userName ??= string.Empty;
password ??= string.Empty; password ??= string.Empty;
@ -52,12 +98,11 @@ namespace WinSW.Native
var info = new CREDUI_INFO var info = new CREDUI_INFO
{ {
Size = Marshal.SizeOf(typeof(CREDUI_INFO)), Size = Marshal.SizeOf<CREDUI_INFO>(),
CaptionText = caption, CaptionText = caption,
MessageText = message, MessageText = message,
}; };
uint authPackage = 0; uint authPackage = 0;
bool save = false;
int error = CredUIPromptForWindowsCredentials( int error = CredUIPromptForWindowsCredentials(
info, info,
0, 0,
@ -67,10 +112,15 @@ namespace WinSW.Native
out var outBuffer, out var outBuffer,
out uint outBufferSize, out uint outBufferSize,
ref save, ref save,
CREDUIWIN_GENERIC); CREDUIWIN_GENERIC | CREDUIWIN_CHECKBOX);
if (error != Errors.ERROR_SUCCESS) if (error != Errors.ERROR_SUCCESS)
{ {
if (error == Errors.ERROR_CANCELLED)
{
Throw.Command.Win32Exception(error);
}
throw new Win32Exception(error); throw new Win32Exception(error);
} }

View File

@ -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);
}
}

View File

@ -18,14 +18,6 @@ namespace WinSW.Native
int* maxValueNameLength, int* maxValueNameLength,
int* maxValueLength, int* maxValueLength,
int* securityDescriptorLength, int* securityDescriptorLength,
out FILETIME lastWriteTime); out FileTime lastWriteTime);
internal struct FILETIME
{
internal int LowDateTime;
internal int HighDateTime;
public long ToTicks() => ((long)this.HighDateTime << 32) + this.LowDateTime;
}
} }
} }

View File

@ -16,7 +16,7 @@ namespace WinSW.Util
Throw.Command.Win32Exception(error, "Failed to query registry key."); Throw.Command.Win32Exception(error, "Failed to query registry key.");
} }
return DateTime.FromFileTime(lastWriteTime.ToTicks()); return lastWriteTime.ToDateTime();
} }
} }
} }

View File

@ -434,6 +434,7 @@ namespace WinSW
Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service."); Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service.");
} }
bool saveCredential = false;
if (config.HasServiceAccount()) if (config.HasServiceAccount())
{ {
username = config.ServiceAccountUserName ?? username; username = config.ServiceAccountUserName ?? username;
@ -444,11 +445,16 @@ namespace WinSW
switch (config.ServiceAccountPrompt) switch (config.ServiceAccountPrompt)
{ {
case "dialog": case "dialog":
Credentials.PromptForCredentialsDialog( if (!Credentials.Load($"WinSW:{config.Name}", out username, out password))
ref username, {
ref password, Credentials.PromptForCredentialsDialog(
"Windows Service Wrapper", ref username,
"Enter the service account credentials"); ref password,
"Windows Service Wrapper",
"Enter the service account credentials",
ref saveCredential);
}
break; break;
case "console": case "console":
@ -472,6 +478,11 @@ namespace WinSW
username, username,
password); password);
if (saveCredential)
{
Credentials.Save($"WinSW:{config.Name}", username, password);
}
string description = config.Description; string description = config.Description;
if (description.Length != 0) if (description.Length != 0)
{ {
@ -508,7 +519,7 @@ namespace WinSW
EventLog.CreateEventSource(eventLogSource, "Application"); 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) void Uninstall(string? pathToConfig, bool noElevate)