├── .all-contributorsrc
├── .gitattributes
├── .gitignore
├── BuildAndPushPackage.bat
├── Changelog.md
├── Delete-BIN-OBJ-Folders.bat
├── Icon.png
├── License.txt
├── README.md
├── Updating.md
└── src
├── .editorconfig
├── Directory.Build.props
├── Windows.ico
├── WindowsHello.Tests
├── GlobalUsings.cs
├── TestWindowsHello.cs
├── Windows.ico
└── WindowsHello.Tests.csproj
├── WindowsHello.sln
├── WindowsHello.sln.DotSettings
└── WindowsHello
├── Exceptions
├── AuthProviderException.cs
├── AuthProviderInvalidKeyException.cs
├── AuthProviderIsUnavailableException.cs
├── AuthProviderSystemErrorException.cs
├── AuthProviderUserCancelledException.cs
├── EnvironmentErrorException.cs
└── WindowsHelloException.cs
├── GlobalUsings.cs
├── IAuthProvider.cs
├── WinHelloProvider.cs
├── Windows.ico
└── WindowsHello.csproj
/.all-contributorsrc:
--------------------------------------------------------------------------------
1 | {
2 | "files": [
3 | "README.md"
4 | ],
5 | "imageSize": 100,
6 | "commit": false,
7 | "commitType": "docs",
8 | "commitConvention": "angular",
9 | "contributors": [
10 | {
11 | "login": "SeppPenner",
12 | "name": "HansM",
13 | "avatar_url": "https://avatars.githubusercontent.com/u/9639361?v=4",
14 | "profile": "https://franzhuber23.blogspot.de/",
15 | "contributions": [
16 | "code",
17 | "doc",
18 | "example",
19 | "maintenance",
20 | "projectManagement",
21 | "test"
22 | ]
23 | },
24 | {
25 | "login": "danergo",
26 | "name": "danergo",
27 | "avatar_url": "https://avatars.githubusercontent.com/u/11708344?v=4",
28 | "profile": "https://github.com/danergo",
29 | "contributions": [
30 | "test"
31 | ]
32 | }
33 | ],
34 | "contributorsPerLine": 7,
35 | "skipCi": true,
36 | "repoType": "github",
37 | "repoHost": "https://github.com",
38 | "projectName": "WindowsHello",
39 | "projectOwner": "SeppPenner"
40 | }
41 |
--------------------------------------------------------------------------------
/.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 |
2 | #Ignore thumbnails created by Windows
3 | Thumbs.db
4 | #Ignore files built by Visual Studio
5 | *.obj
6 | *.exe
7 | *.pdb
8 | *.user
9 | *.aps
10 | *.pch
11 | *.vspscc
12 | *_i.c
13 | *_p.c
14 | *.ncb
15 | *.suo
16 | *.tlb
17 | *.tlh
18 | *.bak
19 | *.cache
20 | *.ilk
21 | *.log
22 | [Bb]in
23 | [Dd]ebug*/
24 | *.lib
25 | *.sbr
26 | obj/
27 | [Rr]elease*/
28 | _ReSharper*/
29 | [Tt]est[Rr]esult*
30 | .vs/
31 | #Nuget packages folder
32 | packages/
33 | private-packages/
34 | InstallFiles.wxs
35 | node_modules/
36 | package-lock.json
--------------------------------------------------------------------------------
/BuildAndPushPackage.bat:
--------------------------------------------------------------------------------
1 | cd .\src
2 |
3 | @ECHO off
4 | cls
5 |
6 | FOR /d /r . %%d in (bin,obj) DO (
7 | IF EXIST "%%d" (
8 | ECHO %%d | FIND /I "\node_modules\" > Nul && (
9 | ECHO.Skipping: %%d
10 | ) || (
11 | ECHO.Deleting: %%d
12 | rd /s/q "%%d"
13 | )
14 | )
15 | )
16 |
17 | @ECHO on
18 | @ECHO.Building solution...
19 | @dotnet restore
20 | @dotnet build -c Release
21 | @cd .\WindowsHello\bin\Release
22 | @ECHO.Build successful.
23 | dotnet nuget push *.nupkg -s "nuget.org" --skip-duplicate -k "%NUGET_API_KEY%"
24 | dotnet nuget push *.snupkg -s "nuget.org" --skip-duplicate -k "%NUGET_API_KEY%"
25 | dotnet nuget push *.nupkg -s "github" --skip-duplicate --api-key "%GITHUB_API_KEY%"
26 | dotnet nuget push *.snupkg -s "github" --skip-duplicate --api-key "%GITHUB_API_KEY%"
27 | @ECHO.Upload success. Press any key to exit.
28 | PAUSE
--------------------------------------------------------------------------------
/Changelog.md:
--------------------------------------------------------------------------------
1 | Change history
2 | --------------
3 |
4 | * **Version 1.1.4.0 (2024-11-24)** : Removed support for Net6.0, added support for Net9.0, updated NuGet packages.
5 | * **Version 1.1.3.0 (2024-05-16)** : Removed support for Net7.0.
6 | * **Version 1.1.2.0 (2023-12-07)** : Updated NuGet packages, added support for Net8, fixed AuthProviderSystemErrorException, fixes https://github.com/SeppPenner/WindowsHello/issues/6.
7 | * **Version 1.1.1.0 (2023-11-13)** : Updated NuGet packages.
8 | * **Version 1.1.0.0 (2022-11-20)** : Updated nuget packages, removed support for Net5.0, added support for Net7.0, removed tests for Net5.0.
9 | * **Version 1.0.12.0 (2022-02-18)** : Added nullable checks, added editorconfig, added file scoped namespaces, added global usings, removed native support for Net Framework (Breaking change).
10 | * **Version 1.0.11.0 (2021-11-11)** : Updated nuget packages, added .Net 6.0.
11 | * **Version 1.0.10.0 (2021-08-09)** : Updated nuget packages.
12 | * **Version 1.0.9.0 (2021-07-25)** : Updated nuget packages, enabled source linking for debugging.
13 | * **Version 1.0.8.0 (2021-02-21)** : Updated nuget packages.
14 | * **Version 1.0.7.0 (2021-01-01)** : Removed license year, moved changelog to extra file, adjusted folder structure, updated nuget packages, moved to Net 5.0.
15 | * **Version 1.0.6.0 (2020-06-05)** : Updated nuget packages, adjusted build to Visual Studio, moved changelog to extra file.
16 | * **Version 1.0.5.0 (2020-05-10)** : Updated nuget packages.
17 | * **Version 1.0.4.0 (2019-11-18)** : Fixed security bug (Thanks to [@Angelelz](https://github.com/Angelelz)).
18 | * **Version 1.0.3.0 (2019-11-08)** : Updated nuget packages.
19 | * **Version 1.0.2.0 (2019-06-23)** : Added icon to the nuget package.
20 | * **Version 1.0.0.1 (2019-05-05)** : Updated .Net version to 4.8.
21 | * **Version 1.0.0.0 (2019-02-09)** : 1.0 release.
--------------------------------------------------------------------------------
/Delete-BIN-OBJ-Folders.bat:
--------------------------------------------------------------------------------
1 | @ECHO off
2 | cls
3 |
4 | ECHO Deleting all BIN and OBJ folders...
5 | ECHO.
6 |
7 | FOR /d /r . %%d in (bin,obj) DO (
8 | IF EXIST "%%d" (
9 | ECHO %%d | FIND /I "\node_modules\" > Nul && (
10 | ECHO.Skipping: %%d
11 | ) || (
12 | ECHO.Deleting: %%d
13 | rd /s/q "%%d"
14 | )
15 | )
16 | )
17 |
18 | ECHO.
19 | ECHO.BIN and OBJ folders have been successfully deleted. Press any key to exit.
20 | pause > nul
--------------------------------------------------------------------------------
/Icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeppPenner/WindowsHello/2e616704c95a6140b9a9f59b8aab9862f6b4295c/Icon.png
--------------------------------------------------------------------------------
/License.txt:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) SeppPenner (https://github.com/SeppPenner)
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.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | WindowsHello
2 | ====================================
3 |
4 | WindowsHello is an assembly/ library to work with [Microsoft's Windows Hello](https://support.microsoft.com/de-de/help/17215/windows-10-what-is-hello) in aplications.
5 |
6 | [](https://ci.appveyor.com/project/SeppPenner/windowshello)
7 | [](https://github.com/SeppPenner/WindowsHello/issues)
8 | [](https://github.com/SeppPenner/WindowsHello/network)
9 | [](https://github.com/SeppPenner/WindowsHello/stargazers)
10 | [](https://raw.githubusercontent.com/SeppPenner/WindowsHello/master/License.txt)
11 | [](https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/)
12 | [](https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/)
13 | [](https://snyk.io/test/github/SeppPenner/WindowsHello)
14 | [](https://matrix.to/#/#WindowsHello2_community:gitter.im)
15 | [](https://franzhuber23.blogspot.de/)
16 | [](https://patreon.com/SeppPennerOpenSourceDevelopment)
17 | [](https://paypal.me/th070795)
18 |
19 |
20 | [](#contributors-)
21 |
22 |
23 | ## Available for
24 | * Net 8.0
25 | * Net 9.0
26 |
27 | ## Net Framework latest and LTS versions
28 | * https://dotnet.microsoft.com/download/dotnet
29 |
30 | ## Basic usage (Version 1.0.4.0 and above)
31 | ```csharp
32 | public void WindowsHelloTest()
33 | {
34 | var handle = new IntPtr();
35 | var data = new byte[] { 0x32, 0x32 };
36 | var provider = WinHelloProvider.CreateInstance("Hello", handle);
37 | // Set the persistent key name if you want:
38 | provider.SetPersistentKeyName("Test");
39 | var encryptedData = provider.Encrypt(data);
40 | var decryptedData = provider.PromptToDecrypt(encryptedData);
41 | }
42 | ```
43 |
44 | ## Basic usage (Before version 1.0.4.0)
45 | ```csharp
46 | public void WindowsHelloTest()
47 | {
48 | var handle = new IntPtr();
49 | var data = new byte[] { 0x32, 0x32 };
50 | IAuthProvider provider = new WinHelloProvider("Hello", handle);
51 | var encryptedData = provider.Encrypt(data);
52 | var decryptedData = provider.PromptToDecrypt(encryptedData);
53 | }
54 | ```
55 |
56 | The project can be found on [nuget](https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/).
57 |
58 | ## Install
59 |
60 | ```bash
61 | dotnet add package HaemmerElectronics.SeppPenner.WindowsHello
62 | ```
63 |
64 | ## Further links
65 | This project is mainly taken from https://github.com/sirAndros/KeePassWinHello.
66 |
67 | Change history
68 | --------------
69 |
70 | See the [Changelog](https://github.com/SeppPenner/WindowsHello/blob/master/Changelog.md).
71 |
72 | ## Contributors ✨
73 |
74 | Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/docs/en/emoji-key)):
75 |
76 |
77 |
78 |
79 |
87 |
88 |
89 |
90 |
91 |
92 |
93 | This project follows the [all-contributors](https://github.com/all-contributors/all-contributors) specification. Contributions of any kind welcome!
--------------------------------------------------------------------------------
/Updating.md:
--------------------------------------------------------------------------------
1 | ## How to update the package
2 |
3 | 1. Add your changes.
4 | 2. Update the release notes in the *.csproj file.
5 | 3. Update version using a Git tag.
6 | 4. Build the project with Visual Studio.
7 | 5. Upload nuget package.
--------------------------------------------------------------------------------
/src/.editorconfig:
--------------------------------------------------------------------------------
1 | root = true
2 |
3 | [*]
4 | end_of_line = crlf
5 | indent_style = space
6 | indent_size = 4
7 | charset = utf-8
8 | csharp_using_directive_placement=inside_namespace:warning
9 | csharp_prefer_braces=true:warning
10 | dotnet_style_allow_multiple_blank_lines_experimental=false:warning
11 | dotnet_style_allow_statement_immediately_after_block_experimental=false:warning
12 | dotnet_style_qualification_for_field=true:warning
13 | dotnet_style_qualification_for_property=true:warning
14 | dotnet_style_qualification_for_method=true:warning
15 | dotnet_style_qualification_for_event=true:warning
16 | csharp_style_allow_blank_lines_between_consecutive_braces_experimental=false:warning
17 | dotnet_sort_system_directives_first=true:warning
18 | dotnet_diagnostic.IDE0005.severity=warning
19 |
20 | [*.cs]
21 | csharp_style_namespace_declarations = file_scoped:warning
--------------------------------------------------------------------------------
/src/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 | true
4 |
5 |
--------------------------------------------------------------------------------
/src/Windows.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeppPenner/WindowsHello/2e616704c95a6140b9a9f59b8aab9862f6b4295c/src/Windows.ico
--------------------------------------------------------------------------------
/src/WindowsHello.Tests/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert.
2 | global using Microsoft.VisualStudio.TestTools.UnitTesting;
3 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert.
--------------------------------------------------------------------------------
/src/WindowsHello.Tests/TestWindowsHello.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // This class is used to test the Microsoft Windows Hello
7 | // (https://support.microsoft.com/de-de/help/17215/windows-10-what-is-hello) functionality.
8 | //
9 | // --------------------------------------------------------------------------------------------------------------------
10 |
11 | namespace WindowsHello.Tests;
12 |
13 | ///
14 | /// This class is used to test the Microsoft Windows Hello
15 | /// (https://support.microsoft.com/de-de/help/17215/windows-10-what-is-hello) functionality.
16 | ///
17 | [TestClass]
18 | public class TestWindowsHello
19 | {
20 | ///
21 | /// Tests the Microsoft Windows Hello functionality.
22 | ///
23 | [TestMethod]
24 | public void WindowsHelloTest()
25 | {
26 | var handle = new IntPtr();
27 | const string Message = "Windows Hello Test";
28 | var data = new byte[] { 0x32, 0x32 };
29 | Console.WriteLine(BitConverter.ToString(data));
30 | var provider = WinHelloProvider.CreateInstance(Message, handle);
31 | Console.WriteLine("Instance created.");
32 | var encryptedData = provider.Encrypt(data);
33 | Console.WriteLine("Encrypted data:");
34 | Console.WriteLine(BitConverter.ToString(encryptedData));
35 | var decryptedData = provider.PromptToDecrypt(encryptedData);
36 | Console.WriteLine("Decrypted data:");
37 | Console.WriteLine(BitConverter.ToString(decryptedData));
38 | CollectionAssert.AreEqual(data, decryptedData);
39 | }
40 |
41 | ///
42 | /// Tests the Microsoft Windows Hello functionality.
43 | ///
44 | [TestMethod]
45 | public void WindowsHelloTest2()
46 | {
47 | var handle = new IntPtr();
48 | const string Message = "Windows Hello Test2";
49 | var data = new byte[] { 0x32, 0x32 };
50 | Console.WriteLine(BitConverter.ToString(data));
51 | var provider = WinHelloProvider.CreateInstance(Message, handle);
52 | Console.WriteLine("Instance created.");
53 | provider.SetPersistentKeyName("Test");
54 | Console.WriteLine("PersistentKeyName set to \"Test\".");
55 | var encryptedData = provider.Encrypt(data);
56 | Console.WriteLine("Encrypted data:");
57 | Console.WriteLine(BitConverter.ToString(encryptedData));
58 | var decryptedData = provider.PromptToDecrypt(encryptedData);
59 | Console.WriteLine("Decrypted data:");
60 | Console.WriteLine(BitConverter.ToString(decryptedData));
61 | CollectionAssert.AreEqual(data, decryptedData);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/WindowsHello.Tests/Windows.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeppPenner/WindowsHello/2e616704c95a6140b9a9f59b8aab9862f6b4295c/src/WindowsHello.Tests/Windows.ico
--------------------------------------------------------------------------------
/src/WindowsHello.Tests/WindowsHello.Tests.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net9.0-windows
5 | false
6 | WindowsHello.Tests
7 | Windows.ico
8 | Library
9 | latest
10 | enable
11 |
12 | enable
13 | NU1803
14 | true
15 |
16 |
17 |
18 | full
19 | true
20 |
21 |
22 |
23 |
24 | all
25 | runtime; build; native; contentfiles; analyzers; buildtransitive
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
--------------------------------------------------------------------------------
/src/WindowsHello.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 17
4 | VisualStudioVersion = 17.4.33103.184
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsHello.Tests", "WindowsHello.Tests\WindowsHello.Tests.csproj", "{1CDA9CEF-8B5D-442B-8BC4-4D25F9E2826C}"
7 | EndProject
8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WindowsHello", "WindowsHello\WindowsHello.csproj", "{23B8E703-7DF1-4BD1-B49D-BE06D76C6681}"
9 | EndProject
10 | Global
11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
12 | Debug|Any CPU = Debug|Any CPU
13 | Release|Any CPU = Release|Any CPU
14 | EndGlobalSection
15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
16 | {1CDA9CEF-8B5D-442B-8BC4-4D25F9E2826C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
17 | {1CDA9CEF-8B5D-442B-8BC4-4D25F9E2826C}.Debug|Any CPU.Build.0 = Debug|Any CPU
18 | {1CDA9CEF-8B5D-442B-8BC4-4D25F9E2826C}.Release|Any CPU.ActiveCfg = Release|Any CPU
19 | {1CDA9CEF-8B5D-442B-8BC4-4D25F9E2826C}.Release|Any CPU.Build.0 = Release|Any CPU
20 | {23B8E703-7DF1-4BD1-B49D-BE06D76C6681}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
21 | {23B8E703-7DF1-4BD1-B49D-BE06D76C6681}.Debug|Any CPU.Build.0 = Debug|Any CPU
22 | {23B8E703-7DF1-4BD1-B49D-BE06D76C6681}.Release|Any CPU.ActiveCfg = Release|Any CPU
23 | {23B8E703-7DF1-4BD1-B49D-BE06D76C6681}.Release|Any CPU.Build.0 = Release|Any CPU
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | GlobalSection(ExtensibilityGlobals) = postSolution
29 | SolutionGuid = {DB66062A-3D8C-4E69-BE1D-0E7876359A30}
30 | EndGlobalSection
31 | EndGlobal
32 |
--------------------------------------------------------------------------------
/src/WindowsHello.sln.DotSettings:
--------------------------------------------------------------------------------
1 |
2 | True
3 | True
4 | True
5 | True
6 |
7 |
8 |
9 |
10 |
11 |
12 | True
13 | True
14 | True
15 | True
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/AuthProviderException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The authentication provider exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The authentication provider exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class AuthProviderException : WindowsHelloException
18 | {
19 | /// Initializes a new instance of the class.
20 | public AuthProviderException()
21 | {
22 | }
23 |
24 | /// Initializes a new instance of the class.
25 | /// The message describing the error.
26 | public AuthProviderException(string message) : base(message)
27 | {
28 | }
29 |
30 | /// Initializes a new instance of the class.
31 | /// The message describing the error.
32 | /// The inner exception causing this exception.
33 | public AuthProviderException(string message, Exception inner) : base(message, inner)
34 | {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/AuthProviderInvalidKeyException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The authentication provider invalid key exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The authentication provider invalid key exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class AuthProviderInvalidKeyException : AuthProviderException
18 | {
19 | /// Initializes a new instance of the class.
20 | /// The message describing the error.
21 | public AuthProviderInvalidKeyException(string message) : base(message)
22 | {
23 | }
24 |
25 | ///
26 | /// Initializes a new instance of the class.
27 | ///
28 | /// The message describing the error.
29 | /// The inner exception causing this exception.
30 | public AuthProviderInvalidKeyException(string message, Exception inner) : base(message, inner)
31 | {
32 | }
33 |
34 | /// Initializes a new instance of the class.
35 | protected AuthProviderInvalidKeyException()
36 | {
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/AuthProviderIsUnavailableException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The authentication provider unavailable exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The authentication provider unavailable exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class AuthProviderIsUnavailableException : AuthProviderException
18 | {
19 | ///
20 | /// Initializes a new instance of the class.
21 | ///
22 | public AuthProviderIsUnavailableException() : this("Authentication provider is not available.")
23 | {
24 | }
25 |
26 | ///
27 | /// Initializes a new instance of the class.
28 | ///
29 | /// The message describing the error.
30 | public AuthProviderIsUnavailableException(string message) : base(message)
31 | {
32 | }
33 |
34 | ///
35 | /// Initializes a new instance of the class.
36 | ///
37 | /// The message describing the error.
38 | /// The inner exception causing this exception.
39 | public AuthProviderIsUnavailableException(string message, Exception inner) : base(message, inner)
40 | {
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/AuthProviderSystemErrorException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The authentication provider system error exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The authentication provider system error exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class AuthProviderSystemErrorException : EnvironmentErrorException
18 | {
19 | /// Initializes a new instance of the class.
20 | public AuthProviderSystemErrorException()
21 | {
22 | }
23 |
24 | /// Initializes a new instance of the class.
25 | /// The message describing the error.
26 | /// The error code.
27 | public AuthProviderSystemErrorException(string message, int errorCode) : base(message, errorCode)
28 | {
29 | }
30 |
31 | /// Initializes a new instance of the class.
32 | /// The message describing the error.
33 | public AuthProviderSystemErrorException(string message) : base(message)
34 | {
35 | }
36 |
37 | /// Initializes a new instance of the class.
38 | /// The message describing the error.
39 | /// The inner exception causing this exception.
40 | public AuthProviderSystemErrorException(string message, Exception inner) : base(message, inner)
41 | {
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/AuthProviderUserCancelledException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The authentication provider system error exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The authentication provider system error exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class AuthProviderUserCancelledException : AuthProviderException
18 | {
19 | /// Initializes a new instance of the class.
20 | public AuthProviderUserCancelledException() : this("Operation was canceled by user.")
21 | {
22 | }
23 |
24 | /// Initializes a new instance of the class.
25 | /// The message describing the error.
26 | public AuthProviderUserCancelledException(string message) : base(message)
27 | {
28 | }
29 |
30 | /// Initializes a new instance of the class.
31 | /// The message describing the error.
32 | /// The inner exception causing this exception.
33 | public AuthProviderUserCancelledException(string message, Exception inner) : base(message, inner)
34 | {
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/EnvironmentErrorException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The environment error exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The environment error exception.
14 | ///
15 | ///
16 | [Serializable]
17 | public class EnvironmentErrorException : WindowsHelloException
18 | {
19 | /// Initializes a new instance of the class.
20 | public EnvironmentErrorException()
21 | {
22 | }
23 |
24 | /// Initializes a new instance of the class.
25 | /// The message describing the error.
26 | public EnvironmentErrorException(string message) : base(message)
27 | {
28 | }
29 |
30 | /// Initializes a new instance of the class.
31 | /// The message describing the error.
32 | /// The inner exception causing this exception.
33 | public EnvironmentErrorException(string message, Exception inner) : base(message, inner)
34 | {
35 | }
36 |
37 | /// Initializes a new instance of the class.
38 | /// The message describing the error.
39 | /// The error code.
40 | public EnvironmentErrorException(string message, int errorCode) : this(
41 | message + "\nError code: " + errorCode.ToString("X"))
42 | {
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/WindowsHello/Exceptions/WindowsHelloException.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // The windows hello exception.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello.Exceptions;
11 |
12 | ///
13 | /// The windows hello exception.
14 | ///
15 | [Serializable]
16 | public class WindowsHelloException : Exception
17 | {
18 | /// Initializes a new instance of the class.
19 | public WindowsHelloException()
20 | {
21 | }
22 |
23 | /// Initializes a new instance of the class.
24 | /// The message describing the error.
25 | public WindowsHelloException(string message) : base(message)
26 | {
27 | }
28 |
29 | /// Initializes a new instance of the class.
30 | /// The message describing the error.
31 | /// The inner exception causing this exception.
32 | public WindowsHelloException(string message, Exception inner) : base(message, inner)
33 | {
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/WindowsHello/GlobalUsings.cs:
--------------------------------------------------------------------------------
1 | #pragma warning disable IDE0065 // Die using-Anweisung wurde falsch platziert.
2 | global using System.Diagnostics;
3 | global using System.Runtime.InteropServices;
4 | global using System.Runtime.Serialization;
5 | global using System.Security.Cryptography;
6 | global using System.Security.Principal;
7 |
8 | global using Microsoft.Win32.SafeHandles;
9 |
10 | global using WindowsHello.Exceptions;
11 | #pragma warning restore IDE0065 // Die using-Anweisung wurde falsch platziert.
--------------------------------------------------------------------------------
/src/WindowsHello/IAuthProvider.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // This interface is used to abstract different authentication providers.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello;
11 |
12 | ///
13 | /// This interface is used to abstract different authentication providers.
14 | ///
15 | public interface IAuthProvider
16 | {
17 | ///
18 | /// Prompts to decrypt the data.
19 | ///
20 | /// The encrypted data.
21 | /// The decrypted data as .
22 | byte[] PromptToDecrypt(byte[] data);
23 |
24 | ///
25 | /// Encrypts the specified data.
26 | ///
27 | /// The decrypted data.
28 | /// The encrypted data as .
29 | byte[] Encrypt(byte[] data);
30 |
31 | ///
32 | /// Sets the persistent key name.
33 | ///
34 | /// The persistent name to use.
35 | void SetPersistentKeyName(string persistentName);
36 | }
37 |
--------------------------------------------------------------------------------
/src/WindowsHello/WinHelloProvider.cs:
--------------------------------------------------------------------------------
1 | // --------------------------------------------------------------------------------------------------------------------
2 | //
3 | // Copyright (c) All rights reserved.
4 | //
5 | //
6 | // Defines the WinHelloProvider type.
7 | //
8 | // --------------------------------------------------------------------------------------------------------------------
9 |
10 | namespace WindowsHello;
11 |
12 | ///
13 | ///
14 | /// This class provides access to the Microsoft Windows Hello
15 | /// (https://support.microsoft.com/de-de/help/17215/windows-10-what-is-hello) functionality.
16 | ///
17 | public class WinHelloProvider : IAuthProvider
18 | {
19 | ///
20 | /// The BCrypt RSA algorithm.
21 | ///
22 | private const string BCRYPT_RSA_ALGORITHM = "RSA";
23 |
24 | ///
25 | /// The domain.
26 | ///
27 | private const string Domain = "WindowsHello";
28 |
29 | ///
30 | /// The invalid key message.
31 | ///
32 | private const string InvalidatedKeyMessage =
33 | "Persistent key has not met integrity requirements. It might be caused by a spoofing attack. Try to recreate the key.";
34 |
35 | ///
36 | /// The Microsoft passport key storage provider.
37 | ///
38 | private const string MS_NGC_KEY_STORAGE_PROVIDER = "Microsoft Passport Key Storage Provider";
39 |
40 | ///
41 | /// The NCrypt allow decrypt flag.
42 | ///
43 | private const int NCRYPT_ALLOW_DECRYPT_FLAG = 0x00000001;
44 |
45 | ///
46 | /// The NCrypt allow key import flag.
47 | ///
48 | private const int NCRYPT_ALLOW_KEY_IMPORT_FLAG = 0x00000008;
49 |
50 | ///
51 | /// The NCrypt allow signing flag.
52 | ///
53 | private const int NCRYPT_ALLOW_SIGNING_FLAG = 0x00000002;
54 |
55 | ///
56 | /// The NCrypt key usage.
57 | ///
58 | private const string NCRYPT_KEY_USAGE_PROPERTY = "Key Usage";
59 |
60 | ///
61 | /// The NCrypt length.
62 | ///
63 | private const string NCRYPT_LENGTH_PROPERTY = "Length";
64 |
65 | ///
66 | /// The NCrypt NGC cache type.
67 | ///
68 | private const string NCRYPT_NGC_CACHE_TYPE_PROPERTY = "NgcCacheType";
69 |
70 | ///
71 | /// The NCrypt NGC cache type property auth mandatory flag.
72 | ///
73 | private const int NCRYPT_NGC_CACHE_TYPE_PROPERTY_AUTH_MANDATORY_FLAG = 0x00000001;
74 |
75 | ///
76 | /// The NCrypt NGC cache type (deprecated).
77 | ///
78 | private const string NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED = "NgcCacheTypeProperty";
79 |
80 | ///
81 | /// The NCrypt pad PKCS1 flag.
82 | ///
83 | private const int NCRYPT_PAD_PKCS1_FLAG = 0x00000002;
84 |
85 | ///
86 | /// The NCrypt pin cache is gesture required value.
87 | ///
88 | private const string NCRYPT_PIN_CACHE_IS_GESTURE_REQUIRED_PROPERTY = "PinCacheIsGestureRequired";
89 |
90 | ///
91 | /// The NCrypt use context.
92 | ///
93 | private const string NCRYPT_USE_CONTEXT_PROPERTY = "Use Context";
94 |
95 | ///
96 | /// The NCrypt window handle.
97 | ///
98 | private const string NCRYPT_WINDOW_HANDLE_PROPERTY = "HWND Handle";
99 |
100 | ///
101 | /// The NTE no key.
102 | ///
103 | private const int NTE_NO_KEY = unchecked((int)0x8009000D);
104 |
105 | ///
106 | /// The NTE user cancelled.
107 | ///
108 | private const int NTE_USER_CANCELLED = unchecked((int)0x80090036);
109 |
110 | ///
111 | /// The persistent name.
112 | ///
113 | private const string PersistentName = "WindowsHello";
114 |
115 | ///
116 | /// The sub domain.
117 | ///
118 | private const string SubDomain = "";
119 |
120 | ///
121 | /// The local key name.
122 | ///
123 | private static readonly Lazy LocalKeyName = new (RetrieveLocalKeyName);
124 |
125 | ///
126 | /// The mutex.
127 | ///
128 | private static readonly object Mutex = new();
129 |
130 | ///
131 | /// The persistent key name.
132 | ///
133 | private static Lazy persistentKeyName = new(RetrievePersistentKeyName);
134 |
135 | ///
136 | /// The current key name.
137 | ///
138 | private static string currentKeyName = string.Empty;
139 |
140 | ///
141 | /// The Windows hello provider instance.
142 | ///
143 | private static WinHelloProvider? instance;
144 |
145 | ///
146 | /// The authentication provider UI context.
147 | ///
148 | private static AuthProviderUiContext? uiContext;
149 |
150 | ///
151 | /// Initializes a new instance of the class.
152 | ///
153 | /// The authentication provider UI context.
154 | private WinHelloProvider(AuthProviderUiContext uIContext)
155 | {
156 | if (!TryOpenPersistentKey(out var ngcKeyHandle))
157 | {
158 | throw new AuthProviderInvalidKeyException("Persistent key does not exist.");
159 | }
160 |
161 | using (ngcKeyHandle)
162 | {
163 | if (!VerifyPersistentKeyIntegrity(ngcKeyHandle))
164 | {
165 | ngcKeyHandle.Close();
166 | DeletePersistentKey();
167 | throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage);
168 | }
169 | }
170 |
171 | uiContext = uIContext;
172 | currentKeyName = persistentKeyName.Value;
173 | }
174 |
175 | ///
176 | /// Creates a new instance of the class.
177 | ///
178 | /// The message.
179 | /// The window handle.
180 | /// A new instance of the class.
181 | public static WinHelloProvider CreateInstance(string message, IntPtr windowHandle)
182 | {
183 | if (!IsAvailable())
184 | {
185 | throw new AuthProviderIsUnavailableException("Windows Hello is not available.");
186 | }
187 |
188 | lock (Mutex)
189 | {
190 | if (!TryOpenPersistentKey(out var ngcKeyHandle))
191 | {
192 | CreatePersistentKey(true, AuthProviderUiContext.With(message, windowHandle)).Dispose();
193 | }
194 |
195 | ngcKeyHandle.Dispose();
196 | var winHelloProvider = new WinHelloProvider(AuthProviderUiContext.With(message, windowHandle));
197 | return instance ??= winHelloProvider;
198 | }
199 | }
200 |
201 | ///
202 | ///
203 | /// Encrypts the specified data.
204 | ///
205 | /// The decrypted data.
206 | ///
207 | /// The encrypted data as .
208 | ///
209 | /// Windows Hello is not available
210 | public byte[] Encrypt(byte[] data)
211 | {
212 | byte[] cbResult;
213 | NCryptOpenStorageProvider(out var ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0)
214 | .CheckStatus("NCryptOpenStorageProvider");
215 |
216 | using (ngcProviderHandle)
217 | {
218 | NCryptOpenKey(ngcProviderHandle, out var ngcKeyHandle, currentKeyName, 0, CngKeyOpenOptions.Silent)
219 | .CheckStatus("NCryptOpenKey");
220 |
221 | using (ngcKeyHandle)
222 | {
223 | if (!VerifyPersistentKeyIntegrity(ngcKeyHandle))
224 | {
225 | throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage);
226 | }
227 |
228 | NCryptEncrypt(
229 | ngcKeyHandle,
230 | data,
231 | data.Length,
232 | IntPtr.Zero,
233 | null,
234 | 0,
235 | out var pcbResult,
236 | NCRYPT_PAD_PKCS1_FLAG).CheckStatus("NCryptEncrypt");
237 |
238 | cbResult = new byte[pcbResult];
239 | NCryptEncrypt(
240 | ngcKeyHandle,
241 | data,
242 | data.Length,
243 | IntPtr.Zero,
244 | cbResult,
245 | cbResult.Length,
246 | out pcbResult,
247 | NCRYPT_PAD_PKCS1_FLAG).CheckStatus("NCryptEncrypt");
248 |
249 | Debug.Assert(cbResult.Length == pcbResult, "cbResult.Length == pcbResult");
250 | }
251 | }
252 |
253 | return cbResult;
254 | }
255 |
256 | ///
257 | ///
258 | /// Prompts to decrypt the data.
259 | ///
260 | /// The encrypted data.
261 | ///
262 | /// The decrypted data as .
263 | ///
264 | /// Windows Hello is not available
265 | public byte[] PromptToDecrypt(byte[] data)
266 | {
267 | if (uiContext is null)
268 | {
269 | throw new ArgumentNullException(nameof(uiContext), "The UI context wasn't set properly.");
270 | }
271 |
272 | byte[] cbResult;
273 | NCryptOpenStorageProvider(out var ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0)
274 | .CheckStatus("NCryptOpenStorageProvider");
275 |
276 | using (ngcProviderHandle)
277 | {
278 | NCryptOpenKey(ngcProviderHandle, out var ngcKeyHandle, currentKeyName, 0, CngKeyOpenOptions.None)
279 | .CheckStatus("NCryptOpenKey");
280 |
281 | using (ngcKeyHandle)
282 | {
283 | if (!VerifyPersistentKeyIntegrity(ngcKeyHandle))
284 | {
285 | throw new AuthProviderInvalidKeyException(InvalidatedKeyMessage);
286 | }
287 |
288 | ApplyUiContext(ngcKeyHandle, uiContext);
289 |
290 | var pinRequired = BitConverter.GetBytes(1);
291 | NCryptSetProperty(
292 | ngcKeyHandle,
293 | NCRYPT_PIN_CACHE_IS_GESTURE_REQUIRED_PROPERTY,
294 | pinRequired,
295 | pinRequired.Length,
296 | CngPropertyOptions.None).CheckStatus("NCRYPT_PIN_CACHE_IS_GESTURE_REQUIRED_PROPERTY");
297 |
298 | // The pbInput and pbOutput parameters can point to the same buffer. In this case, this function will perform the decryption in place.
299 | cbResult = new byte[data.Length * 2];
300 | NCryptDecrypt(
301 | ngcKeyHandle,
302 | data,
303 | data.Length,
304 | IntPtr.Zero,
305 | cbResult,
306 | cbResult.Length,
307 | out var pcbResult,
308 | NCRYPT_PAD_PKCS1_FLAG).CheckStatus("NCryptDecrypt");
309 |
310 | // TODO: secure resize
311 | Array.Resize(ref cbResult, pcbResult);
312 | }
313 | }
314 |
315 | return cbResult;
316 | }
317 |
318 | ///
319 | /// Sets the persistent key name.
320 | ///
321 | /// The persistent name to use.
322 | public void SetPersistentKeyName(string persistentName)
323 | {
324 | var sid = WindowsIdentity.GetCurrent().User?.Value;
325 | var value = sid + "//" + Domain + "/" + SubDomain + "/"
326 | + persistentName.Replace("/", string.Empty).Replace("//", string.Empty);
327 | persistentKeyName = new Lazy(() => value);
328 | }
329 |
330 | ///
331 | /// Applies the UI context.
332 | ///
333 | /// The safe NCrypt key handle.
334 | /// The authentication provider UI context.
335 | private static void ApplyUiContext(SafeNCryptHandle ngcKeyHandle, AuthProviderUiContext uiContextParam)
336 | {
337 | if (uiContextParam == null)
338 | {
339 | return;
340 | }
341 |
342 | var parentWindowHandle = uiContextParam.ParentWindowHandle;
343 | if (parentWindowHandle != IntPtr.Zero)
344 | {
345 | var handle = BitConverter.GetBytes(
346 | IntPtr.Size == 8 ? parentWindowHandle.ToInt64() : parentWindowHandle.ToInt32());
347 | NCryptSetProperty(
348 | ngcKeyHandle,
349 | NCRYPT_WINDOW_HANDLE_PROPERTY,
350 | handle,
351 | handle.Length,
352 | CngPropertyOptions.None).CheckStatus("NCRYPT_WINDOW_HANDLE_PROPERTY");
353 | }
354 |
355 | var message = uiContextParam.Message;
356 | if (!string.IsNullOrEmpty(message))
357 | {
358 | NCryptSetProperty(
359 | ngcKeyHandle,
360 | NCRYPT_USE_CONTEXT_PROPERTY,
361 | message,
362 | (message.Length + 1) * 2,
363 | CngPropertyOptions.None).CheckStatus("NCRYPT_USE_CONTEXT_PROPERTY");
364 | }
365 | }
366 |
367 | ///
368 | /// Creates a persistent key.
369 | ///
370 | ///
371 | /// A value indicating whether the existing key should be overwritten
372 | /// or not.
373 | ///
374 | /// The authentication provider UI context.
375 | /// A new safe NCrypt key handle.
376 | private static SafeNCryptKeyHandle CreatePersistentKey(bool overwriteExisting, AuthProviderUiContext uIContext)
377 | {
378 | NCryptOpenStorageProvider(out var ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0)
379 | .CheckStatus("NCryptOpenStorageProvider");
380 |
381 | SafeNCryptKeyHandle ngcKeyHandle;
382 | using (ngcProviderHandle)
383 | {
384 | NCryptCreatePersistedKey(
385 | ngcProviderHandle,
386 | out ngcKeyHandle,
387 | BCRYPT_RSA_ALGORITHM,
388 | persistentKeyName.Value,
389 | 0,
390 | overwriteExisting ? CngKeyCreationOptions.OverwriteExistingKey : CngKeyCreationOptions.None)
391 | .CheckStatus("NCryptCreatePersistedKey");
392 |
393 | var lengthProp = BitConverter.GetBytes(2048);
394 | NCryptSetProperty(
395 | ngcKeyHandle,
396 | NCRYPT_LENGTH_PROPERTY,
397 | lengthProp,
398 | lengthProp.Length,
399 | CngPropertyOptions.None).CheckStatus("NCRYPT_LENGTH_PROPERTY");
400 |
401 | var keyUsage = BitConverter.GetBytes(NCRYPT_ALLOW_DECRYPT_FLAG | NCRYPT_ALLOW_SIGNING_FLAG);
402 | NCryptSetProperty(
403 | ngcKeyHandle,
404 | NCRYPT_KEY_USAGE_PROPERTY,
405 | keyUsage,
406 | keyUsage.Length,
407 | CngPropertyOptions.None).CheckStatus("NCRYPT_KEY_USAGE_PROPERTY");
408 |
409 | var cacheType = BitConverter.GetBytes(NCRYPT_NGC_CACHE_TYPE_PROPERTY_AUTH_MANDATORY_FLAG);
410 | try
411 | {
412 | NCryptSetProperty(
413 | ngcKeyHandle,
414 | NCRYPT_NGC_CACHE_TYPE_PROPERTY,
415 | cacheType,
416 | cacheType.Length,
417 | CngPropertyOptions.None).CheckStatus("NCRYPT_NGC_CACHE_TYPE_PROPERTY");
418 | }
419 | catch
420 | {
421 | NCryptSetProperty(
422 | ngcKeyHandle,
423 | NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED,
424 | cacheType,
425 | cacheType.Length,
426 | CngPropertyOptions.None).CheckStatus("NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED");
427 | }
428 |
429 | ApplyUiContext(ngcKeyHandle, uIContext);
430 |
431 | NCryptFinalizeKey(ngcKeyHandle, 0).CheckStatus("NCryptFinalizeKey");
432 | }
433 |
434 | return ngcKeyHandle;
435 | }
436 |
437 | ///
438 | /// Deletes the persistent key.
439 | ///
440 | private static void DeletePersistentKey()
441 | {
442 | if (!TryOpenPersistentKey(out var ngcKeyHandle))
443 | {
444 | return;
445 | }
446 |
447 | using (ngcKeyHandle)
448 | {
449 | NCryptDeleteKey(ngcKeyHandle, 0).CheckStatus("NCryptDeleteKey");
450 | ngcKeyHandle.SetHandleAsInvalid();
451 | }
452 | }
453 |
454 | ///
455 | /// Determines whether this instance is available.
456 | ///
457 | ///
458 | /// true if this instance is available; otherwise, false.
459 | ///
460 | private static bool IsAvailable()
461 | {
462 | return !string.IsNullOrEmpty(LocalKeyName.Value);
463 | }
464 |
465 | ///
466 | /// Creates the NCrypt persistent key.
467 | ///
468 | /// The safe NCrypt provider handle.
469 | /// The safe NCrypt key handle.
470 | /// The algorithm identifier.
471 | /// The PSZ key name.
472 | /// The legacy key spec.
473 | /// The CNG key creation options.
474 | /// A new .
475 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
476 | private static extern SECURITY_STATUS NCryptCreatePersistedKey(
477 | SafeNCryptProviderHandle hProvider,
478 | [Out] out SafeNCryptKeyHandle phKey,
479 | string pszAlgId,
480 | string pszKeyName,
481 | int dwLegacyKeySpec,
482 | CngKeyCreationOptions dwFlags);
483 |
484 | ///
485 | /// Decrypts using NCrypt.
486 | ///
487 | /// The safe NCrypt key handle.
488 | /// The input bytes.
489 | /// The input CB value.
490 | /// The zero padding.
491 | /// The output bytes.
492 | /// The output CB value.
493 | /// The result.
494 | /// The flags.
495 | /// A new .
496 | [DllImport("ncrypt.dll")]
497 | private static extern SECURITY_STATUS NCryptDecrypt(
498 | SafeNCryptKeyHandle hKey,
499 | [In] [MarshalAs(UnmanagedType.LPArray)]
500 | byte[] pbInput,
501 | int cbInput,
502 | IntPtr pvPaddingZero,
503 | [Out] [MarshalAs(UnmanagedType.LPArray)]
504 | byte[] pbOutput,
505 | int cbOutput,
506 | [Out] out int pcbResult,
507 | int dwFlags);
508 |
509 | ///
510 | /// Deletes the key using NCrypt.
511 | ///
512 | /// The safe NCrypt key handle.
513 | /// The flags.
514 | /// A new .
515 | [DllImport("ncrypt.dll")]
516 | private static extern SECURITY_STATUS NCryptDeleteKey(SafeNCryptKeyHandle hKey, int flags);
517 |
518 | ///
519 | /// Encrypts using NCrypt.
520 | ///
521 | /// The safe NCrypt key handle.
522 | /// The input bytes.
523 | /// The input CB value.
524 | /// The zero padding.
525 | /// The output bytes.
526 | /// The output CB value.
527 | /// The result.
528 | /// The flags.
529 | /// A new .
530 | [DllImport("ncrypt.dll")]
531 | private static extern SECURITY_STATUS NCryptEncrypt(
532 | SafeNCryptKeyHandle hKey,
533 | [In] [MarshalAs(UnmanagedType.LPArray)]
534 | byte[] pbInput,
535 | int cbInput,
536 | IntPtr pvPaddingZero,
537 | [Out] [MarshalAs(UnmanagedType.LPArray)]
538 | byte[]? pbOutput,
539 | int cbOutput,
540 | [Out] out int pcbResult,
541 | int dwFlags);
542 |
543 | ///
544 | /// Finalizes the key using NCrypt.
545 | ///
546 | /// The safe NCrypt key handle.
547 | /// The flags.
548 | /// A new .
549 | [DllImport("ncrypt.dll")]
550 | private static extern SECURITY_STATUS NCryptFinalizeKey(SafeNCryptKeyHandle hKey, int dwFlags);
551 |
552 | ///
553 | /// Gets the property using NCrypt.
554 | ///
555 | /// The safe NCrypt handle.
556 | /// The PSZ property.
557 | /// The output bytes.
558 | /// The output CB value.
559 | /// The result.
560 | /// The flags.
561 | /// A new .
562 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
563 | private static extern SECURITY_STATUS NCryptGetProperty(
564 | SafeNCryptHandle hObject,
565 | string pszProperty,
566 | ref int pbOutput,
567 | int cbOutput,
568 | [Out] out int pcbResult,
569 | CngPropertyOptions dwFlags);
570 |
571 | ///
572 | /// Opens the key using NCrypt.
573 | ///
574 | /// The safe NCrypt provider handle.
575 | /// The safe NCrypt key handle.
576 | /// The PSZ key name.
577 | /// The legacy key spec.
578 | /// The flags.
579 | /// A new .
580 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
581 | private static extern SECURITY_STATUS NCryptOpenKey(
582 | SafeNCryptProviderHandle hProvider,
583 | [Out] out SafeNCryptKeyHandle phKey,
584 | string pszKeyName,
585 | int dwLegacyKeySpec,
586 | CngKeyOpenOptions dwFlags);
587 |
588 | ///
589 | /// Opens the storage provider using NCrypt.
590 | ///
591 | /// The safe NCrypt handle.
592 | /// The PSZ provider name.
593 | /// The flags.
594 | /// A new .
595 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
596 | private static extern SECURITY_STATUS NCryptOpenStorageProvider(
597 | [Out] out SafeNCryptProviderHandle phProvider,
598 | string pszProviderName,
599 | int dwFlags);
600 |
601 | ///
602 | /// Sets a property using NCrypt.
603 | ///
604 | /// The safe NCrypt handle.
605 | /// The PSZ property.
606 | /// The input bytes.
607 | /// The input CB value.
608 | /// The flags.
609 | /// A new .
610 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
611 | private static extern SECURITY_STATUS NCryptSetProperty(
612 | SafeNCryptHandle hObject,
613 | string pszProperty,
614 | string pbInput,
615 | int cbInput,
616 | CngPropertyOptions dwFlags);
617 |
618 | ///
619 | /// Sets a property using NCrypt.
620 | ///
621 | /// The safe NCrypt handle.
622 | /// The PSZ property.
623 | /// The input bytes.
624 | /// The input CB value.
625 | /// The flags.
626 | /// A new .
627 | [DllImport("ncrypt.dll", CharSet = CharSet.Unicode)]
628 | private static extern SECURITY_STATUS NCryptSetProperty(
629 | SafeNCryptHandle hObject,
630 | string pszProperty,
631 | [In] [MarshalAs(UnmanagedType.LPArray)]
632 | byte[] pbInput,
633 | int cbInput,
634 | CngPropertyOptions dwFlags);
635 |
636 | ///
637 | /// Gets the default decryption key name using NCrypt.
638 | ///
639 | /// The PSZ S identifier.
640 | /// The DW reserved 1.
641 | /// The DW reserved 2.
642 | /// The PPSZ key name.
643 | /// A new .
644 | [DllImport("cryptngc.dll", CharSet = CharSet.Unicode)]
645 | private static extern SECURITY_STATUS NgcGetDefaultDecryptionKeyName(
646 | string pszSid,
647 | int dwReserved1,
648 | int dwReserved2,
649 | [Out] out string ppszKeyName);
650 |
651 | ///
652 | /// Gets the local key name.
653 | ///
654 | /// The local key name as .
655 | private static string RetrieveLocalKeyName()
656 | {
657 | NgcGetDefaultDecryptionKeyName(WindowsIdentity.GetCurrent().User?.Value ?? string.Empty, 0, 0, out var key);
658 | return key;
659 | }
660 |
661 | ///
662 | /// Gets the persistent key name.
663 | ///
664 | /// The local key name as .
665 | private static string RetrievePersistentKeyName()
666 | {
667 | var sid = WindowsIdentity.GetCurrent().User?.Value;
668 | return sid + "//" + Domain + "/" + SubDomain + "/" + PersistentName;
669 | }
670 |
671 | ///
672 | /// Tries to open the persistent key,
673 | ///
674 | /// The safe NCrypt key handle.
675 | /// A value indicating whether the handle is valid or not.
676 | private static bool TryOpenPersistentKey(out SafeNCryptKeyHandle ngcKeyHandle)
677 | {
678 | NCryptOpenStorageProvider(out var ngcProviderHandle, MS_NGC_KEY_STORAGE_PROVIDER, 0)
679 | .CheckStatus("NCryptOpenStorageProvider");
680 |
681 | using (ngcProviderHandle)
682 | {
683 | NCryptOpenKey(ngcProviderHandle, out ngcKeyHandle, persistentKeyName.Value, 0, CngKeyOpenOptions.None)
684 | .CheckStatus("NCryptOpenKey", NTE_NO_KEY);
685 | }
686 |
687 | return ngcKeyHandle != null && !ngcKeyHandle.IsInvalid;
688 | }
689 |
690 | ///
691 | /// Verifies the integrity of the persistent key.
692 | ///
693 | /// The safe NCrypt key handle.
694 | /// A value indicating whether the persistent key's integrity is valid or not.
695 | private static bool VerifyPersistentKeyIntegrity(SafeNCryptHandle ngcKeyHandle)
696 | {
697 | var keyUsage = 0;
698 | NCryptGetProperty(
699 | ngcKeyHandle,
700 | NCRYPT_KEY_USAGE_PROPERTY,
701 | ref keyUsage,
702 | sizeof(int),
703 | out _,
704 | CngPropertyOptions.None).CheckStatus("NCRYPT_KEY_USAGE_PROPERTY");
705 |
706 | if ((keyUsage & NCRYPT_ALLOW_KEY_IMPORT_FLAG) == NCRYPT_ALLOW_KEY_IMPORT_FLAG)
707 | {
708 | return false;
709 | }
710 |
711 | var cacheType = 0;
712 | try
713 | {
714 | NCryptGetProperty(
715 | ngcKeyHandle,
716 | NCRYPT_NGC_CACHE_TYPE_PROPERTY,
717 | ref cacheType,
718 | sizeof(int),
719 | out _,
720 | CngPropertyOptions.None).CheckStatus("NCRYPT_NGC_CACHE_TYPE_PROPERTY");
721 | }
722 | catch
723 | {
724 | NCryptGetProperty(
725 | ngcKeyHandle,
726 | NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED,
727 | ref cacheType,
728 | sizeof(int),
729 | out _,
730 | CngPropertyOptions.None).CheckStatus("NCRYPT_NGC_CACHE_TYPE_PROPERTY_DEPRECATED");
731 | }
732 |
733 | return cacheType == NCRYPT_NGC_CACHE_TYPE_PROPERTY_AUTH_MANDATORY_FLAG;
734 | }
735 |
736 | [StructLayout(LayoutKind.Sequential)]
737 | private struct SECURITY_STATUS
738 | {
739 | ///
740 | /// The security status.
741 | ///
742 | private readonly int secStatus;
743 |
744 | ///
745 | /// Checks the status.
746 | ///
747 | /// The name.
748 | /// A value indicating whether the status is ignored or not
749 | public void CheckStatus(string name = "", int ignoreStatus = 0)
750 | {
751 | /*
752 | * NTE_BAD_FLAGS
753 | * NTE_BAD_KEYSET
754 | * NTE_BAD_KEY_STATE
755 | * NTE_BUFFER_TOO_SMALL
756 | * NTE_INVALID_HANDLE
757 | * NTE_INVALID_PARAMETER
758 | * NTE_PERM
759 | * NTE_NO_MEMORY
760 | * NTE_NOT_SUPPORTED
761 | * NTE_USER_CANCELLED
762 | */
763 |
764 | if (this.secStatus >= 0 || this.secStatus == ignoreStatus)
765 | {
766 | return;
767 | }
768 |
769 | throw this.secStatus switch
770 | {
771 | NTE_USER_CANCELLED => new AuthProviderUserCancelledException(),
772 | _ => new AuthProviderSystemErrorException(name, this.secStatus)
773 | };
774 | }
775 | }
776 | }
777 |
778 | ///
779 | ///
780 | /// The authentication provider UI context.
781 | ///
782 | ///
783 | internal sealed class AuthProviderUiContext : IWin32Window
784 | {
785 | ///
786 | /// Initializes a new instance of the class.
787 | ///
788 | /// The message.
789 | /// The window handle.
790 | private AuthProviderUiContext(string message, IntPtr windowHandle)
791 | {
792 | this.Message = message;
793 | this.ParentWindowHandle = windowHandle;
794 | }
795 |
796 | ///
797 | /// Gets the message.
798 | ///
799 | public string Message { get; }
800 |
801 | ///
802 | /// Gets the parent window handle.
803 | ///
804 | public IntPtr ParentWindowHandle { get; }
805 |
806 | ///
807 | /// Gets the handle.
808 | ///
809 | IntPtr IWin32Window.Handle => this.ParentWindowHandle;
810 |
811 | ///
812 | /// A handler method to use the authentication UI context.
813 | ///
814 | /// The message.
815 | /// The window handle.
816 | /// A new authentication UI context.
817 | public static AuthProviderUiContext With(string message, IntPtr windowHandle)
818 | {
819 | return new AuthProviderUiContext(message, windowHandle);
820 | }
821 | }
822 |
--------------------------------------------------------------------------------
/src/WindowsHello/Windows.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/SeppPenner/WindowsHello/2e616704c95a6140b9a9f59b8aab9862f6b4295c/src/WindowsHello/Windows.ico
--------------------------------------------------------------------------------
/src/WindowsHello/WindowsHello.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | net8.0-windows;net9.0-windows
5 | true
6 | WindowsHello
7 | WindowsHello
8 | true
9 | true
10 | HaemmerElectronics.SeppPenner.WindowsHello
11 | SeppPenner
12 | Hämmer Electronics
13 | Copyright © Hämmer Electronics
14 | WindowsHello is an assembly/ library to work with [Microsoft's Windows Hello](https://support.microsoft.com/de-de/help/17215/windows-10-what-is-hello) in aplications.
15 | c# csharp windows hello windows-hello
16 | https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/
17 | https://github.com/SeppPenner/WindowsHello
18 | Icon.png
19 | Github
20 | Version 1.1.4.0 (2024-11-24): Removed support for Net6.0, added support for Net9.0, updated NuGet packages.
21 | MIT
22 | win-x64
23 | latest
24 | enable
25 | true
26 | true
27 | true
28 | true
29 | snupkg
30 | enable
31 | NU1803
32 | true
33 | README.md
34 |
35 |
36 |
37 |
38 | all
39 | runtime; build; native; contentfiles; analyzers; buildtransitive
40 |
41 |
42 |
43 |
44 |
45 |
46 | True
47 |
48 |
49 |
50 | True
51 |
52 | Always
53 |
54 |
55 | True
56 |
57 |
58 |
59 | True
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
--------------------------------------------------------------------------------