Add SGU credential provider and authentication broker

This commit is contained in:
2026-08-31 17:48:18 -06:00
parent 5e216f42a4
commit 1f43f200b4
50 changed files with 3226 additions and 236 deletions
+52 -236
View File
@@ -1,256 +1,72 @@
![](https://github.com/lithnet/miis-powershell/wiki/images/logo-ex-small.png)
# SGU Windows Credential Provider
# Windows Credential Provider
![](https://img.shields.io/nuget/vpre/lithnet.credentialprovider?label=Current%20prerelease)![](https://img.shields.io/nuget/v/Lithnet.CredentialProvider?label=Current%20release)
![](https://img.shields.io/nuget/dt/lithnet.credentialprovider)
Windows Credential Provider and ASP.NET Core authentication broker for the
`lci.lasalle.mx` Active Directory laboratory.
A library for creating secure Windows Credential Providers in .NET, without the COM complications.
The repository starts from the current
[Lithnet Windows Credential Provider](https://github.com/lithnet/windows-credential-provider)
source and adds an SGU-specific provider, an mTLS-protected broker, Active
Directory synchronization, deployment scripts, and tests.
The Lithnet Credential Provider for Windows provides an easy way to create a credential provider, without having to implement the COM components. The COM components are still there, but abstracted away into a fully managed implementation.
## Authentication contract
## Getting started
* Create a new Class Library project. You can use .NET Framework 4.7.2 or later, or .NET 8.0, 9.0, or 10.0. You must build an x64 or x86 binary. You cannot use AnyCPU.
* Install the package from nuget `Install-Package Lithnet.CredentialProvider`
1. The Windows tile collects a `DO`, `AL`, or `AD` institutional key and a password.
2. It sends that exact password over mutually authenticated TLS to the broker.
3. The broker validates the same key/password pair against the configured SGU
NTLM endpoint.
4. On success, the broker creates or moves the AD user and sets the AD password
to the exact submitted password.
5. The Credential Provider serializes the original `SecureString` to Windows.
* Modify the `csproj` file and set `RegisterForComInterop` to `false`
```xml
<PropertyGroup>
<TargetFramework>net472</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x64</Platform>
</PropertyGroup>
```
No derived password is created. Passwords are not written to a database, file,
event log, application log, command line, or response.
* If you are using .NET 8.0, 9.0, or 10.0, you must also set `EnableComHosting` to `true`.
| Prefix | Role | Default OU |
|---|---|---|
| `DO` | Professor / docente | `OU=Docentes,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` |
| `AL` | Student / alumno | `OU=Alumnos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` |
| `AD` | Administrative | `OU=Administrativos,OU=Usuarios-SGU,DC=lci,DC=lasalle,DC=mx` |
```xml
<PropertyGroup>
<TargetFramework>net8.0-windows</TargetFramework>
<RegisterForComInterop>false</RegisterForComInterop>
<Platform>x64</Platform>
<EnableComHosting>true</EnableComHosting>
</PropertyGroup>
```
If the broker or institutional NTLM authority is unavailable, the provider
submits the unchanged credentials to Windows for normal AD/cached-domain
validation. This is not an unauthenticated bypass: Windows LSA must still accept
the last password registered in AD. An explicit NTLM `401` is rejected and is
not treated as an outage.
* Create a new class an inherit from `CredentialProviderBase`, as shown below, replacing the `ProgId` and `Guid` values with ones of your own
## Projects
```cs
[ComVisible(true)]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("MyCredentialProvider")]
[Guid("00000000-0000-0000-0000-000000000000")]
public class MyCredentialProvider : CredentialProviderBase
{
}
```
- `src/SGU.CredentialProvider` — x64 .NET 10 COM Credential Provider based on Lithnet.
- `src/SGU.AuthBroker` — Windows-hosted ASP.NET Core broker with mTLS, NTLM validation,
and Active Directory provisioning.
- `src/SGU.AuthBroker.Core` — testable authentication workflow and prefix classifier.
- `tests` — exact-password, role mapping, rejection, and outage-fallback tests.
- `scripts` — publishing, certificate, server deployment, client installation,
broker testing, and rollback.
* Override the `IsUsageScenarioSupported` method, to specify which scenarios you want to support with your credential provider
## Build
```cs
public override bool IsUsageScenarioSupported(UsageScenario cpus, CredUIWinFlags dwFlags)
{
switch (cpus)
{
case UsageScenario.Logon:
case UsageScenario.UnlockWorkstation:
case UsageScenario.CredUI:
case UsageScenario.ChangePassword:
return true;
default:
return false;
}
}
```
* Override the `GetControls` method, and provide the controls to render your UI. You can conditionally render based on the current scenario
```cs
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
yield return new CredentialProviderLabelControl("CredProviderLabel", "My first credential provider");
var infoLabel = new SmallLabelControl("InfoLabel", "Enter your username and password please!");
infoLabel.State = FieldState.DisplayInSelectedTile;
yield return infoLabel;
yield return new TextboxControl("UsernameField", "Username");
var password = new SecurePasswordTextboxControl("PasswordField", "Password");
yield return password;
if (cpus == UsageScenario.ChangePassword)
{
var confirmPassword = new SecurePasswordTextboxControl("ConfirmPasswordField", "Confirm password");
yield return confirmPassword;
yield return new SubmitButtonControl("SubmitButton", "Submit", confirmPassword);
}
else
{
yield return new SubmitButtonControl("SubmitButton", "Submit", password);
}
}
```
* Windows will ask for the tiles to show. You can determine if you want to show a generic tile (that is, a tile not associated with a user), or a user-specific tile. Windows will provide the list of known users for you to create tiles for.
```cs
public override bool ShouldIncludeUserTile(CredentialProviderUser user)
{
return true;
}
public override bool ShouldIncludeGenericTile()
{
return true;
}
public override CredentialTile CreateGenericTile()
{
return new MyTile(this);
}
public override CredentialTile2 CreateUserTile(CredentialProviderUser user)
{
return new MyTile(this, user);
}
```
* Create your tile class. Inherit from `CredentialTile2` if you want to create personalized tiles supported by Windows 8 and later, or `CredentialTile` if you only want to implement a generic tile. Use `CredentialTile3` when an image must preserve transparency. Grab the instances of your controls in the `Initialize` method, so you can attach to their properties to read and respond to value changes. Finally, override the `GetCredentials` method, which is called when the user clicks the submit button.
```cs
public class MyTile : CredentialTile2
{
private TextboxControl UsernameControl;
private SecurePasswordTextboxControl PasswordControl;
private SecurePasswordTextboxControl PasswordConfirmControl;
public MyTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
public MyTile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user)
{
}
public string Username
{
get => UsernameControl.Text;
set => UsernameControl.Text = value;
}
public SecureString Password
{
get => PasswordControl.Password;
set => PasswordControl.Password = value;
}
public SecureString ConfirmPassword
{
get => PasswordConfirmControl.Password;
set => PasswordConfirmControl.Password = value;
}
public override void Initialize()
{
if (UsageScenario == UsageScenario.ChangePassword)
{
this.PasswordConfirmControl = this.Controls.GetControl<SecurePasswordTextboxControl>("ConfirmPasswordField");
}
this.PasswordControl = this.Controls.GetControl<SecurePasswordTextboxControl>("PasswordField");
this.UsernameControl = this.Controls.GetControl<TextboxControl>("UsernameField");
Username = this.User?.QualifiedUserName;
}
protected override CredentialResponseBase GetCredentials()
{
string username;
string domain;
if (Username.Contains("\\"))
{
domain = Username.Split('\\')[0];
username = Username.Split('\\')[1];
}
else
{
username = Username;
domain = Environment.MachineName;
}
var spassword = Controls.GetControl<SecurePasswordTextboxControl>("PasswordField").Password;
return new CredentialResponseSecure()
{
IsSuccess = true,
Password = spassword,
Domain = domain,
Username = username
};
}
}
```
* Build your project and you have a functional credential provider!
## Bitmap transparency
`CredentialProviderLogoControl` and `UserTileControl` both accept a `Bitmap`. The tile base class controls how Windows receives images that contain transparent or partially transparent pixels.
| Tile base class | Image behaviour |
|-----------------|-----------------|
| `CredentialTile` | Renders transparency against the control's `BackgroundColor`. |
| `CredentialTile2` | Renders transparency against the control's `BackgroundColor`. |
| `CredentialTile3` | Preserves the image's alpha channel and ignores `BackgroundColor`. |
Existing providers that inherit from `CredentialTile` or `CredentialTile2` keep their current behaviour. The default `BackgroundColor` is `#464646`.
To preserve transparency, inherit your tile class from `CredentialTile3` and provide a `Bitmap` with an alpha channel. The library selects the image representation required by Windows, so your provider does not need to handle that conversion.
```cs
public class MyTile : CredentialTile3
{
public MyTile(CredentialProviderBase credentialProvider) : base(credentialProvider)
{
}
public MyTile(CredentialProviderBase credentialProvider, CredentialProviderUser user) : base(credentialProvider, user)
{
}
}
public override IEnumerable<ControlBase> GetControls(UsageScenario cpus)
{
Bitmap image = LoadTransparentBitmap();
yield return new UserTileControl("UserTile", "User tile image", image);
yield return new CredentialProviderLogoControl("ProviderLogo", "Credential provider logo", image);
}
```
## Installing the credential provider
You can use the traditional methods of registering a credential provider (regasm, regsvr32, create registry keys etc), but we've provided a PowerShell module to automatically register your credential provider with a single command.
Prerequisites are captured in `.vsconfig`; the pinned SDK is .NET `10.0.400`.
```powershell
Install-Module Lithnet.CredentialProvider.Management
Register-CredentialProvider -File C:\path-to-your-provider.dll
dotnet restore .\SGU-CredentialProvider.sln
dotnet build .\SGU-CredentialProvider.sln -c Release --no-restore
dotnet test .\SGU-CredentialProvider.sln -c Release --no-build --no-restore
.\scripts\Publish-Lab.ps1
```
You can disable, enable, and uninstall the provider with the following commands
The provider's .NET COM host is framework-dependent, so the Windows client needs
the latest .NET 10 x64 runtime. The broker is published self-contained.
```powershell
Disable-CredentialProvider -File "C:\path-to-your-provider.dll"
Enable-CredentialProvider -File "C:\path-to-your-provider.dll"
Unregister-CredentialProvider -File "C:\path-to-your-provider.dll"
```
## Deployment and test
Once the credential provider is registered, you can use the `Invoke-CredUI` cmdlet provided as part of the module, to bring up CredUI window and render your credential provider.
Follow [docs/lab-runbook.md](docs/lab-runbook.md). Review
[docs/security.md](docs/security.md) before production deployment and
[docs/architecture.md](docs/architecture.md) for the component contract.
## How can I contribute to the project?
* Found an issue and want us to fix it? [Log it](https://github.com/lithnet/windows-credential-provider/issues)
* Want to fix an issue yourself or add functionality? Clone the project and submit a pull request
Never disable the built-in Microsoft password Credential Provider. It is the
supported recovery path if a third-party provider fails to load.
## Enteprise support
Enterprise support is not currently offered for this product.
## Upstream license
## Keep up to date
* [Visit our blog](http://blog.lithnet.io)
* [Follow us on twitter](https://twitter.com/lithnet_io)![](http://twitter.com/favicon.ico)
The Lithnet source remains under its MIT license in [LICENSE](LICENSE). Project
additions are distributed under the same license.