├── .editorconfig ├── .gitattributes ├── .gitignore ├── CredentialManager.sln ├── Directory.Build.props ├── License.md ├── README.md ├── appveyor.yml ├── changelog.md ├── key.snk ├── src └── AdysTech.CredentialManager │ ├── AdysTech.CredentialManager.csproj │ ├── Credential.cs │ ├── CredentialAPIException.cs │ ├── CredentialExtensions.cs │ ├── CredentialManager.cs │ ├── CriticalCredentialHandle.cs │ ├── ICredential.cs │ ├── NativeCode.cs │ ├── Properties │ └── AssemblyInfo.cs │ └── PublicEnums.cs └── tests └── CredentialManager.Test ├── CredentialManager.Test.csproj └── CredentialManagerTest.cs /.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # IDE0008: Use explicit type 4 | csharp_style_var_elsewhere = false:suggestion 5 | 6 | # IDE0018: Inline variable declaration 7 | csharp_style_inlined_variable_declaration = false:suggestion 8 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | 4 | # User-specific files 5 | *.suo 6 | *.user 7 | *.sln.docstates 8 | 9 | # Build results 10 | [Dd]ebug/ 11 | [Dd]ebugPublic/ 12 | [Rr]elease/ 13 | x64/ 14 | build/ 15 | bld/ 16 | [Bb]in/ 17 | [Oo]bj/ 18 | 19 | # Roslyn cache directories 20 | *.ide/ 21 | 22 | # MSTest test Results 23 | [Tt]est[Rr]esult*/ 24 | [Bb]uild[Ll]og.* 25 | 26 | #NUNIT 27 | *.VisualState.xml 28 | TestResult.xml 29 | 30 | # Build Results of an ATL Project 31 | [Dd]ebugPS/ 32 | [Rr]eleasePS/ 33 | dlldata.c 34 | 35 | *_i.c 36 | *_p.c 37 | *_i.h 38 | *.ilk 39 | *.meta 40 | *.obj 41 | *.pch 42 | *.pdb 43 | *.pgc 44 | *.pgd 45 | *.rsp 46 | *.sbr 47 | *.tlb 48 | *.tli 49 | *.tlh 50 | *.tmp 51 | *.tmp_proj 52 | *.log 53 | *.vspscc 54 | *.vssscc 55 | .builds 56 | *.pidb 57 | *.svclog 58 | *.scc 59 | 60 | # Chutzpah Test files 61 | _Chutzpah* 62 | 63 | # Visual C++ cache files 64 | ipch/ 65 | *.aps 66 | *.ncb 67 | *.opensdf 68 | *.sdf 69 | *.cachefile 70 | 71 | # Visual Studio profiler 72 | *.psess 73 | *.vsp 74 | *.vspx 75 | 76 | # TFS 2012 Local Workspace 77 | $tf/ 78 | 79 | # Guidance Automation Toolkit 80 | *.gpState 81 | 82 | # ReSharper is a .NET coding add-in 83 | _ReSharper*/ 84 | *.[Rr]e[Ss]harper 85 | *.DotSettings.user 86 | 87 | # JustCode is a .NET coding addin-in 88 | .JustCode 89 | 90 | # TeamCity is a build add-in 91 | _TeamCity* 92 | 93 | # DotCover is a Code Coverage Tool 94 | *.dotCover 95 | 96 | # NCrunch 97 | _NCrunch_* 98 | .*crunch*.local.xml 99 | 100 | # MightyMoose 101 | *.mm.* 102 | AutoTest.Net/ 103 | 104 | # Web workbench (sass) 105 | .sass-cache/ 106 | 107 | # Installshield output folder 108 | [Ee]xpress/ 109 | 110 | # DocProject is a documentation generator add-in 111 | DocProject/buildhelp/ 112 | DocProject/Help/*.HxT 113 | DocProject/Help/*.HxC 114 | DocProject/Help/*.hhc 115 | DocProject/Help/*.hhk 116 | DocProject/Help/*.hhp 117 | DocProject/Help/Html2 118 | DocProject/Help/html 119 | 120 | # Click-Once directory 121 | publish/ 122 | 123 | # Publish Web Output 124 | *.[Pp]ublish.xml 125 | *.azurePubxml 126 | ## TODO: Comment the next line if you want to checkin your 127 | ## web deploy settings but do note that will include unencrypted 128 | ## passwords 129 | #*.pubxml 130 | 131 | # NuGet Packages Directory 132 | packages/* 133 | ## TODO: If the tool you use requires repositories.config 134 | ## uncomment the next line 135 | #!packages/repositories.config 136 | 137 | # Enable "build/" folder in the NuGet Packages folder since 138 | # NuGet packages use it for MSBuild targets. 139 | # This line needs to be after the ignore of the build folder 140 | # (and the packages folder if the line above has been uncommented) 141 | !packages/build/ 142 | 143 | # Windows Azure Build Output 144 | csx/ 145 | *.build.csdef 146 | 147 | # Windows Store app package directory 148 | AppPackages/ 149 | 150 | # Others 151 | sql/ 152 | *.Cache 153 | ClientBin/ 154 | [Ss]tyle[Cc]op.* 155 | ~$* 156 | *~ 157 | *.dbmdl 158 | *.dbproj.schemaview 159 | *.pfx 160 | *.publishsettings 161 | node_modules/ 162 | 163 | # RIA/Silverlight projects 164 | Generated_Code/ 165 | 166 | # Backup & report files from converting an old project file 167 | # to a newer Visual Studio version. Backup files are not needed, 168 | # because we have git ;-) 169 | _UpgradeReport_Files/ 170 | Backup*/ 171 | UpgradeLog*.XML 172 | UpgradeLog*.htm 173 | 174 | # SQL Server files 175 | *.mdf 176 | *.ldf 177 | 178 | # Business Intelligence projects 179 | *.rdl.data 180 | *.bim.layout 181 | *.bim_*.settings 182 | 183 | # Microsoft Fakes 184 | FakesAssemblies/ 185 | 186 | # LightSwitch generated files 187 | GeneratedArtifacts/ 188 | _Pvt_Extensions/ 189 | ModelManifest.xml 190 | /.vs 191 | /.vs 192 | /.vscode/tasks.json 193 | -------------------------------------------------------------------------------- /CredentialManager.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.29503.13 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "AdysTech.CredentialManager", "src\AdysTech.CredentialManager\AdysTech.CredentialManager.csproj", "{ABA77D40-8D43-42EE-BE03-44E4AD93672A}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CredentialManager.Test", "tests\CredentialManager.Test\CredentialManager.Test.csproj", "{5DCA2C07-C1F3-424F-B934-F952595586CE}" 9 | EndProject 10 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EBCE1FF4-81EB-4EC9-A085-2CA0F81100E5}" 11 | ProjectSection(SolutionItems) = preProject 12 | .editorconfig = .editorconfig 13 | appveyor.yml = appveyor.yml 14 | changelog.md = changelog.md 15 | Directory.Build.props = Directory.Build.props 16 | README.md = README.md 17 | EndProjectSection 18 | EndProject 19 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{0609161C-CF52-44B3-A09E-6CCD4BCCB30F}" 20 | EndProject 21 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{7E19D2AF-C0F0-4229-8E95-5B8ADC0BE732}" 22 | EndProject 23 | Global 24 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 25 | Debug|Any CPU = Debug|Any CPU 26 | Debug|x64 = Debug|x64 27 | Release|Any CPU = Release|Any CPU 28 | Release|x64 = Release|x64 29 | EndGlobalSection 30 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 31 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Debug|x64.ActiveCfg = Debug|Any CPU 34 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Debug|x64.Build.0 = Debug|Any CPU 35 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Release|Any CPU.ActiveCfg = Release|Any CPU 36 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Release|Any CPU.Build.0 = Release|Any CPU 37 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Release|x64.ActiveCfg = Release|Any CPU 38 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A}.Release|x64.Build.0 = Release|Any CPU 39 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 40 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Debug|Any CPU.Build.0 = Debug|Any CPU 41 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Debug|x64.ActiveCfg = Debug|Any CPU 42 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Debug|x64.Build.0 = Debug|Any CPU 43 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Release|Any CPU.ActiveCfg = Release|Any CPU 44 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Release|Any CPU.Build.0 = Release|Any CPU 45 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Release|x64.ActiveCfg = Release|Any CPU 46 | {5DCA2C07-C1F3-424F-B934-F952595586CE}.Release|x64.Build.0 = Release|Any CPU 47 | EndGlobalSection 48 | GlobalSection(SolutionProperties) = preSolution 49 | HideSolutionNode = FALSE 50 | EndGlobalSection 51 | GlobalSection(NestedProjects) = preSolution 52 | {ABA77D40-8D43-42EE-BE03-44E4AD93672A} = {0609161C-CF52-44B3-A09E-6CCD4BCCB30F} 53 | {5DCA2C07-C1F3-424F-B934-F952595586CE} = {7E19D2AF-C0F0-4229-8E95-5B8ADC0BE732} 54 | EndGlobalSection 55 | GlobalSection(ExtensibilityGlobals) = postSolution 56 | SolutionGuid = {36A3FDA1-ACC5-4934-8E8A-DBE3ACADFE39} 57 | EndGlobalSection 58 | EndGlobal 59 | -------------------------------------------------------------------------------- /Directory.Build.props: -------------------------------------------------------------------------------- 1 | 2 | 3 | true 4 | net45;netstandard2.0 5 | 8 6 | https://github.com/AdysTech/CredentialManager 7 | https://github.com/AdysTech/CredentialManager 8 | MIT 9 | © AdysTech 2016-2022 10 | AdysTech 11 | true 12 | false 13 | true 14 | ../../key.snk 15 | 16 | -------------------------------------------------------------------------------- /License.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Adys Tech 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build status](https://ci.appveyor.com/api/projects/status/b6osdeuob7qeuivr?svg=true)](https://ci.appveyor.com/project/AdysTech/credentialmanager) 2 | 3 | 4 | ### Nuget Package 5 | [AdysTech.CredentialManager](https://www.nuget.org/packages/AdysTech.CredentialManager/) 6 | 7 | Supports .NET Framework 4.5+, and .NET Standard 2.1+. 8 | 9 | #### Latest Download 10 | [AdysTech.CredentialManager](https://ci.appveyor.com/api/buildjobs/so3ev8bmq51pp2im/artifacts/AdysTech.CredentialManager%2Fbin%2FCredentialManager.zip) 11 | 12 | # CredentialManager 13 | C# wrapper around CredWrite / CredRead functions to store and retrieve from Windows Credential Store. 14 | Windows OS comes equipped with a very secure robust [Credential Manager](https://technet.microsoft.com/en-us/library/jj554668.aspx) from Windows XP onwards, and [good set of APIs](https://msdn.microsoft.com/en-us/library/windows/desktop/aa374731(v=vs.85).aspx#credentials_management_functions) to interact with it. However .NET Framework did not provide any standard way to interact with this vault [until Windows 8.1](https://msdn.microsoft.com/en-us/library/windows/apps/windows.security.credentials.aspx). 15 | 16 | Microsoft Peer Channel blog (WCF team) has written [a blog post](http://blogs.msdn.com/b/peerchan/archive/2005/11/01/487834.aspx) in 2005 which provided basic structure of using the Win32 APIs for credential management in .NET. 17 | I used their code, and improved up on it to add `PromptForCredentials` function to display a dialog to get the credentials from user. 18 | 19 | Need: Many web services and REST Urls use basic authentication. .Net does not have a way to generate basic auth text (username:password encoded in Base64) for the current logged in user, with their credentials. 20 | `ICredential.GetCredential (uri, "Basic")` does not provide a way to get current user security context either as it will expose the current password in plain text. So only way to retrieve Basic auth text is to prompt the user for the credentials and storing it, or assume some stored credentials in Windows store, and retrieving it. 21 | 22 | This project provides access to all three 23 | #### 1. Prompt user for Credentials 24 | ```C# 25 | var cred = CredentialManager.PromptForCredentials ("Some Webservice", ref save, "Please provide credentials", "Credentials for service"); 26 | ``` 27 | 28 | #### 2. Save Credentials 29 | ```C# 30 | var cred = new NetworkCredential ("TestUser", "Pwd"); 31 | CredentialManager.SaveCredentials ("TestSystem", cred); 32 | ``` 33 | 34 | #### 3. Retrieve saved Credentials 35 | ```C# 36 | var cred = CredentialManager.GetCredentials ("TestSystem"); 37 | ``` 38 | 39 | With v2.0 release exposes raw credential, with additional information not available in normal `NetworkCredential` available in previous versions. This library also allows to store comments and additional attributes associated with a Credential object. The attributes are serialized using `BinaryFormatter` and API has 256 byte length. `BinaryFormatter` generates larger than what you think the object size is going to be, si keep an eye on that. 40 | 41 | Comments and attributes are only accessible programmatically. Windows always supported such a feature (via `CREDENTIALW` [structure](https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentialw)) but `Windows Credential Manager applet` does not have any way to show this information to user. So if an user edits the saved credentials using control panel comments and attributes gets lost. The lack of this information may be used as a tamper check. Note that this information is accessible all programs with can read write to credential store, so don't assume the information is secure from everything. 42 | 43 | #### 4. Save and retrieve credentials with comments and attributes 44 | ```C# 45 | var cred = (new NetworkCredential(uName, pwd, domain)).ToICredential(); 46 | cred.TargetName = "TestSystem_Attributes"; 47 | cred.Attributes = new Dictionary(); 48 | var sample = new SampleAttribute() { role = "regular", created = DateTime.UtcNow }; 49 | cred.Attributes.Add("sampleAttribute", sample); 50 | cred.Comment = "This comment is only visible via API, not in Windows UI"; 51 | cred.SaveCredential(); 52 | ``` 53 | 54 | #### 5. Getting ICredential from previously saved credential 55 | ```C# 56 | var cred = CredentialManager.GetICredential(TargetName); 57 | cred.Comment = "Update the credential data and save back"; 58 | cred.SaveCredential(); 59 | ``` 60 | #### deprecated 61 | [AdysTech.CredentialManager.Core](https://www.nuget.org/packages/AdysTech.CredentialManager.Core/) -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | version: 0.{build} 2 | image: Visual Studio 2019 3 | pull_requests: 4 | do_not_increment_build_number: true 5 | branches: 6 | only: 7 | - master 8 | before_build: 9 | - nuget restore 10 | - dotnet restore 11 | configuration: 12 | - Release 13 | build: 14 | verbosity: minimal 15 | # publish_nuget: true 16 | publish_nuget_symbols: true 17 | use_snupkg_format: true 18 | test: 19 | categories: 20 | only: 21 | - AppVeyor 22 | deploy: 23 | - provider: NuGet 24 | api_key: 25 | secure: z+VCuAl52RiKf1Ie2p6ivQloYTH12q7H0G6fEl/c4FepMD75/vI7oRnbMISdgWEm 26 | on: 27 | branch: master 28 | artifacts: 29 | - path: src\AdysTech.CredentialManager\bin\Debug 30 | name: CredentialManager 31 | - path: src\AdysTech.CredentialManager\bin\**\*nupkg 32 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## v2.6.0 [Jan 18, 2022] 2 | 3 | ### Release Notes 4 | 5 | This version allows to save blank passwords with an additional `AllowBlankPassowrd` paremeter. Fixes #67. 6 | 7 | ## v2.5.0 [Nov 10, 2021] 8 | 9 | ### Release Notes 10 | 11 | Update max credential length to 2560 charecters. Fixes #65, Thanks to @ldattilo 12 | 13 | ## v2.4.0 [Aug 30, 2021] 14 | 15 | ### Release Notes 16 | 17 | Fixes #62 allowing to save single charecter passowrds, thanks to @aschoelzhorn 18 | 19 | 20 | 21 | ## v2.3.0 [Dec 20, 2020] 22 | 23 | ### Release Notes 24 | 25 | This version adds RemoveCredential. Fixes #59 26 | 27 | ## v2.2.0 [May 27, 2020] 28 | 29 | ### Release Notes 30 | 31 | This version adds EnumerateICredentials. Housekeeping to move common project properties to build.props. 32 | 33 | ### Bugfixes 34 | 35 | - [#47](https://github.com/AdysTech/CredentialManager/issues/47): Getting Multiple targets 36 | 37 | ### Features 38 | 39 | - [#46](https://github.com/AdysTech/CredentialManager/pull/46): Add EnumerateICredentials . Thanks to @strzalkowski 40 | 41 | ## v2.1.0 [May 20, 2020] 42 | 43 | ### Release Notes 44 | 45 | This version merges the .NET Framework and .NET Core projects into one multi-target project. 46 | 47 | ### Bugfixes 48 | 49 | - [#42](https://github.com/AdysTech/CredentialManager/pull/42): Fix NullReferenceException when credentials not found. Thanks to @LePtitDev 50 | 51 | ### Features 52 | 53 | - [#41](https://github.com/AdysTech/CredentialManager/pull/41): Use single project to target .NET Framework & Core. SDK-style projects allow multi-targeting which makes this much simpler. Thanks to @drewnoakes 54 | 55 | ### Breaking Change 56 | - since [main Nuget Package](https://www.nuget.org/packages/AdysTech.CredentialManager) supports .NET Core specific Nuget package will be deprecated. 57 | 58 | 59 | ## v2.0.0 [Apr 20, 2020] 60 | 61 | ### Release Notes 62 | 63 | This version support to expose raw credential, with additional information not available in normal `NetworkCredential` available in previous versions. Currently there is a open [issue #30](https://github.com/AdysTech/CredentialManager/issues/30) for quite some asking for this feature. 64 | 65 | This version also adds support to store comments and additional attributes associated with a Credential object. The attributes are serialized using `BinaryFormatter` and API has 256 byte length. `BinaryFormatter` generates larger than what you think the object size is going to be, si keep an eye on that. 66 | 67 | Comments and attributes are only accessible programmatically. Windows always supported such a feature (via `CREDENTIALW` [structure](https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentialw)) but `Windows Credential Manager applet` does not have any way to show this information to user. So if an user edits the saved credentials using control panel comments and attributes gets lost. The lack of this information may be used as a tamper check. Note that this information is accessible all programs with can read write to credential store, so don't assume the information is secure from everything. 68 | 69 | ### Bugfixes 70 | 71 | - [#39](https://github.com/AdysTech/CredentialManager/issues/39): Password is exposed in the process memory after saving in the Windows credentials storage 72 | 73 | ### Features 74 | 75 | - [#30](https://github.com/AdysTech/CredentialManager/issues/30): Expose properties of Credential object which are not part of generic `NetworkCredential` 76 | - Ability to add comments to saved credentials. Use the `ICredential` returned from `SaveCredentials`, and call save on the interface for the second time after updating comment. 77 | - Ability to read write attributes to credentials. These Attributes can be any binary data, including strings, user roles etc, as it applies to use case. 78 | - New `CredentialAPIException` to indicate the credential API failures, with API name. 79 | 80 | ### Breaking Change 81 | - `SaveCredentials` return type changed from `bool` to `ICredential`, giving reference to just saved instance. 82 | - `ToNetworkCredential` doesn't throw `Win32Exception` anymore. It will throw `InvalidOperationException` or new `CredentialAPIException` instead. 83 | - `CredentialManager.CredentialType` enum is removed. Use `CredentialType` instead. 84 | 85 | 86 | 87 | ## Acknowledging external contributors to previous versions. 88 | 89 | ### v1.9.5.0 [Dec 22, 2019] 90 | - Add CredentialType as an extra optional parameter when saving credentials, Thanks to @esrahofstede 91 | 92 | ### v1.0.1.0 [Dec 11, 2019] 93 | - add strong name to nuget, Thanks to @kvvinokurov 94 | 95 | ### v1.9.0.0 [Nov 9, 2019] 96 | - [#31](https://github.com/AdysTech/CredentialManager/issues/31): Support .NET Standard, Thanks to @RussKie for the issue 97 | 98 | ### v1.8.0.0 [Feb 25, 2019] 99 | - Add EnumerateCredentials, Thanks to @erikjon 100 | 101 | ### v1.7.0.0 [Sep 26, 2018] 102 | - Allow prefilled user name, Thanks to @jairbubbles 103 | 104 | 105 | ### v1.6.0.0 [Sep 24, 2018] 106 | - Fix buffer sizes in ParseUserName, Thanks to @jairbubbles 107 | 108 | ### v1.2.1.0 [Oct 25, 2017] 109 | - Don't crash if credential not found, Thanks to @pmiossec 110 | 111 | ### v1.2.1.0 [Oct 25, 2017] 112 | - Corrections to Error message, Thanks to @nguillermin 113 | 114 | ### v1.1.0.0 [Jan 9, 2017] 115 | - Initial Release -------------------------------------------------------------------------------- /key.snk: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/AdysTech/CredentialManager/d30cf565f0ec8d20729c72a80ee9ab7fcb4677a2/key.snk -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/AdysTech.CredentialManager.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | C# wrapper around CredWrite / CredRead functions to store and retrieve from Windows Credential Store. 6 | The library .NET Standard 1.2+ and .NET Framework 4.5+ and provides 7 | 1. Prompt user for Windows Generic Credentials 8 | 2. Save Windows Generic Credentials 9 | 3. Retrieve saved Windows Generic Credentials 10 | 4. Add or edit ccomments, store additional attribute objects associated with Credentials 11 | 2.6.0.0 12 | true 13 | true 14 | snupkg 15 | © AdysTech 2016-2022 16 | 2.6.0.0 17 | 2.6.0.0 18 | 19 | 20 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/Credential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Net; 5 | using System.Runtime.InteropServices; 6 | using System.Runtime.Serialization; 7 | using System.Runtime.Serialization.Formatters.Binary; 8 | using System.Text; 9 | 10 | namespace AdysTech.CredentialManager 11 | { 12 | internal class Credential : ICredential 13 | { 14 | 15 | public CredentialType Type { get; set; } 16 | public string TargetName { get; set; } 17 | public string Comment { get; set; } 18 | public DateTime LastWritten { get; set; } 19 | public string CredentialBlob { get; set; } 20 | public Persistance Persistance { get; set; } 21 | public IDictionary Attributes { get; set; } 22 | public string UserName { get; set; } 23 | 24 | 25 | public UInt32 Flags; 26 | public string TargetAlias; 27 | 28 | /// 29 | /// Maximum size in bytes of a credential that can be stored. While the API 30 | /// documentation lists 512 as the max size, the current Windows SDK sets 31 | /// it to 5*512 via CRED_MAX_CREDENTIAL_BLOB_SIZE in wincred.h. This has 32 | /// been verified to work on Windows Server 2016 and later. 33 | /// 34 | /// API Doc: https://docs.microsoft.com/en-us/windows/win32/api/wincred/ns-wincred-credentiala 35 | /// 36 | /// 37 | /// 38 | /// This only controls the guard in the library. The actual underlying OS 39 | /// controls the actual limit. Operating Systems older than Windows Server 40 | /// 2016 may only support 512 bytes. 41 | /// 42 | /// Tokens often are 1040 bytes or more. 43 | /// 44 | /// 45 | internal const int MaxCredentialBlobSize = 2560; 46 | 47 | internal Credential(NativeCode.NativeCredential ncred) 48 | { 49 | Flags = ncred.Flags; 50 | TargetName = ncred.TargetName; 51 | Comment = ncred.Comment; 52 | try 53 | { 54 | #pragma warning disable CS0675 // Bitwise-or operator used on a sign-extended operand 55 | LastWritten = DateTime.FromFileTime((long)((ulong)ncred.LastWritten.dwHighDateTime << 32 | (uint)ncred.LastWritten.dwLowDateTime)); 56 | #pragma warning restore CS0675 // Bitwise-or operator used on a sign-extended operand 57 | } 58 | catch (ArgumentOutOfRangeException) 59 | { } 60 | 61 | 62 | var CredentialBlobSize = ncred.CredentialBlobSize; 63 | if (ncred.CredentialBlobSize >= 2) 64 | { 65 | CredentialBlob = Marshal.PtrToStringUni(ncred.CredentialBlob, (int)ncred.CredentialBlobSize / 2); 66 | } 67 | Persistance = (Persistance)ncred.Persist; 68 | var AttributeCount = ncred.AttributeCount; 69 | if (AttributeCount > 0) 70 | { 71 | try 72 | { 73 | var formatter = new BinaryFormatter(); 74 | var attribSize = Marshal.SizeOf(typeof(NativeCode.NativeCredentialAttribute)); 75 | Attributes = new Dictionary(); 76 | byte[] rawData = new byte[AttributeCount * attribSize]; 77 | var buffer = Marshal.AllocHGlobal(attribSize); 78 | Marshal.Copy(ncred.Attributes, rawData, (int)0, (int)AttributeCount * attribSize); 79 | for (int i = 0; i < AttributeCount; i++) 80 | { 81 | Marshal.Copy(rawData, i * attribSize, buffer, attribSize); 82 | var attr = (NativeCode.NativeCredentialAttribute)Marshal.PtrToStructure(buffer, 83 | typeof(NativeCode.NativeCredentialAttribute)); 84 | var key = attr.Keyword; 85 | var val = new byte[attr.ValueSize]; 86 | Marshal.Copy(attr.Value, val, (int)0, (int)attr.ValueSize); 87 | using var stream = new MemoryStream(val, false); 88 | Attributes.Add(key, formatter.Deserialize(stream)); 89 | } 90 | Marshal.FreeHGlobal(buffer); 91 | rawData = null; 92 | } 93 | catch 94 | { 95 | 96 | } 97 | } 98 | TargetAlias = ncred.TargetAlias; 99 | UserName = ncred.UserName; 100 | Type = (CredentialType)ncred.Type; 101 | } 102 | 103 | public Credential(System.Net.NetworkCredential credential) 104 | { 105 | CredentialBlob = credential.Password; 106 | UserName = String.IsNullOrWhiteSpace(credential.Domain) ? credential.UserName : credential.Domain + "\\" + credential.UserName; 107 | Attributes = null; 108 | Comment = null; 109 | TargetAlias = null; 110 | Type = CredentialType.Generic; 111 | Persistance = Persistance.Session; 112 | } 113 | 114 | public Credential(ICredential credential) 115 | { 116 | CredentialBlob = credential.CredentialBlob; 117 | UserName = credential.UserName; 118 | if (credential.Attributes?.Count > 0) 119 | { 120 | this.Attributes = new Dictionary(); 121 | foreach (var a in credential.Attributes) 122 | { 123 | Attributes.Add(a); 124 | } 125 | } 126 | Comment = credential.Comment; 127 | TargetAlias = null; 128 | Type = credential.Type; 129 | Persistance = credential.Persistance; 130 | } 131 | 132 | public Credential(string target, CredentialType type) 133 | { 134 | Type = type; 135 | TargetName = target; 136 | } 137 | 138 | public NetworkCredential ToNetworkCredential() 139 | { 140 | if (!string.IsNullOrEmpty(UserName)) 141 | { 142 | var userBuilder = new StringBuilder(UserName.Length + 2); 143 | var domainBuilder = new StringBuilder(UserName.Length + 2); 144 | 145 | var returnCode = NativeCode.CredUIParseUserName(UserName, userBuilder, userBuilder.Capacity, domainBuilder, domainBuilder.Capacity); 146 | var lastError = Marshal.GetLastWin32Error(); 147 | 148 | //assuming invalid account name to be not meeting condition for CredUIParseUserName 149 | //"The name must be in UPN or down-level format, or a certificate" 150 | if (returnCode == NativeCode.CredentialUIReturnCodes.InvalidAccountName) 151 | { 152 | userBuilder.Append(UserName); 153 | } 154 | else if (returnCode != 0) 155 | { 156 | throw new CredentialAPIException($"Unable to Parse UserName", "CredUIParseUserName", lastError); 157 | } 158 | 159 | return new NetworkCredential(userBuilder.ToString(), this.CredentialBlob, domainBuilder.ToString()); 160 | } 161 | else 162 | { 163 | return new NetworkCredential(UserName, this.CredentialBlob); 164 | } 165 | } 166 | 167 | public bool SaveCredential(bool AllowBlankPassword=false) 168 | { 169 | IntPtr buffer = default(IntPtr); 170 | GCHandle pinned = default(GCHandle); 171 | 172 | if (!String.IsNullOrEmpty(this.Comment) && Encoding.Unicode.GetBytes(this.Comment).Length > 256) 173 | throw new ArgumentException("Comment can't be more than 256 bytes long", "Comment"); 174 | 175 | if (String.IsNullOrEmpty(this.TargetName)) 176 | throw new ArgumentNullException("TargetName", "TargetName can't be Null or Empty"); 177 | else if (this.TargetName.Length > 32767) 178 | throw new ArgumentNullException("TargetName can't be more than 32kB", "TargetName"); 179 | 180 | if (!AllowBlankPassword && String.IsNullOrEmpty(this.CredentialBlob)) 181 | throw new ArgumentNullException("CredentialBlob", "CredentialBlob can't be Null or Empty"); 182 | 183 | NativeCode.NativeCredential ncred = new NativeCode.NativeCredential 184 | { 185 | Comment = this.Comment, 186 | TargetAlias = null, 187 | Type = (UInt32)this.Type, 188 | Persist = (UInt32)this.Persistance, 189 | UserName = this.UserName, 190 | TargetName = this.TargetName, 191 | CredentialBlobSize = (UInt32)Encoding.Unicode.GetBytes(this.CredentialBlob).Length 192 | }; 193 | if (ncred.CredentialBlobSize > MaxCredentialBlobSize) 194 | throw new ArgumentException($"Credential can't be more than {MaxCredentialBlobSize} bytes long", "CredentialBlob"); 195 | 196 | ncred.CredentialBlob = Marshal.StringToCoTaskMemUni(this.CredentialBlob); 197 | if (this.LastWritten != DateTime.MinValue) 198 | { 199 | var fileTime = this.LastWritten.ToFileTimeUtc(); 200 | ncred.LastWritten.dwLowDateTime = (int)(fileTime & 0xFFFFFFFFL); 201 | ncred.LastWritten.dwHighDateTime = (int)((fileTime >> 32) & 0xFFFFFFFFL); 202 | } 203 | 204 | NativeCode.NativeCredentialAttribute[] nativeAttribs = null; 205 | try 206 | { 207 | if (Attributes == null || Attributes.Count == 0) 208 | { 209 | ncred.AttributeCount = 0; 210 | ncred.Attributes = IntPtr.Zero; 211 | } 212 | else 213 | { 214 | if (Attributes.Count > 64) 215 | throw new ArgumentException("Credentials can't have more than 64 Attributes!!"); 216 | 217 | ncred.AttributeCount = (UInt32)Attributes.Count; 218 | nativeAttribs = new NativeCode.NativeCredentialAttribute[Attributes.Count]; 219 | var attribSize = Marshal.SizeOf(typeof(NativeCode.NativeCredentialAttribute)); 220 | byte[] rawData = new byte[Attributes.Count * attribSize]; 221 | buffer = Marshal.AllocHGlobal(attribSize); 222 | var formatter = new BinaryFormatter(); 223 | 224 | var i = 0; 225 | foreach (var a in Attributes) 226 | { 227 | if (a.Key.Length > 256) 228 | throw new ArgumentException($"Attribute names can't be more than 256 bytes long. Error with key:{a.Key}", a.Key); 229 | if (a.Value == null) 230 | throw new ArgumentNullException(a.Key, $"Attribute value cant'be null. Error with key:{a.Key}"); 231 | if (!a.Value.GetType().IsSerializable) 232 | throw new ArgumentException($"Attribute value must be Serializable. Error with key:{a.Key}", a.Key); 233 | 234 | using var stream = new MemoryStream(); 235 | formatter.Serialize(stream, a.Value); 236 | var value = stream.ToArray(); 237 | 238 | if (value.Length > 256) 239 | throw new ArgumentException($"Attribute values can't be more than 256 bytes long after serialization. Error with Value for key:{a.Key}", a.Key); 240 | 241 | var attrib = new NativeCode.NativeCredentialAttribute 242 | { 243 | Keyword = a.Key, 244 | ValueSize = (UInt32)value.Length 245 | }; 246 | 247 | attrib.Value = Marshal.AllocHGlobal(value.Length); 248 | Marshal.Copy(value, 0, attrib.Value, value.Length); 249 | nativeAttribs[i] = attrib; 250 | 251 | Marshal.StructureToPtr(attrib, buffer, false); 252 | Marshal.Copy(buffer, rawData, i * attribSize, attribSize); 253 | i++; 254 | } 255 | pinned = GCHandle.Alloc(rawData, GCHandleType.Pinned); 256 | ncred.Attributes = pinned.AddrOfPinnedObject(); 257 | } 258 | // Write the info into the CredMan storage. 259 | 260 | if (NativeCode.CredWrite(ref ncred, 0)) 261 | { 262 | return true; 263 | } 264 | else 265 | { 266 | int lastError = Marshal.GetLastWin32Error(); 267 | throw new CredentialAPIException($"Unable to Save Credential", "CredWrite", lastError); 268 | } 269 | } 270 | 271 | finally 272 | { 273 | if (ncred.CredentialBlob != default(IntPtr)) 274 | Marshal.FreeCoTaskMem(ncred.CredentialBlob); 275 | if (nativeAttribs != null) 276 | { 277 | foreach (var a in nativeAttribs) 278 | { 279 | if (a.Value != default(IntPtr)) 280 | Marshal.FreeHGlobal(a.Value); 281 | } 282 | if (pinned.IsAllocated) 283 | pinned.Free(); 284 | if (buffer != default(IntPtr)) 285 | Marshal.FreeHGlobal(buffer); 286 | } 287 | } 288 | } 289 | 290 | public bool RemoveCredential() 291 | { 292 | // Make the API call using the P/Invoke signature 293 | var isSuccess = NativeCode.CredDelete(TargetName, (uint)Type, 0); 294 | 295 | if (isSuccess) 296 | return true; 297 | 298 | int lastError = Marshal.GetLastWin32Error(); 299 | throw new CredentialAPIException($"Unable to Delete Credential", "CredDelete", lastError); 300 | } 301 | } 302 | 303 | } 304 | 305 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/CredentialAPIException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net; 4 | using System.Runtime.InteropServices; 5 | 6 | namespace AdysTech.CredentialManager 7 | { 8 | [Serializable] 9 | public class CredentialAPIException : ExternalException 10 | { 11 | public string APIName { get; internal set; } 12 | 13 | public CredentialAPIException(string message,string api, int errorCode) : base(message, errorCode) 14 | { 15 | APIName = api; 16 | } 17 | 18 | public CredentialAPIException(string message, int errorCode): base(message, errorCode) 19 | { 20 | 21 | } 22 | 23 | public CredentialAPIException(string message) : base(message) 24 | { 25 | } 26 | 27 | public CredentialAPIException(string message, Exception innerException) : base(message, innerException) 28 | { 29 | } 30 | 31 | public CredentialAPIException() 32 | { 33 | } 34 | 35 | protected CredentialAPIException(System.Runtime.Serialization.SerializationInfo serializationInfo, System.Runtime.Serialization.StreamingContext streamingContext):base(serializationInfo,streamingContext) 36 | { 37 | 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/CredentialExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Net; 2 | 3 | namespace AdysTech.CredentialManager 4 | { 5 | public static class CredentialExtensions 6 | { 7 | public static ICredential ToICredential(this NetworkCredential cred) 8 | { 9 | if (cred == null) 10 | { 11 | return null; 12 | } 13 | 14 | return new Credential(cred); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/CredentialManager.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.ComponentModel; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Net; 7 | using System.Runtime.InteropServices; 8 | using System.Text; 9 | 10 | namespace AdysTech.CredentialManager 11 | { 12 | //ref: https://docs.microsoft.com/en-us/archive/blogs/peerchan/application-password-security 13 | 14 | public static class CredentialManager 15 | { 16 | private static bool PromptForCredentials(string target, NativeCode.CredentialUIInfo credUI, ref bool save, ref string user, out string password, out string domain) 17 | { 18 | password = string.Empty; 19 | domain = string.Empty; 20 | 21 | // Setup the flags and variables 22 | credUI.cbSize = Marshal.SizeOf(credUI); 23 | int errorcode = 0; 24 | uint authPackage = 0; 25 | 26 | IntPtr outCredBuffer; 27 | var flags = NativeCode.PromptForWindowsCredentialsFlags.GenericCredentials | 28 | NativeCode.PromptForWindowsCredentialsFlags.EnumerateCurrentUser; 29 | flags = save ? flags | NativeCode.PromptForWindowsCredentialsFlags.ShowCheckbox : flags; 30 | 31 | // Prefill username 32 | IntPtr inCredBuffer; 33 | int inCredSize; 34 | GetInputBuffer(user, out inCredBuffer, out inCredSize); 35 | 36 | // Setup the flags and variables 37 | int result = NativeCode.CredUIPromptForWindowsCredentials(ref credUI, 38 | errorcode, 39 | ref authPackage, 40 | inCredBuffer, 41 | inCredSize, 42 | out outCredBuffer, 43 | out uint outCredSize, 44 | ref save, 45 | flags); 46 | 47 | if (inCredBuffer != IntPtr.Zero) 48 | { 49 | NativeCode.CoTaskMemFree(inCredBuffer); 50 | } 51 | 52 | if (result == 0) 53 | { 54 | GetCredentialsFromOutputBuffer(ref user, ref password, ref domain, outCredBuffer, outCredSize); 55 | return true; 56 | } 57 | 58 | user = null; 59 | domain = null; 60 | return false; 61 | } 62 | 63 | private static void GetCredentialsFromOutputBuffer(ref string user, ref string password, ref string domain, IntPtr outCredBuffer, uint outCredSize) 64 | { 65 | int maxUserName = 100; 66 | int maxDomain = 100; 67 | int maxPassword = 100; 68 | var usernameBuf = new StringBuilder(maxUserName); 69 | var passwordBuf = new StringBuilder(maxDomain); 70 | var domainBuf = new StringBuilder(maxPassword); 71 | 72 | if (NativeCode.CredUnPackAuthenticationBuffer(0, outCredBuffer, outCredSize, usernameBuf, ref maxUserName, 73 | domainBuf, ref maxDomain, passwordBuf, ref maxPassword)) 74 | { 75 | user = usernameBuf.ToString(); 76 | password = passwordBuf.ToString(); 77 | domain = domainBuf.ToString(); 78 | if (string.IsNullOrWhiteSpace(domain)) 79 | { 80 | Debug.WriteLine("Domain null"); 81 | if (!ParseUserName(usernameBuf.ToString(), usernameBuf.Capacity, domainBuf.Capacity, out user, out domain)) 82 | user = usernameBuf.ToString(); 83 | password = passwordBuf.ToString(); 84 | } 85 | } 86 | 87 | //mimic SecureZeroMem function to make sure buffer is zeroed out. SecureZeroMem is not an exported function, neither is RtlSecureZeroMemory 88 | var zeroBytes = new byte[outCredSize]; 89 | Marshal.Copy(zeroBytes, 0, outCredBuffer, (int)outCredSize); 90 | 91 | //clear the memory allocated by CredUIPromptForWindowsCredentials 92 | NativeCode.CoTaskMemFree(outCredBuffer); 93 | } 94 | 95 | private static void GetInputBuffer(string user, out IntPtr inCredBuffer, out int inCredSize) 96 | { 97 | if (!string.IsNullOrEmpty(user)) 98 | { 99 | var usernameBuf = new StringBuilder(user); 100 | var passwordBuf = new StringBuilder(); 101 | 102 | inCredSize = 1024; 103 | inCredBuffer = Marshal.AllocCoTaskMem(inCredSize); 104 | if (NativeCode.CredPackAuthenticationBuffer(0x00, usernameBuf, passwordBuf, inCredBuffer, ref inCredSize)) 105 | return; 106 | 107 | if (inCredBuffer != IntPtr.Zero) 108 | { 109 | NativeCode.CoTaskMemFree(inCredBuffer); 110 | } 111 | } 112 | 113 | inCredBuffer = IntPtr.Zero; 114 | inCredSize = 0; 115 | } 116 | 117 | internal static bool ParseUserName(string usernameBuf, int maxUserName, int maxDomain, out string user, out string domain) 118 | { 119 | var userBuilder = new StringBuilder(maxUserName); 120 | var domainBuilder = new StringBuilder(maxDomain); 121 | user = String.Empty; 122 | domain = String.Empty; 123 | 124 | var returnCode = NativeCode.CredUIParseUserName(usernameBuf, userBuilder, maxUserName, domainBuilder, maxDomain); 125 | Debug.WriteLine(returnCode); 126 | switch (returnCode) 127 | { 128 | case NativeCode.CredentialUIReturnCodes.Success: // The username is valid. 129 | user = userBuilder.ToString(); 130 | domain = domainBuilder.ToString(); 131 | return true; 132 | } 133 | return false; 134 | } 135 | 136 | internal static bool PromptForCredentials(string target, ref bool save, ref string user, out string password, out string domain, IntPtr parentWindowHandle = default) 137 | { 138 | var credUI = new NativeCode.CredentialUIInfo 139 | { 140 | hwndParent = parentWindowHandle, 141 | pszMessageText = " ", 142 | pszCaptionText = " ", 143 | hbmBanner = IntPtr.Zero 144 | }; 145 | return PromptForCredentials(target, credUI, ref save, ref user, out password, out domain); 146 | } 147 | 148 | internal static bool PromptForCredentials(string target, ref bool save, string message, string caption, ref string user, out string password, out string domain, IntPtr parentWindowHandle = default) 149 | { 150 | var credUI = new NativeCode.CredentialUIInfo 151 | { 152 | hwndParent = parentWindowHandle, 153 | pszMessageText = message, 154 | pszCaptionText = caption, 155 | hbmBanner = IntPtr.Zero 156 | }; 157 | return PromptForCredentials(target, credUI, ref save, ref user, out password, out domain); 158 | } 159 | 160 | /// 161 | /// Opens OS Version specific Window prompting for credentials 162 | /// 163 | /// A descriptive text for where teh credentials being asked are used for 164 | /// Whether or not to offer the checkbox to save the credentials 165 | /// NetworkCredential object containing the user name, 166 | public static NetworkCredential PromptForCredentials(string target, ref bool save, IntPtr parentWindowHandle = default) 167 | { 168 | string username = "", password, domain; 169 | return PromptForCredentials(target, ref save, ref username, out password, out domain, parentWindowHandle) ? new NetworkCredential(username, password, domain) : null; 170 | } 171 | 172 | /// 173 | /// Opens OS Version specific Window prompting for credentials 174 | /// 175 | /// A descriptive text for where teh credentials being asked are used for 176 | /// Whether or not to offer the checkbox to save the credentials 177 | /// A brief message to display in the dialog box 178 | /// Title for the dialog box 179 | /// NetworkCredential object containing the user name, 180 | public static NetworkCredential PromptForCredentials(string target, ref bool save, string message, string caption, IntPtr parentWindowHandle = default) 181 | { 182 | string username = "", password, domain; 183 | return PromptForCredentials(target, ref save, message, caption, ref username, out password, out domain, parentWindowHandle) ? new NetworkCredential(username, password, domain) : null; 184 | } 185 | 186 | /// 187 | /// Opens OS Version specific Window prompting for credentials 188 | /// 189 | /// A descriptive text for where teh credentials being asked are used for 190 | /// Whether or not to offer the checkbox to save the credentials 191 | /// A brief message to display in the dialog box 192 | /// Title for the dialog box 193 | /// Default value for username 194 | /// NetworkCredential object containing the user name, 195 | public static NetworkCredential PromptForCredentials(string target, ref bool save, string message, string caption, string defaultUserName, IntPtr parentWindowHandle = default) 196 | { 197 | string username = defaultUserName, password, domain; 198 | return PromptForCredentials(target, ref save, message, caption, ref username, out password, out domain, parentWindowHandle) ? new NetworkCredential(username, password, domain) : null; 199 | } 200 | 201 | /// 202 | /// Accepts credentials in a console window 203 | /// 204 | /// A descriptive text for where teh credentials being asked are used for 205 | /// NetworkCredential object containing the user name, 206 | public static NetworkCredential PromptForCredentialsConsole(string target) 207 | { 208 | var user = String.Empty; 209 | var domain = string.Empty; 210 | 211 | // Setup the flags and variables 212 | StringBuilder userPassword = new StringBuilder(), userID = new StringBuilder(); 213 | bool save = true; 214 | NativeCode.CredentialUIFlags flags = NativeCode.CredentialUIFlags.CompleteUsername | NativeCode.CredentialUIFlags.ExcludeCertificates | NativeCode.CredentialUIFlags.GenericCredentials; 215 | 216 | // Prompt the user 217 | NativeCode.CredentialUIReturnCodes returnCode = NativeCode.CredUICmdLinePromptForCredentials(target, IntPtr.Zero, 0, userID, 100, userPassword, 100, ref save, flags); 218 | 219 | string password = userPassword.ToString(); 220 | 221 | StringBuilder userBuilder = new StringBuilder(); 222 | StringBuilder domainBuilder = new StringBuilder(); 223 | 224 | returnCode = NativeCode.CredUIParseUserName(userID.ToString(), userBuilder, int.MaxValue, domainBuilder, int.MaxValue); 225 | switch (returnCode) 226 | { 227 | case NativeCode.CredentialUIReturnCodes.Success: // The username is valid. 228 | user = userBuilder.ToString(); 229 | domain = domainBuilder.ToString(); 230 | break; 231 | 232 | case NativeCode.CredentialUIReturnCodes.InvalidAccountName: // The username is not valid. 233 | user = userID.ToString(); 234 | domain = null; 235 | break; 236 | 237 | case NativeCode.CredentialUIReturnCodes.InsufficientBuffer: // One of the buffers is too small. 238 | throw new OutOfMemoryException(); 239 | 240 | case NativeCode.CredentialUIReturnCodes.InvalidParameter: // ulUserMaxChars or ulDomainMaxChars is zero OR userName, user, or domain is NULL. 241 | throw new ArgumentException("userName"); 242 | } 243 | return new NetworkCredential(user, password, domain); 244 | } 245 | 246 | /// 247 | /// Saves the given Network Credential into Windows Credential store 248 | /// 249 | /// Name of the application/Url where the credential is used for 250 | /// Credential to store 251 | /// Credential type 252 | /// True:Success, throw if failed 253 | public static ICredential SaveCredentials(string target, NetworkCredential credential, CredentialType type = CredentialType.Generic, bool AllowNullPassword= false) 254 | { 255 | // Go ahead with what we have are stuff it into the CredMan structures. 256 | var cred = new Credential(credential) 257 | { 258 | TargetName = target, 259 | Persistance = Persistance.Enterprise, 260 | Type = type 261 | }; 262 | if( cred.SaveCredential(AllowNullPassword)) 263 | { 264 | return cred; 265 | } 266 | return null; 267 | } 268 | 269 | /// 270 | /// Extract the stored credential from Windows Credential store 271 | /// 272 | /// Name of the application/Url where the credential is used for 273 | /// Credential type 274 | /// return the credentials if success, null if target not found, throw if failed to read stored credentials 275 | public static NetworkCredential GetCredentials(string target, CredentialType type = CredentialType.Generic) 276 | { 277 | return (GetICredential(target, type) as Credential)?.ToNetworkCredential(); 278 | } 279 | 280 | /// 281 | /// Enumerate the specified stored credentials in the Windows Credential store 282 | /// 283 | /// Name of the application or URL for which the credential is used 284 | /// Return a if success, null if target not found, throw if failed to read stored credentials 285 | public static List EnumerateCredentials(string target = null) 286 | { 287 | return EnumerateICredentials(target)?.Select(c => c.ToNetworkCredential())?.ToList(); 288 | } 289 | 290 | /// 291 | /// Enumerate the specified stored credentials in the Windows Credential store 292 | /// 293 | /// Name of the application or URL for which the credential is used 294 | /// Return a if success, null if target not found, throw if failed to read stored credentials 295 | public static List EnumerateICredentials(string target = null) 296 | { 297 | IntPtr pCredentials = IntPtr.Zero; 298 | uint count = 0; 299 | 300 | var success = NativeCode.CredEnumerate(target, 0, out count, out pCredentials); 301 | 302 | if (!success) 303 | { 304 | var lastError = Marshal.GetLastWin32Error(); 305 | if (lastError == (int)NativeCode.CredentialUIReturnCodes.NotFound) 306 | { 307 | return null; 308 | } 309 | 310 | throw new CredentialAPIException($"Unable to Enumerate Credential store", "CredEnumerate", lastError); 311 | } 312 | 313 | List networkCredentials = new List(); 314 | Credential[] credentials; 315 | 316 | try 317 | { 318 | using var criticalSection = new CriticalCredentialHandle(pCredentials); 319 | credentials = criticalSection.EnumerateCredentials(count); 320 | } 321 | catch (Exception) 322 | { 323 | return null; 324 | } 325 | 326 | return credentials.Select(c => c as ICredential).ToList(); 327 | } 328 | 329 | /// 330 | /// Remove stored credentials from windows credential store 331 | /// 332 | /// Name of the application/Url where the credential is used for 333 | /// True: Success, throw if failed 334 | public static bool RemoveCredentials(string target, CredentialType type = CredentialType.Generic) 335 | { 336 | var cred = new Credential( 337 | target, 338 | type 339 | ); 340 | return cred.RemoveCredential(); 341 | } 342 | 343 | /// 344 | /// Generates a string that can be used for "Auth" headers in web requests, "username:password" encoded in Base64 345 | /// 346 | /// 347 | /// 348 | public static string GetBasicAuthString(this NetworkCredential cred) 349 | { 350 | byte[] credentialBuffer = new UTF8Encoding().GetBytes(cred.UserName + ":" + cred.Password); 351 | return Convert.ToBase64String(credentialBuffer); 352 | } 353 | 354 | /// 355 | /// Extract the stored credential from Windows Credential store 356 | /// 357 | /// Name of the application/Url where the credential is used for 358 | /// Credential type 359 | /// return the ICredential if success, null if target not found, throw if failed to read stored credentials 360 | public static ICredential GetICredential(string target, CredentialType type = CredentialType.Generic) 361 | { 362 | IntPtr nCredPtr; 363 | 364 | // Make the API call using the P/Invoke signature 365 | bool isSuccess = NativeCode.CredRead(target, (UInt32)type, 0, out nCredPtr); 366 | if (!isSuccess) 367 | { 368 | var lastError = Marshal.GetLastWin32Error(); 369 | if (lastError == (int)NativeCode.CredentialUIReturnCodes.NotFound) 370 | return null; 371 | throw new CredentialAPIException($"Unable to Read Credential", "CredRead", lastError); 372 | } 373 | 374 | try 375 | { 376 | using var critCred = new CriticalCredentialHandle(nCredPtr); 377 | Credential cred = critCred.GetCredential(); 378 | return cred; 379 | } 380 | catch (Exception) 381 | { 382 | return null; 383 | } 384 | } 385 | } 386 | } -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/CriticalCredentialHandle.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Win32.SafeHandles; 2 | using System; 3 | using System.Runtime.InteropServices; 4 | 5 | namespace AdysTech.CredentialManager 6 | { 7 | sealed class CriticalCredentialHandle : CriticalHandleZeroOrMinusOneIsInvalid 8 | { 9 | // Set the handle. 10 | internal CriticalCredentialHandle(IntPtr preexistingHandle) 11 | { 12 | SetHandle (preexistingHandle); 13 | } 14 | 15 | internal Credential GetCredential() 16 | { 17 | if ( !IsInvalid ) 18 | { 19 | // Get the Credential from the mem location 20 | NativeCode.NativeCredential ncred = (NativeCode.NativeCredential) Marshal.PtrToStructure (handle, 21 | typeof (NativeCode.NativeCredential)); 22 | 23 | // Create a managed Credential type and fill it with data from the native counterpart. 24 | Credential cred = new Credential (ncred); 25 | 26 | return cred; 27 | } 28 | else 29 | { 30 | throw new InvalidOperationException ("Invalid CriticalHandle!"); 31 | } 32 | } 33 | 34 | internal Credential[] EnumerateCredentials(uint size) 35 | { 36 | if (!IsInvalid) 37 | { 38 | var credentialArray = new Credential[size]; 39 | 40 | for (int i = 0; i < size; i++) 41 | { 42 | IntPtr ptrPlc = Marshal.ReadIntPtr(handle, i * IntPtr.Size); 43 | 44 | var nc = (NativeCode.NativeCredential)Marshal.PtrToStructure(ptrPlc, typeof(NativeCode.NativeCredential)); 45 | 46 | credentialArray[i] = new Credential(nc); 47 | } 48 | 49 | return credentialArray; 50 | } 51 | else 52 | { 53 | throw new InvalidOperationException("Invalid CriticalHandle!"); 54 | } 55 | } 56 | 57 | // Perform any specific actions to release the handle in the ReleaseHandle method. 58 | // Often, you need to use Pinvoke to make a call into the Win32 API to release the 59 | // handle. In this case, however, we can use the Marshal class to release the unmanaged memory. 60 | 61 | override protected bool ReleaseHandle() 62 | { 63 | // If the handle was set, free it. Return success. 64 | if ( !IsInvalid ) 65 | { 66 | // NOTE: We should also ZERO out the memory allocated to the handle, before free'ing it 67 | // so there are no traces of the sensitive data left in memory. 68 | NativeCode.CredFree (handle); 69 | // Mark the handle as invalid for future users. 70 | SetHandleAsInvalid (); 71 | return true; 72 | } 73 | // Return false. 74 | return false; 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/ICredential.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Runtime.Serialization; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace AdysTech.CredentialManager 10 | { 11 | public interface ICredential 12 | { 13 | CredentialType Type { get; set; } 14 | string TargetName { get; set; } 15 | string Comment { get; set; } 16 | DateTime LastWritten { get; set; } 17 | string CredentialBlob { get; set; } 18 | Persistance Persistance { get; set; } 19 | IDictionary Attributes { get; set; } 20 | string UserName { get; set; } 21 | 22 | NetworkCredential ToNetworkCredential(); 23 | bool SaveCredential(bool AllowBlankPassword=false); 24 | 25 | bool RemoveCredential(); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/NativeCode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.InteropServices; 3 | using System.Text; 4 | 5 | namespace AdysTech.CredentialManager 6 | { 7 | internal static class NativeCode 8 | { 9 | [Flags] 10 | internal enum CredentialUIFlags 11 | { 12 | IncorrectPassword = 0x1, 13 | DoNotPersist = 0x2, 14 | RequestAdministrator = 0x4, 15 | ExcludeCertificates = 0x8, 16 | RequireCertificate = 0x10, 17 | ShowSaveCheckBox = 0x40, 18 | AlwaysShowUi = 0x80, 19 | RequireSmartcard = 0x100, 20 | PasswordOnlyOk = 0x200, 21 | ValidateUsername = 0x400, 22 | CompleteUsername = 0x800, 23 | Persist = 0x1000, 24 | ServerCredential = 0x4000, 25 | ExpectConfirmation = 0x20000, 26 | GenericCredentials = 0x40000, 27 | UsernameTargetCredentials = 0x80000, 28 | KeepUsername = 0x100000 29 | } 30 | 31 | internal enum CredentialUIReturnCodes : uint 32 | { 33 | Success = 0, 34 | Cancelled = 1223, 35 | NoSuchLogonSession = 1312, 36 | NotFound = 1168, 37 | InvalidAccountName = 1315, 38 | InsufficientBuffer = 122, 39 | InvalidParameter = 87, 40 | InvalidFlags = 1004 41 | } 42 | 43 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 44 | internal struct CredentialUIInfo 45 | { 46 | public int cbSize; 47 | public IntPtr hwndParent; 48 | public string pszMessageText; 49 | public string pszCaptionText; 50 | public IntPtr hbmBanner; 51 | } 52 | 53 | [DllImport("credui", CharSet = CharSet.Unicode)] 54 | internal static extern CredentialUIReturnCodes CredUIPromptForCredentials(ref CredentialUIInfo creditUR, 55 | string targetName, 56 | IntPtr reserved1, 57 | int iError, 58 | StringBuilder userName, 59 | int maxUserName, 60 | StringBuilder password, 61 | int maxPassword, 62 | [MarshalAs(UnmanagedType.Bool)] ref bool pfSave, 63 | CredentialUIFlags flags); 64 | 65 | [DllImport("credui.dll", EntryPoint = "CredUIParseUserNameW", CharSet = CharSet.Unicode, SetLastError = true)] 66 | internal static extern CredentialUIReturnCodes CredUIParseUserName( 67 | string userName, 68 | StringBuilder user, 69 | int userMaxChars, 70 | StringBuilder domain, 71 | int domainMaxChars); 72 | 73 | [DllImport("credui.dll", CharSet = CharSet.Unicode, SetLastError = true)] 74 | public static extern bool CredPackAuthenticationBuffer( 75 | Int32 dwFlags, 76 | StringBuilder pszUserName, 77 | StringBuilder pszPassword, 78 | IntPtr pPackedCredentials, 79 | ref Int32 pcbPackedCredentials 80 | ); 81 | 82 | [DllImport("credui.dll", CharSet = CharSet.Unicode)] 83 | internal static extern bool CredUnPackAuthenticationBuffer(int dwFlags, 84 | IntPtr pAuthBuffer, 85 | uint cbAuthBuffer, 86 | StringBuilder pszUserName, 87 | ref int pcchMaxUserName, 88 | StringBuilder pszDomainName, 89 | ref int pcchMaxDomainame, 90 | StringBuilder pszPassword, 91 | ref int pcchMaxPassword); 92 | 93 | [DllImport("credui.dll", EntryPoint = "CredUIPromptForWindowsCredentialsW", CharSet = CharSet.Unicode, SetLastError = true)] 94 | internal static extern int CredUIPromptForWindowsCredentials(ref CredentialUIInfo creditUR, 95 | int authError, 96 | ref uint authPackage, 97 | IntPtr inAuthBuffer, 98 | int inAuthBufferSize, 99 | out IntPtr refOutAuthBuffer, 100 | out uint refOutAuthBufferSize, 101 | ref bool fSave, 102 | PromptForWindowsCredentialsFlags flags); 103 | 104 | [DllImport("credui", CharSet = CharSet.Unicode)] 105 | internal static extern CredentialUIReturnCodes CredUICmdLinePromptForCredentials( 106 | string targetName, 107 | IntPtr reserved1, 108 | int iError, 109 | StringBuilder userName, 110 | int maxUserName, 111 | StringBuilder password, 112 | int maxPassword, 113 | [MarshalAs(UnmanagedType.Bool)] ref bool pfSave, 114 | CredentialUIFlags flags); 115 | 116 | 117 | [Flags] 118 | internal enum PromptForWindowsCredentialsFlags : uint 119 | { 120 | GenericCredentials = 0x1, 121 | ShowCheckbox = 0x2, 122 | AuthpackageOnly = 0x10, 123 | InCredOnly = 0x20, 124 | EnumerateAdmins = 0x100, 125 | EnumerateCurrentUser = 0x200, 126 | SecurePrompt = 0x1000, 127 | Pack32Wow = 0x10000000 128 | } 129 | 130 | 131 | 132 | 133 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 134 | internal struct NativeCredential 135 | { 136 | public UInt32 Flags; 137 | public UInt32 Type; 138 | [MarshalAs(UnmanagedType.LPWStr)] 139 | public string TargetName; 140 | [MarshalAs(UnmanagedType.LPWStr)] 141 | public string Comment; 142 | public System.Runtime.InteropServices.ComTypes.FILETIME LastWritten; 143 | public UInt32 CredentialBlobSize; 144 | public IntPtr CredentialBlob; 145 | public UInt32 Persist; 146 | public UInt32 AttributeCount; 147 | public IntPtr Attributes; 148 | [MarshalAs(UnmanagedType.LPWStr)] 149 | public string TargetAlias; 150 | [MarshalAs(UnmanagedType.LPWStr)] 151 | public string UserName; 152 | 153 | } 154 | 155 | [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] 156 | internal struct NativeCredentialAttribute 157 | { 158 | [MarshalAs(UnmanagedType.LPWStr)] 159 | public string Keyword; 160 | public UInt32 Flags; 161 | public UInt32 ValueSize; 162 | public IntPtr Value; 163 | } 164 | 165 | [DllImport("Advapi32.dll", EntryPoint = "CredDeleteW", CharSet = CharSet.Unicode, SetLastError = true)] 166 | internal static extern bool CredDelete([MarshalAs(UnmanagedType.LPWStr)] string target, uint type, int reservedFlag); 167 | 168 | [DllImport("Advapi32.dll", EntryPoint = "CredEnumerateW", CharSet = CharSet.Unicode, SetLastError = true)] 169 | internal static extern bool CredEnumerate([MarshalAs(UnmanagedType.LPWStr)] string target, UInt32 flags, out UInt32 count, out IntPtr credentialsPtr); 170 | 171 | [DllImport("Advapi32.dll", EntryPoint = "CredReadW", CharSet = CharSet.Unicode, SetLastError = true)] 172 | internal static extern bool CredRead([MarshalAs(UnmanagedType.LPWStr)]string target, uint type, int reservedFlag, out IntPtr CredentialPtr); 173 | 174 | [DllImport("Advapi32.dll", EntryPoint = "CredWriteW", CharSet = CharSet.Unicode, SetLastError = true)] 175 | internal static extern bool CredWrite([In] ref NativeCredential userCredential, [In] UInt32 flags); 176 | 177 | [DllImport("Advapi32.dll", EntryPoint = "CredFree", SetLastError = true)] 178 | internal static extern bool CredFree([In] IntPtr cred); 179 | 180 | [DllImport("ole32.dll", EntryPoint = "CoTaskMemFree", SetLastError = true)] 181 | internal static extern void CoTaskMemFree(IntPtr buffer); 182 | } 183 | } 184 | -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Runtime.CompilerServices; 2 | 3 | [assembly: InternalsVisibleTo("CredentialManager.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001000100220309e07b2bcbc4cc402fd9cb14db50bf946aa993cc7617a55be474f1364d4cc60bd09325621fafb12f46ba26cf5bd39b8d2aa58ff1c4ff096d827528209527afd63be3b9aa5cce2db016dd68b56f4839e06afa27b9fef6c018783e197ee77901ae401fe347e44231ee03abfb6595d6b12a19f21d24ae199763cea49fb0")] -------------------------------------------------------------------------------- /src/AdysTech.CredentialManager/PublicEnums.cs: -------------------------------------------------------------------------------- 1 | namespace AdysTech.CredentialManager 2 | { 3 | public enum CredentialType : uint 4 | { 5 | Generic = 1, 6 | Windows = 2, 7 | Certificate = 3 8 | } 9 | 10 | public enum Persistance : uint 11 | { 12 | Session = 1, 13 | LocalMachine = 2, 14 | Enterprise = 3 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/CredentialManager.Test/CredentialManager.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /tests/CredentialManager.Test/CredentialManagerTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.VisualStudio.TestTools.UnitTesting; 3 | using AdysTech.CredentialManager; 4 | using System.Net; 5 | using System.Diagnostics; 6 | using System.Collections.Generic; 7 | 8 | namespace CredentialManagerTest 9 | { 10 | [TestClass] 11 | public class CredentialManagerTest 12 | { 13 | private const string uName = "ZYYM3ufm3kFY9ZJZUAqYFQfzxcRc9rzdYxUwqEhBqqdrHttrh"; 14 | private const string pwd = "5NJuqKfJBtAZYYM3ufm3kFY9ZJZUAqYFQfzxcRc9rzdYxUwqEhBqqdrHttrhcvnnDPFHEn3L"; 15 | private const string domain = "AdysTech.com"; 16 | 17 | [Serializable] 18 | struct SampleAttribute 19 | { 20 | #pragma warning disable CA2235 // Mark all non-serializable fields 21 | public string role; 22 | public DateTime created; 23 | #pragma warning restore CA2235 // Mark all non-serializable fields 24 | } 25 | 26 | [TestMethod, TestCategory("AppVeyor")] 27 | public void TestSaveCredentials() 28 | { 29 | try 30 | { 31 | var cred = new NetworkCredential(uName, pwd, domain); 32 | Assert.IsNotNull(CredentialManager.SaveCredentials("TestSystem", cred), "SaveCredential failed"); 33 | } 34 | catch (Exception e) 35 | { 36 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 37 | e.GetType(), e.Message); 38 | return; 39 | } 40 | } 41 | 42 | [TestMethod, TestCategory("AppVeyor")] 43 | public void TestGetCredentials() 44 | { 45 | 46 | try 47 | { 48 | var cred = CredentialManager.GetCredentials("TestSystem"); 49 | Assert.IsNotNull(cred, "GetCredential failed"); 50 | Assert.IsTrue(uName == cred.UserName && pwd == cred.Password && domain == cred.Domain, "Saved and retrieved data doesn't match"); 51 | } 52 | catch (Exception e) 53 | { 54 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 55 | e.GetType(), e.Message); 56 | return; 57 | } 58 | } 59 | 60 | 61 | [TestMethod, TestCategory("AppVeyor")] 62 | public void TestICredential_Comment() 63 | { 64 | try 65 | { 66 | var cred = (new NetworkCredential(uName, pwd, domain)).ToICredential(); 67 | cred.TargetName = "TestSystem_comment"; 68 | cred.Comment = "This comment is only visible via API, not in Windows UI"; 69 | Assert.IsTrue(cred.SaveCredential(), "SaveCredential on ICredential failed"); 70 | 71 | var cred1 = CredentialManager.GetICredential(cred.TargetName); 72 | Assert.IsNotNull(cred, "GetICredential failed"); 73 | Assert.IsTrue(cred1.UserName == cred.UserName && cred1.CredentialBlob == cred.CredentialBlob && cred1.Comment == cred.Comment, "Saved and retrieved data doesn't match"); 74 | } 75 | catch (Exception e) 76 | { 77 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 78 | e.GetType(), e.Message); 79 | return; 80 | } 81 | } 82 | 83 | [TestMethod, TestCategory("AppVeyor")] 84 | public void TestICredential_Attributes() 85 | { 86 | 87 | try 88 | { 89 | var cred = (new NetworkCredential(uName, pwd, domain)).ToICredential(); 90 | cred.TargetName = "TestSystem_Attributes"; 91 | cred.Attributes = new Dictionary(); 92 | 93 | var sample = new SampleAttribute() { role = "regular", created = DateTime.UtcNow }; 94 | cred.Attributes.Add("sampleAttribute", sample); 95 | 96 | Assert.IsTrue(cred.SaveCredential(), "SaveCredential on ICredential failed"); 97 | var cred1 = CredentialManager.GetICredential(cred.TargetName); 98 | Assert.IsNotNull(cred, "GetICredential failed"); 99 | Assert.IsTrue(cred1.UserName == cred.UserName && cred1.CredentialBlob == cred.CredentialBlob && cred1.Attributes?.Count == cred.Attributes?.Count, "Saved and retrieved data doesn't match"); 100 | //Assert.IsTrue(cred.Attributes.All(a=>a.Value == cred1.Attributes[a.Key]), "Saved and retrieved data doesn't match"); 101 | Assert.IsTrue(((SampleAttribute)cred1.Attributes["sampleAttribute"]).role == sample.role, "Saved and retrieved data doesn't match"); 102 | } 103 | catch (Exception e) 104 | { 105 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 106 | e.GetType(), e.Message); 107 | return; 108 | } 109 | } 110 | 111 | [TestMethod, TestCategory("AppVeyor")] 112 | public void TestEnumerateCredentials() 113 | { 114 | try 115 | { 116 | var creds = CredentialManager.EnumerateCredentials(); 117 | Assert.IsNotNull(creds, "EnumerateCredentials failed"); 118 | Assert.IsTrue(creds?.Count > 0, "No credentials stored in the system"); 119 | } 120 | catch (Exception e) 121 | { 122 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 123 | e.GetType(), e.Message); 124 | return; 125 | } 126 | } 127 | 128 | [TestMethod, TestCategory("AppVeyor")] 129 | public void TestEnumerateICredentials() 130 | { 131 | try 132 | { 133 | var creds = CredentialManager.EnumerateICredentials(); 134 | Assert.IsNotNull(creds, "EnumerateICredentials failed"); 135 | Assert.IsTrue(creds?.Count > 0, "No credentials stored in the system"); 136 | } 137 | catch (Exception e) 138 | { 139 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 140 | e.GetType(), e.Message); 141 | return; 142 | } 143 | } 144 | 145 | /// 146 | /// This test assumes you have a Generic Credential for https://github.com stored on your system. 147 | /// 148 | public void TestEnumerateCredentialWithTarget() 149 | { 150 | try 151 | { 152 | var creds = CredentialManager.EnumerateCredentials(@"git:https://github.com"); 153 | Assert.IsNotNull(creds, "EnumerateCredentials failed"); 154 | Assert.IsTrue(creds?.Count > 0, "No credentials stored in the system"); 155 | } 156 | catch (Exception e) 157 | { 158 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 159 | e.GetType(), e.Message); 160 | return; 161 | } 162 | } 163 | 164 | [TestMethod] 165 | public void TestPromptForCredentials() 166 | { 167 | 168 | try 169 | { 170 | bool save = false; 171 | Assert.IsNotNull(CredentialManager.PromptForCredentials("Some Webservice", ref save, "Please provide credentials", "Credentials for service"), "PromptForCredentials failed"); 172 | 173 | } 174 | catch (Exception e) 175 | { 176 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 177 | e.GetType(), e.Message); 178 | return; 179 | } 180 | } 181 | 182 | /// 183 | /// Not working as Console window can't be seen during test 184 | /// 185 | //[TestMethod] 186 | // public void TestPromptForCredentialsConsole() 187 | // { 188 | 189 | // try 190 | // { 191 | // bool save = false; 192 | // Assert.IsNotNull (CredentialManager.PromptForCredentialsConsole ("Some Webservice"), "PromptForCredentialsConsole failed"); 193 | 194 | // } 195 | // catch ( Exception e ) 196 | // { 197 | // Assert.Fail ("Unexpected exception of type {0} caught: {1}", 198 | // e.GetType (), e.Message); 199 | // return; 200 | // } 201 | // } 202 | 203 | [TestMethod] 204 | public void IntegrationTest() 205 | { 206 | 207 | try 208 | { 209 | bool save = true; 210 | var cred = CredentialManager.PromptForCredentials("Some Webservice", ref save, "Please provide credentials", "Credentials for service"); 211 | Assert.IsNotNull(cred, "PromptForCredentials failed"); 212 | if (save) 213 | { 214 | var usr = cred.UserName; 215 | var pwd = cred.Password; 216 | var dmn = cred.Domain; 217 | Debug.WriteLine("Usr:{0}, Pwd{1}, Dmn{2}", usr, pwd, dmn); 218 | Assert.IsNotNull(CredentialManager.SaveCredentials("TestSystem", cred), "SaveCredential failed"); 219 | cred = CredentialManager.GetCredentials("TestSystem"); 220 | Assert.IsNotNull(cred, "GetCredential failed"); 221 | Assert.IsTrue(usr == cred.UserName && pwd == cred.Password && dmn == cred.Domain, "Saved and retrieved data doesn't match"); 222 | } 223 | 224 | } 225 | catch (Exception e) 226 | { 227 | Assert.Fail("Unexpected exception of type {0} caught: {1} on {2}", 228 | e.GetType(), e.Message, e.StackTrace); 229 | return; 230 | } 231 | } 232 | 233 | [TestMethod] 234 | public void IntegrationTest_with_prefilled_username() 235 | { 236 | try 237 | { 238 | bool save = true; 239 | var cred = CredentialManager.PromptForCredentials("Some Webservice", ref save, "Please provide credentials", "Credentials for service", "mike.flemming@domain.com"); 240 | Assert.IsNotNull(cred, "PromptForCredentials failed"); 241 | if (save) 242 | { 243 | var usr = cred.UserName; 244 | var pwd = cred.Password; 245 | var dmn = cred.Domain; 246 | Debug.WriteLine("Usr:{0}, Pwd{1}, Dmn{2}", usr, pwd, dmn); 247 | Assert.IsNotNull(CredentialManager.SaveCredentials("TestSystem1", cred), "SaveCredential failed"); 248 | cred = CredentialManager.GetCredentials("TestSystem1"); 249 | Assert.IsNotNull(cred, "GetCredential failed"); 250 | Assert.IsTrue(usr == cred.UserName && pwd == cred.Password && dmn == cred.Domain, "Saved and retrieved data doesn't match"); 251 | } 252 | 253 | } 254 | catch (Exception e) 255 | { 256 | Assert.Fail("Unexpected exception of type {0} caught: {1} on {2}", 257 | e.GetType(), e.Message, e.StackTrace); 258 | return; 259 | } 260 | } 261 | 262 | 263 | 264 | [TestMethod, TestCategory("AppVeyor")] 265 | public void TestSaveCredentials_Windows() 266 | { 267 | var cred = new NetworkCredential("admin", "P@$$w0rd"); 268 | var res = CredentialManager.SaveCredentials("TestWindowsCredential", cred, CredentialType.Windows); 269 | var cred1 = CredentialManager.GetCredentials("TestWindowsCredential", CredentialType.Windows); 270 | //https://msdn.microsoft.com/en-us/library/windows/desktop/aa374788(v=vs.85).aspx 271 | //CredentialType.Windows internally gets translated to CRED_TYPE_DOMAIN_PASSWORD 272 | //as per MSDN, for this type CredentialBlob can only be read by the authentication packages. 273 | //I am not able to get the password even while running in elevated mode. more to come. 274 | Assert.IsTrue(cred1 != null && cred1.UserName == cred.UserName, "Saved and retrieved data doesn't match"); 275 | } 276 | 277 | [TestMethod, TestCategory("AppVeyor")] 278 | public void TestGetCredentials_NullUserName() 279 | { 280 | var cred = new NetworkCredential(string.Empty, "P@$$w0rd"); 281 | var res = CredentialManager.SaveCredentials("TestCredWithoutUserName", cred); 282 | var cred1 = CredentialManager.GetCredentials("TestCredWithoutUserName"); 283 | Assert.IsTrue(cred1.UserName == cred.UserName && cred1.Password == cred.Password && cred1.Domain == cred.Domain, "Saved and retrieved data doesn't match"); 284 | } 285 | 286 | [TestMethod, TestCategory("AppVeyor")] 287 | public void TestGetCredentials_NonExistantCredential() 288 | { 289 | var cred = CredentialManager.GetCredentials("TotallyNonExistingTarget"); 290 | Assert.IsNull(cred); 291 | } 292 | 293 | [TestMethod, TestCategory("AppVeyor")] 294 | public void Test_ParseUserName_supports_long_name() 295 | { 296 | var longUserName = "ksdqkdbkbqskdbqskdqsdsqdqsdjsqdjqsdjlqsjd@domain.com"; 297 | string domain; 298 | string user; 299 | Assert.IsTrue(CredentialManager.ParseUserName(longUserName, 100, 100, out user, out domain)); 300 | 301 | Assert.AreEqual(longUserName, user); 302 | Assert.AreEqual("", domain); 303 | } 304 | 305 | [TestMethod, TestCategory("AppVeyor")] 306 | public void Test_ParseUserName_returns_false_if_buffer_is_too_small() 307 | { 308 | var longUserName = "ksdqkdbkbqskdbqskdqsdsqdqsdjsqdjqsdjlqsjd@domain.com"; 309 | string domain; 310 | string user; 311 | Assert.IsFalse(CredentialManager.ParseUserName(longUserName, 10, 100, out user, out domain)); 312 | Assert.AreEqual("", user); 313 | Assert.AreEqual("", domain); 314 | } 315 | 316 | [TestMethod, TestCategory("AppVeyor")] 317 | public void Test_ParseUserName_supports_domain_name() 318 | { 319 | string user; 320 | string domain; 321 | Assert.IsTrue(CredentialManager.ParseUserName("domain.com\\mike", 100, 100, out user, out domain)); 322 | 323 | Assert.AreEqual("mike", user); 324 | Assert.AreEqual("domain.com", domain); 325 | } 326 | 327 | [TestMethod, TestCategory("AppVeyor")] 328 | public void TestICredential_LongComment() 329 | { 330 | 331 | try 332 | { 333 | string test = "test"; 334 | var cred = (new NetworkCredential(test, test, test)).ToICredential(); 335 | cred.TargetName = "TestSystem_Attributes"; 336 | cred.Comment = new String('*', 257); 337 | Assert.ThrowsException(() => cred.SaveCredential(), "SaveCredential didn't throw ArgumentException for larger than 256 byte Comment"); 338 | } 339 | catch (Exception e) 340 | { 341 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 342 | e.GetType(), e.Message); 343 | return; 344 | } 345 | } 346 | 347 | [TestMethod, TestCategory("AppVeyor")] 348 | public void TestICredential_LongPassword() 349 | { 350 | try 351 | { 352 | int tooLong = 2 * Credential.MaxCredentialBlobSize; 353 | string test = "test"; 354 | var cred = (new NetworkCredential(test, new String('*', tooLong), test)).ToICredential(); 355 | cred.TargetName = "TestSystem_Attributes"; 356 | Assert.ThrowsException(() => cred.SaveCredential(), 357 | $"SaveCredential didn't throw ArgumentException for exceeding {Credential.MaxCredentialBlobSize} bytes."); 358 | } 359 | catch (Exception e) 360 | { 361 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 362 | e.GetType(), e.Message); 363 | return; 364 | } 365 | } 366 | 367 | [TestMethod, TestCategory("AppVeyor")] 368 | public void TestICredential_LongTokenShouldWork() 369 | { 370 | // Tokens can be rather large. 1040: a size that can be stored. 371 | const int tokenLength = 1040; 372 | Assert.IsTrue(tokenLength < Credential.MaxCredentialBlobSize, "This test is supposed to verify a valid length."); 373 | 374 | string test = "longPasswordTest"; 375 | var net = new NetworkCredential(test, new String('1', tokenLength), test); 376 | ICredential cred = net.ToICredential(); 377 | cred.TargetName = "TestSystem_LongPassword"; 378 | Assert.IsNotNull(cred.SaveCredential(), "SaveCredential should handle passwords of token size"); 379 | 380 | var cred1 = CredentialManager.GetCredentials("TestSystem_LongPassword"); 381 | Assert.IsTrue(cred1.Password == net.Password, "Saved and retrieved password doesn't match"); 382 | } 383 | 384 | [TestMethod, TestCategory("AppVeyor")] 385 | public void TestICredential_AttributesNullValue() 386 | { 387 | 388 | try 389 | { 390 | string test = "test"; 391 | var cred = (new NetworkCredential(test, test, test)).ToICredential(); 392 | cred.TargetName = "TestSystem_Attributes"; 393 | cred.Attributes = new Dictionary(); 394 | cred.Attributes.Add("sampleAttribute", null); 395 | 396 | Assert.ThrowsException(() => cred.SaveCredential(), "SaveCredential didn't throw ArgumentNullException for null valued Attribute"); 397 | } 398 | catch (Exception e) 399 | { 400 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 401 | e.GetType(), e.Message); 402 | return; 403 | } 404 | } 405 | 406 | #if !NET45 407 | [TestMethod, TestCategory("AppVeyor")] 408 | public void TestICredential_AttributesLargeValue() 409 | { 410 | 411 | try 412 | { 413 | string test = "test"; 414 | var cred = (new NetworkCredential(test, test, test)).ToICredential(); 415 | cred.TargetName = "TestSystem_Attributes"; 416 | cred.Attributes = new Dictionary(); 417 | cred.Attributes.Add("sampleAttribute", ValueTuple.Create("RegularUser", DateTime.UtcNow)); 418 | 419 | Assert.ThrowsException(() => cred.SaveCredential(), "SaveCredential didn't throw ArgumentException for larger than 256 byte Attribute"); 420 | } 421 | catch (Exception e) 422 | { 423 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 424 | e.GetType(), e.Message); 425 | return; 426 | } 427 | } 428 | #endif 429 | 430 | [TestMethod, TestCategory("AppVeyor")] 431 | public void TestDeleteCredentials_Windows() 432 | { 433 | var cred = new NetworkCredential("admin", "P@$$w0rd"); 434 | var saved = CredentialManager.SaveCredentials("TestDeletingWindowsCredential", cred, CredentialType.Windows); 435 | Assert.IsNotNull(saved, "SaveCredential on ICredential failed"); 436 | 437 | var cred1 = CredentialManager.GetICredential(saved.TargetName, CredentialType.Windows); 438 | Assert.IsNotNull(cred1, "GetICredential failed"); 439 | Assert.IsTrue(cred1.UserName == saved.UserName, "Saved and retrieved data doesn't match"); 440 | Assert.IsTrue(CredentialManager.RemoveCredentials(saved.TargetName, saved.Type), "RemoveCredentials returned false"); 441 | 442 | cred1 = CredentialManager.GetICredential(saved.TargetName); 443 | Assert.IsNull(cred1, "Deleted credential was read"); 444 | } 445 | 446 | [TestMethod, TestCategory("AppVeyor")] 447 | public void TestDeleteCredentials_Enumerated() 448 | { 449 | var credentials = CredentialManager.EnumerateICredentials(); 450 | 451 | if (credentials != null) 452 | { 453 | 454 | credentials.ForEach(x => { if (x.Type == CredentialType.Windows) Assert.IsTrue(x.RemoveCredential(),"RemoveCredentials returned false"); }); 455 | } 456 | } 457 | 458 | [TestMethod, TestCategory("AppVeyor")] 459 | public void TestGetCredentials_PasswordLengthOne() 460 | { 461 | var cred = new NetworkCredential("admin", "P"); 462 | var res = CredentialManager.SaveCredentials("TestCredWithPasswordSingleCharacter", cred); 463 | var cred1 = CredentialManager.GetCredentials("TestCredWithPasswordSingleCharacter"); 464 | Assert.IsTrue(cred1.Password == cred.Password, "Saved and retrieved password doesn't match"); 465 | } 466 | [TestMethod, TestCategory("AppVeyor")] 467 | 468 | public void TestSaveCredentials_EmptyPassword() 469 | { 470 | try 471 | { 472 | var cred = new NetworkCredential(uName, "", domain); 473 | Assert.IsNotNull(CredentialManager.SaveCredentials("TestSystem_nullPwd", cred,AllowNullPassword:true), "SaveCredential failed"); 474 | } 475 | catch (Exception e) 476 | { 477 | Assert.Fail("Unexpected exception of type {0} caught: {1}", 478 | e.GetType(), e.Message); 479 | return; 480 | } 481 | } 482 | } 483 | } 484 | --------------------------------------------------------------------------------