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')
{
Write(consoleOutput, Environment.NewLine);
break;
}
else if (key == '\b')

View File

@ -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
{

View File

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

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

View File

@ -16,7 +16,7 @@ namespace WinSW.Util
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.");
}
bool saveCredential = false;
if (config.HasServiceAccount())
{
username = config.ServiceAccountUserName ?? username;
@ -444,11 +445,16 @@ namespace WinSW
switch (config.ServiceAccountPrompt)
{
case "dialog":
if (!Credentials.Load($"WinSW:{config.Name}", out username, out password))
{
Credentials.PromptForCredentialsDialog(
ref username,
ref password,
"Windows Service Wrapper",
"Enter the service account credentials");
"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)
{