diff --git a/README.md b/README.md index bb33aec..4cfed04 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,172 @@ -# windows-credential-provider -A library for creating secure Windows Credential Providers in .NET +![](https://github.com/lithnet/miis-powershell/wiki/images/logo-ex-small.png) + +# Windows Credential Provider +A library for creating secure Windows Credential Providers in .NET, without the COM complications. + +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. + +## Getting started +* Create a new Class Library project. You can use .NET Framework 4.7.2 or higher, or you can use .NET Core 3.1 or higher) to create your provider +* Install the package from nuget `Install-Package Lithnet.CredentialProvider` +* Create a new class an inherit from `redentialProviderBase`, as shown below, replacing the `ProgId` and `Guid` values with ones of your own + +```cs +[ComVisible(true)] +[ClassInterface(ClassInterfaceType.None)] +[ProgId("MyCredentialProvider")] +[Guid("00000000-0000-0000-0000-000000000000")] +public class MyCredentialProvider : CredentialProviderBase +{ +} +``` + +* Override the `IsUsageScenarioSupported` method, to specify which scenarios you want to support with your credential provider + +```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 GetControls(UsageScenario cpus) +{ + 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 CredentialProviderCredential1Tile CreateGenericTile() +{ + return new MyTile(this); +} + +public override CredentialProviderCredential1Tile CreateUserTile(CredentialProviderUser user) +{ + return new MyTile(this, user); +} +``` + +* Create your tile class. Inherit from `CredentialProviderCredential2Tile` if you want to create personalized tiles supported by Windows 8 and later, or `CredentialProviderCredential1Tile` if you only want to implement a generic tile. 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 : CredentialProviderCredential2Tile +{ + 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("ConfirmPasswordField"); + } + + this.PasswordControl = this.Controls.GetControl("PasswordField"); + this.UsernameControl = this.Controls.GetControl(C"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(ControlKeys.Password).Password; + + return new CredentialResponseSecure() + { + IsSuccess = true, + Password = spassword, + Domain = domain, + Username = username + }; + } +} +``` + +## 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 + +## Enteprise support +Enterprise support is not currently offered for this product. + +## 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) diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..5967947 --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,110 @@ +pool: + vmImage: 'windows-latest' + +variables: + - name: buildPlatform + value: 'Any CPU' + - name: buildConfiguration + value: 'Release' + - name: build.version.major + value: 1 + - name: build.version.minor + value: 0 + - name: build.version.revision + value: 0 + - name: build.version.beta + value: $[counter(format('{0}.{1}.{2}', variables['build.version.major'], variables['build.version.minor'], variables['build.version.revision']), 0)] + - name: build.version.suffix + value: -beta1.$(build.version.beta) + - name: build.version + value: $(build.version.major).$(build.version.minor).$(build.version.revision)$(build.version.suffix) + - name: build.date + value: $[format('{0:yyyy}-{0:MM}-{0:dd}T{0:HH}:{0:mm}:{0:ss}', pipeline.startTime)] + - group: Azure KeyVault Code Signing + +name: $(build.version.major).$(build.version.minor).$(build.version.revision)$(build.version.suffix) + +steps: +- task: NuGetToolInstaller@1 + +- task: DotNetCoreCLI@2 + displayName: dotnet build + inputs: + command: build + publishWebProjects: false + arguments: '-c $(buildConfiguration) -p:Version=$(build.version) -p:PackageOutputPath=$(Build.ArtifactStagingDirectory)' + projects: 'src/Lithnet.CredentialProvider/Lithnet.CredentialProvider.csproj' + zipAfterPublish: false + +- task: DotNetCoreCLI@2 + inputs: + command: 'custom' + custom: 'tool' + arguments: 'update --global NuGetKeyVaultSignTool' + displayName: Install NugetKeyVaultSignTool + +- task: DotNetCoreCLI@2 + inputs: + command: 'custom' + custom: 'tool' + arguments: 'update --global azuresigntool' + displayName: Install AzureSignTool + +- task: PowerShell@2 + displayName: 'Sign files with AzureSignTool' + inputs: + targetType: 'inline' + script: | + $files = @() + $files += (Get-ChildItem -Recurse -Path "$(Build.SourcesDirectory)\Lithnet*.dll").FullName + + write-host "Signing $($files.Length) files:" + write-output $files + + $cmdargs = @( + "sign", + "-d", "Lithnet Windows Credential Provider", + "-kvu", "$(akv.url)", + "-kvi", "$(akv.applicationID)", + "-kvs", "$(akv.secret)", + "-kvt", "$(akv.tenantId)", + "-kvc", "$(akv.certificateName)", + "-tr", "http://timestamp.digicert.com", + "-td", "sha256" + ) + + $cmdargs += $files + + & AzureSignTool $cmdargs + + failOnStderr: true + showWarnings: true + +- task: PowerShell@2 + displayName: 'Sign Nuget package' + inputs: + targetType: 'inline' + script: | + $cmdargs = @( + "sign", "$(Build.ArtifactStagingDirectory)\Lithnet.CredentialProvider.$(build.version).nupkg" + "-fd", "sha256", + "-kvu", "$(akv.url)", + "-kvi", "$(akv.applicationID)", + "-kvs", "$(akv.secret)", + "-kvt", "$(akv.tenantId)", + "-kvc", "$(akv.certificateName)", + "-tr", "http://timestamp.digicert.com", + "-td", "sha256" + ) + + & NuGetKeyVaultSignTool $cmdargs + + failOnStderr: true + showWarnings: true + +- task: NuGetCommand@2 + displayName: 'NuGet push' + inputs: + command: push + nuGetFeedType: external + publishFeedCredentials: 'WindowsCredentialProviderNuget' \ No newline at end of file