Add SGU credential provider and authentication broker
This commit is contained in:
@@ -1,256 +1,72 @@
|
||||

|
||||
# SGU Windows Credential Provider
|
||||
|
||||
# Windows Credential Provider
|
||||

|
||||

|
||||
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)
|
||||
The Lithnet source remains under its MIT license in [LICENSE](LICENSE). Project
|
||||
additions are distributed under the same license.
|
||||
|
||||
Reference in New Issue
Block a user