Cleans up static loggers

Adds test projects for different frameworks and architectures
Adds registration tool assembly
This commit is contained in:
Ryan Newington
2023-01-29 17:30:37 +11:00
parent 4366b24e63
commit 1205f217b3
46 changed files with 1589 additions and 200 deletions
@@ -29,7 +29,7 @@ namespace Lithnet.CredentialProvider
this.Key = source.Key;
this.Type = source.Type;
this.FieldTypeGuid = source.FieldTypeGuid;
this.logger = source.logger;
this.state = source.State;
this.interactiveState = source.InteractiveState;
this.label = source.Label;
@@ -54,7 +54,6 @@ namespace Lithnet.CredentialProvider
this.options = FieldOptions.None;
this.Key = key;
this.logger = CredentialProviderBase.LoggerFactory.CreateLogger(this.GetType());
}
internal void AssignCredential(ICredentialProviderCredential credential)
@@ -76,6 +75,11 @@ namespace Lithnet.CredentialProvider
internal ICredentialProviderCredential Credential { get; private set; }
internal void SetLogger(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger(this.GetType());
}
/// <summary>
/// Gets the unique ID associated with this control
/// </summary>
@@ -13,11 +13,7 @@ namespace Lithnet.CredentialProvider
/// </summary>
public abstract partial class CredentialProviderBase
{
internal static ILoggerFactory LoggerFactory { get; set; } = NullLoggerFactory.Instance;
private readonly ILogger logger;
private readonly ILoggerFactory loggerFactory;
private ICredentialProviderEvents CredentialProviderEvents;
private ICredentialProviderUserArray credentialProviderUsers;
@@ -25,6 +21,8 @@ namespace Lithnet.CredentialProvider
private bool notifyOnTileCollectionChange;
private List<CredentialProviderCredential1Tile> tiles;
internal ILoggerFactory LoggerFactory { get; }
/// <summary>
/// Gets the GUID of the credential provider
/// </summary>
@@ -57,14 +55,10 @@ namespace Lithnet.CredentialProvider
protected CredentialProviderBase()
{
this.loggerFactory = this.GetLoggerFactory();
this.LoggerFactory = this.GetLoggerFactory();
if (CredentialProviderBase.LoggerFactory != this.loggerFactory)
{
CredentialProviderBase.LoggerFactory = this.loggerFactory;
}
this.logger = this.loggerFactory.CreateLogger(this.GetType());
this.logger = this.LoggerFactory.CreateLogger(this.GetType());
var guidAttribute = (GuidAttribute)(this.GetType().GetCustomAttribute(typeof(GuidAttribute)));
if (guidAttribute == null)
@@ -79,7 +73,7 @@ namespace Lithnet.CredentialProvider
/// Gets a logger factory. Override this method and provide an implementation of <c ref="ILoggerFactory"/> to enable credential provider logging
/// </summary>
/// <returns>An ILoggerFactory instance</returns>
public virtual ILoggerFactory GetLoggerFactory() { return NullLoggerFactory.Instance; }
protected virtual ILoggerFactory GetLoggerFactory() { return NullLoggerFactory.Instance; }
/// <summary>
/// Gets a value indicating if the credential provider supports the <c ref="UsageScenario"/> provided by LogonUI or CredUI
@@ -175,6 +169,7 @@ namespace Lithnet.CredentialProvider
foreach (var control in this.GetControls(this.UsageScenario))
{
control.SetLogger(this.LoggerFactory);
this.Controls.Add(control);
}
@@ -205,7 +200,7 @@ namespace Lithnet.CredentialProvider
this.logger.LogTrace($"Got supplied user {i}: with name {user.GetQualifiedUserName()} and SID {sid}");
var credentialProviderUser = new CredentialProviderUser(user);
var credentialProviderUser = new CredentialProviderUser(this.LoggerFactory, user);
users.Add(credentialProviderUser);
if (this.ShouldIncludeUserTile(credentialProviderUser))
@@ -12,7 +12,6 @@ namespace Lithnet.CredentialProvider
public abstract partial class CredentialProviderCredential1Tile
{
private protected readonly ILogger logger;
private protected ICredentialProviderCredentialEvents events;
private protected ICredentialProviderCredentialEvents2 events2;
@@ -21,7 +20,7 @@ namespace Lithnet.CredentialProvider
protected CredentialProviderCredential1Tile(CredentialProviderBase credentialProvider)
{
this.CredentialProvider = credentialProvider;
this.logger = CredentialProviderBase.LoggerFactory.CreateLogger(this.GetType());
this.logger = credentialProvider.LoggerFactory.CreateLogger(this.GetType());
}
internal CredentialProviderBase CredentialProvider { get; }
@@ -206,15 +205,15 @@ namespace Lithnet.CredentialProvider
return response;
}
CredentialSerializer.Logger = this.logger;
var serializer = new CredentialSerializer(this.CredentialProvider.LoggerFactory);
if (credentials is CredentialResponseSecure s)
{
response.SerializedCredentials = CredentialSerializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, s.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId);
response.SerializedCredentials = serializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, s.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId);
}
else if (credentials is CredentialResponseInsecure i)
{
response.SerializedCredentials = CredentialSerializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, i.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId);
response.SerializedCredentials = serializer.GenerateCredentialSerialization(credentials.Domain, credentials.Username, i.Password, this.UsageScenario == UsageScenario.UnlockWorkstation, this.CredentialProvider.CredentialProviderId);
}
else
{
@@ -0,0 +1,233 @@
using System;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Win32;
namespace Lithnet.CredentialProvider
{
public static class CredentialProviderRegistrationServices
{
public static void DisableCredentialProvider<T>() where T : CredentialProviderBase => DisableCredentialProvider(typeof(T));
public static void UnregisterCredentialProvider<T>() where T : CredentialProviderBase => UnregisterCredentialProvider(typeof(T));
public static void RegisterCredentialProvider<T>() where T : CredentialProviderBase => RegisterCredentialProvider(typeof(T));
public static void EnableCredentialProvider<T>() where T : CredentialProviderBase => EnableCredentialProvider(typeof(T));
public static void UnregisterCredentialProvider(Type type)
{
DeleteCredentialProviderRegistration(type);
if (IsFrameworkType(type))
{
UnregisterFrameworkAssembly(type);
}
else
{
UnregisterNetCoreAssembly(type);
}
}
public static void RegisterCredentialProvider(Type type)
{
CreateCredentialProviderRegistration(type);
if (IsFrameworkType(type))
{
RegisterFrameworkAssembly(type);
}
else
{
RegisterNetCoreAssembly(type);
}
}
public static void DisableCredentialProvider(Type type)
{
var comGuid = GetComGuid(type);
DisableCredentialProvider(comGuid);
}
public static void DisableCredentialProvider(Guid comGuid)
{
var key = Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{comGuid:B}", true);
key?.SetValue("Disabled", 1);
}
public static void EnableCredentialProvider(Guid comGuid)
{
var key = Registry.LocalMachine.OpenSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{comGuid:B}", true);
key?.SetValue("Disabled", 0);
}
public static void EnableCredentialProvider(Type type)
{
var comGuid = GetComGuid(type);
EnableCredentialProvider(comGuid);
}
private static void CreateCredentialProviderRegistration(Type t)
{
var comGuid = GetComGuid(t);
var typeName = GetTypeFullName(t);
var key = Registry.LocalMachine.CreateSubKey($@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{comGuid:B}", true);
key.SetValue(null, typeName);
}
private static void DeleteCredentialProviderRegistration(Type t)
{
var comGuid = GetComGuid(t);
Registry.LocalMachine.DeleteSubKeyTree($@"SOFTWARE\Microsoft\Windows\CurrentVersion\Authentication\Credential Providers\{comGuid:B}", false);
}
private static void RegisterNetCoreAssembly(Type t)
{
var comGuid = GetComGuid(t);
var typeName = GetTypeFullName(t);
var progId = GetComProgId(t);
var assemblyLocation = GetTypeAssemblyLocation(t);
var dir = Path.GetDirectoryName(assemblyLocation);
var assemblyFile = Path.GetFileNameWithoutExtension(assemblyLocation);
var comHostLocation = Path.Combine(dir, assemblyFile + ".comhost.dll");
var rootClsid = Registry.LocalMachine.CreateSubKey($@"Software\Classes\CLSID\{comGuid:B}", true);
rootClsid.SetValue(null, "CoreCLR COMHost Server");
var inprocKey = rootClsid.CreateSubKey("InprocServer32", true);
inprocKey.SetValue(null, comHostLocation);
inprocKey.SetValue("ThreadingModel", "Both");
var progIdKey = rootClsid.CreateSubKey("ProgId", true);
progIdKey.SetValue(null, progId);
var progIdRoot = Registry.LocalMachine.CreateSubKey($@"Software\Classes\{progId}", true);
progIdRoot.SetValue(null, typeName);
var progIdSubKey = progIdRoot.CreateSubKey("CLSID");
progIdSubKey.SetValue(null, comGuid.ToString("B"));
}
private static void UnregisterNetCoreAssembly(Type t)
{
var comGuid = GetComGuid(t);
var progId = GetComProgId(t);
Registry.LocalMachine.DeleteSubKeyTree($@"Software\Classes\CLSID\{comGuid:B}", false);
Registry.LocalMachine.DeleteSubKeyTree($@"Software\Classes\{progId}", false);
}
private static void RegisterFrameworkAssembly(Type t)
{
var comGuid = GetComGuid(t);
var typeName = GetTypeFullName(t);
var progId = GetComProgId(t);
var rootClsid = Registry.LocalMachine.CreateSubKey($@"Software\Classes\CLSID\{comGuid:B}", true);
rootClsid.SetValue(null, typeName);
rootClsid.CreateSubKey("Implemented Categories");
rootClsid.CreateSubKey(@"Implemented Categories\{62C8FE65-4EBB-45e7-B440-6E39B2CDBF29}");
var inprocKey = rootClsid.CreateSubKey("InprocServer32", true);
inprocKey.SetValue(null, "mscoree.dll");
inprocKey.SetValue("ThreadingModel", "Both");
inprocKey.SetValue("Class", typeName);
inprocKey.SetValue("RuntimeVersion", "v4.0.30319");
inprocKey.SetValue("Assembly", GetTypeAssemblyName(t));
inprocKey.SetValue("CodeBase", GetTypeAssemblyLocation(t));
var progIdKey = rootClsid.CreateSubKey("ProgId", true);
progIdKey.SetValue(null, progId);
var progIdRoot = Registry.LocalMachine.CreateSubKey($@"Software\Classes\{progId}", true);
progIdRoot.SetValue(null, typeName);
var progIdSubKey = progIdRoot.CreateSubKey("CLSID");
progIdSubKey.SetValue(null, comGuid.ToString("B"));
}
private static void UnregisterFrameworkAssembly(Type t)
{
var comGuid = GetComGuid(t);
var progId = GetComProgId(t);
Registry.LocalMachine.DeleteSubKeyTree($@"Software\Classes\CLSID\{comGuid:B}", false);
Registry.LocalMachine.DeleteSubKeyTree($@"Software\Classes\{progId}", false);
}
private static string GetTypeAssemblyLocation(Type type)
{
return type.Assembly.Location;
}
private static string GetTypeAssemblyName(Type type)
{
return type.Assembly.FullName;
}
private static string GetTypeClassName(Type type)
{
return type.Name;
}
private static string GetTypeFullName(Type type)
{
return type.FullName;
}
private static Guid GetComGuid(Type type)
{
var typeId = type.GetCustomAttributeValue("GuidAttribute");
if (typeId == null)
{
throw new ArgumentException($"The type {type.Name} does not have the Guid attribute present");
}
return new Guid(typeId);
}
private static string GetComProgId(Type type)
{
var typeId = type.GetCustomAttributeValue("ProgIdAttribute");
if (typeId == null)
{
throw new ArgumentException($"The type {type.Name} does not have the ProgId attribute present");
}
return typeId;
}
private static bool IsFrameworkType(Type type)
{
var framework = type.Assembly.GetCustomAttributeValue("TargetFrameworkAttribute");
return framework.StartsWith(".NETFramework");
}
private static string GetCustomAttributeValue(this Type type, string attributeName)
{
var cads = type.GetCustomAttributesData();
foreach (CustomAttributeData cad in cads.Where(a => a.AttributeType.Name == attributeName))
{
return cad.ConstructorArguments.FirstOrDefault().Value as string;
}
return String.Empty;
}
private static string GetCustomAttributeValue(this Assembly assembly, string attributeName)
{
foreach (CustomAttributeData cad in assembly.GetCustomAttributesData().Where(a => a.AttributeType.Name == attributeName))
{
return cad.ConstructorArguments.FirstOrDefault().Value as string;
}
return String.Empty;
}
}
}
@@ -19,10 +19,10 @@ namespace Lithnet.CredentialProvider
private string logonStatus;
private string providerId;
internal CredentialProviderUser(ICredentialProviderUser user)
internal CredentialProviderUser(ILoggerFactory loggerFactory, ICredentialProviderUser user)
{
this.User = user;
this.logger = CredentialProviderBase.LoggerFactory.CreateLogger<CredentialProviderUser>();
this.logger = loggerFactory.CreateLogger<CredentialProviderUser>();
}
/// <summary>
@@ -3,20 +3,24 @@ using System.Runtime.InteropServices;
using System.Security;
using System.Text;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
namespace Lithnet.CredentialProvider.Interop
{
internal static class CredentialSerializer
internal class CredentialSerializer
{
internal static ILogger Logger { get; set; } = NullLogger.Instance;
private readonly ILogger logger;
public static CredentialSerialization GenerateCredentialSerialization(string domain, string username, SecureString password, bool isWorkstationUnlock, Guid providerId)
public CredentialSerializer(ILoggerFactory loggerFactory)
{
this.logger = loggerFactory.CreateLogger<CredentialSerializer>();
}
public CredentialSerialization GenerateCredentialSerialization(string domain, string username, SecureString password, bool isWorkstationUnlock, Guid providerId)
{
var authPackage = PInvoke.LookupAuthenticationPackage(CredProviderConstants.NEGOSSP_NAME_A);
var pData = SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
var pData = this.SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
Logger.LogTrace($"0x{pData.ToString("X16")} - Serializer: Password got packed into ");
this.logger.LogTrace($"0x{pData.ToString("X16")} - Serializer: Password got packed into ");
return new CredentialSerialization()
{
@@ -27,12 +31,12 @@ namespace Lithnet.CredentialProvider.Interop
};
}
public static CredentialSerialization GenerateCredentialSerialization(string domain, string username, string password, bool isWorkstationUnlock, Guid providerId)
public CredentialSerialization GenerateCredentialSerialization(string domain, string username, string password, bool isWorkstationUnlock, Guid providerId)
{
var authPackage = PInvoke.LookupAuthenticationPackage(CredProviderConstants.NEGOSSP_NAME_A);
var pData = SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
var pData = this.SerializeKerbLogon(domain, username, password, isWorkstationUnlock ? KerbLogonSubmitType.WorkstationUnlockLogon : KerbLogonSubmitType.InteractiveLogon, out int size);
Logger.LogTrace($"0x{pData.ToString("X16")}: Password got packed");
this.logger.LogTrace($"0x{pData.ToString("X16")}: Password got packed");
return new CredentialSerialization()
{
@@ -43,7 +47,7 @@ namespace Lithnet.CredentialProvider.Interop
};
}
private static unsafe IntPtr SerializeKerbLogon(string domain, string username, string password, KerbLogonSubmitType type, out int size)
private unsafe IntPtr SerializeKerbLogon(string domain, string username, string password, KerbLogonSubmitType type, out int size)
{
size = sizeof(KerberosInteractiveUnlockLogon) +
Encoding.Unicode.GetMaxByteCount(domain.Length) +
@@ -86,7 +90,7 @@ namespace Lithnet.CredentialProvider.Interop
return pBuffer;
}
private static unsafe IntPtr SerializeKerbLogon(string domain, string username, SecureString password, KerbLogonSubmitType type, out int size)
private unsafe IntPtr SerializeKerbLogon(string domain, string username, SecureString password, KerbLogonSubmitType type, out int size)
{
size = sizeof(KerberosInteractiveUnlockLogon) +
Encoding.Unicode.GetMaxByteCount(domain.Length) +
@@ -127,24 +131,24 @@ namespace Lithnet.CredentialProvider.Interop
try
{
buff = Marshal.SecureStringToCoTaskMemUnicode(password);
Logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Unprotected password");
this.logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Unprotected password");
IntPtr targetPositionToCopyTo = (IntPtr)(buffer + logon->Password.Buffer.ToInt64());
Buffer.MemoryCopy(buff.ToPointer(), targetPositionToCopyTo.ToPointer(), logon->Password.Length, password.Length * sizeof(char));
Logger.LogTrace($"0x{targetPositionToCopyTo.ToString("X16")} - Serializer: Copied unprotected password into LSA string buffer");
this.logger.LogTrace($"0x{targetPositionToCopyTo.ToString("X16")} - Serializer: Copied unprotected password into LSA string buffer");
}
finally
{
if (buff != IntPtr.Zero)
{
Marshal.ZeroFreeCoTaskMemUnicode(buff);
Logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Freed Unprotected password");
this.logger.LogTrace($"0x{buff.ToString("X16")} - Serializer: Freed Unprotected password");
}
}
Logger.LogTrace($"0x{((IntPtr)buffer).ToString("X16")} - Serializer: Put password");
this.logger.LogTrace($"0x{((IntPtr)buffer).ToString("X16")} - Serializer: Put password");
return pBuffer;
}
@@ -3,7 +3,7 @@ using System.Runtime.InteropServices;
namespace Lithnet.CredentialProvider.Interop
{
[StructLayout(LayoutKind.Sequential, Pack = 8)]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct FieldDescriptor
{
public uint FieldID;
@@ -6,6 +6,9 @@
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PlatformTargets>AnyCPU</PlatformTargets>
<Deterministic>true</Deterministic>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<GenerateBindingRedirectsOutputType>true</GenerateBindingRedirectsOutputType>
<LangVersion>9</LangVersion>
</PropertyGroup>
<PropertyGroup>
@@ -14,7 +17,7 @@
<Copyright>Copyright 2023 Lithnet Pty Ltd</Copyright>
<ProductName>Lithnet Windows Credential Provider</ProductName>
<VersionPrefix>1.0.0</VersionPrefix>
<VersionSuffix>beta.3</VersionSuffix>
<VersionSuffix>beta.26</VersionSuffix>
<Authors>Lithnet</Authors>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<AutoIncrementPackageRevision>true</AutoIncrementPackageRevision>
@@ -23,10 +26,12 @@
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<RepositoryUrl>https://github.com/lithnet/windows-credential-provider</RepositoryUrl>
<SupportUrl>https://github.com/lithnet/windows-credential-provider</SupportUrl>
<PackageOutputPath>D:\dev\nuget\packages</PackageOutputPath>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Win32.Registry" Version="5.0.0" />
<PackageReference Include="System.Drawing.Common" Version="6.0.0" Condition="$(TargetFramework) == 'netstandard2.0'" />
</ItemGroup>
</Project>
@@ -0,0 +1,37 @@
using System;
using System.Diagnostics;
using System.IO;
using System.Reflection;
using System.Runtime.CompilerServices;
namespace Lithnet.CredentialProvider.ModuleInit
{
internal static class AssemblyResolver
{
private static string basePath;
[ModuleInitializer]
public static void AttachResolver()
{
Trace.WriteLine("Attaching assembly resolver");
AppDomain.CurrentDomain.AssemblyResolve += CurrentDomain_AssemblyResolve;
basePath = Path.GetFullPath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
}
private static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
var name = new AssemblyName(args.Name);
string assyPath = Path.Combine(basePath, $"{name.Name}.dll");
Trace.WriteLine($"Request for {args.Name}");
if (File.Exists(assyPath))
{
Trace.WriteLine($"Found at {assyPath}");
return Assembly.Load(assyPath);
}
Trace.WriteLine($"Assembly {args.Name} not found");
return null;
}
}
}
@@ -0,0 +1,7 @@
namespace System.Runtime.CompilerServices
{
[AttributeUsage(System.AttributeTargets.Method, Inherited = false)]
internal sealed class ModuleInitializerAttribute : Attribute
{
}
}