├── .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 | [](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 |
--------------------------------------------------------------------------------