Skip to content

Commit

Permalink
Save credentials
Browse files Browse the repository at this point in the history
  • Loading branch information
nxtn committed Dec 28, 2020
1 parent 576cf6b commit 01be236
Show file tree
Hide file tree
Showing 7 changed files with 118 additions and 20 deletions.
1 change: 1 addition & 0 deletions src/WinSW.Core/Native/ConsoleEx.cs
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ internal static string ReadPassword()
}
else if (key == '\r')
{
Write(consoleOutput, Environment.NewLine);
break;
}
else if (key == '\b')
Expand Down
31 changes: 31 additions & 0 deletions src/WinSW.Core/Native/CredentialApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -17,6 +25,9 @@ internal static extern bool CredPackAuthenticationBuffer(
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,
Expand All @@ -41,6 +52,26 @@ internal static extern bool CredUnPackAuthenticationBuffer(
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
{
Expand Down
58 changes: 54 additions & 4 deletions src/WinSW.Core/Native/Credentials.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -24,7 +70,7 @@ internal static void PromptForCredentialsConsole(ref string? userName, ref strin
}
}

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;
Expand Down Expand Up @@ -52,12 +98,11 @@ internal static void PromptForCredentialsDialog(ref string? userName, ref string

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,
Expand All @@ -67,10 +112,15 @@ internal static void PromptForCredentialsDialog(ref string? userName, ref string
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);
}

Expand Down
13 changes: 13 additions & 0 deletions src/WinSW.Core/Native/FileTime.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
using System;
using System.Runtime.CompilerServices;

namespace WinSW.Native
{
internal struct FileTime
{
internal int LowDateTime;
internal int HighDateTime;

public DateTime ToDateTime() => DateTime.FromFileTime(Unsafe.As<FileTime, long>(ref this));
}
}
10 changes: 1 addition & 9 deletions src/WinSW.Core/Native/RegistryApis.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,6 @@ internal static extern unsafe int RegQueryInfoKeyW(
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);
}
}
2 changes: 1 addition & 1 deletion src/WinSW.Core/Util/RegistryKeyExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ internal static unsafe DateTime GetLastWriteTime(this RegistryKey registryKey)
Throw.Command.Win32Exception(error, "Failed to query registry key.");
}

return DateTime.FromFileTime(lastWriteTime.ToTicks());
return lastWriteTime.ToDateTime();
}
}
}
23 changes: 17 additions & 6 deletions src/WinSW/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -434,6 +434,7 @@ void Install(string? pathToConfig, bool noElevate, string? username, string? pas
Throw.Command.Win32Exception(Errors.ERROR_SERVICE_EXISTS, "Failed to install the service.");
}

bool saveCredential = false;
if (config.HasServiceAccount())
{
username = config.ServiceAccountUserName ?? username;
Expand All @@ -444,11 +445,16 @@ void Install(string? pathToConfig, bool noElevate, string? username, string? pas
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":
Expand All @@ -472,6 +478,11 @@ void Install(string? pathToConfig, bool noElevate, string? username, string? pas
username,
password);

if (saveCredential)
{
Credentials.Save($"WinSW:{config.Name}", username, password);
}

string description = config.Description;
if (description.Length != 0)
{
Expand Down Expand Up @@ -508,7 +519,7 @@ void Install(string? pathToConfig, bool noElevate, string? username, string? pas
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)
Expand Down

0 comments on commit 01be236

Please sign in to comment.