├── .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 | [![Build status](https://ci.appveyor.com/api/projects/status/a8h66id7bqk07n79?svg=true)](https://ci.appveyor.com/project/SeppPenner/windowshello) 7 | [![GitHub issues](https://img.shields.io/github/issues/SeppPenner/WindowsHello.svg)](https://github.com/SeppPenner/WindowsHello/issues) 8 | [![GitHub forks](https://img.shields.io/github/forks/SeppPenner/WindowsHello.svg)](https://github.com/SeppPenner/WindowsHello/network) 9 | [![GitHub stars](https://img.shields.io/github/stars/SeppPenner/WindowsHello.svg)](https://github.com/SeppPenner/WindowsHello/stargazers) 10 | [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://raw.githubusercontent.com/SeppPenner/WindowsHello/master/License.txt) 11 | [![Nuget](https://img.shields.io/badge/WindowsHello-Nuget-brightgreen.svg)](https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/) 12 | [![NuGet Downloads](https://img.shields.io/nuget/dt/HaemmerElectronics.SeppPenner.WindowsHello.svg)](https://www.nuget.org/packages/HaemmerElectronics.SeppPenner.WindowsHello/) 13 | [![Known Vulnerabilities](https://snyk.io/test/github/SeppPenner/WindowsHello/badge.svg)](https://snyk.io/test/github/SeppPenner/WindowsHello) 14 | [![Gitter](https://img.shields.io/matrix/WindowsHello2_community%3Agitter.im?server_fqdn=matrix.org)](https://matrix.to/#/#WindowsHello2_community:gitter.im) 15 | [![Blogger](https://img.shields.io/badge/Follow_me_on-blogger-orange)](https://franzhuber23.blogspot.de/) 16 | [![Patreon](https://img.shields.io/badge/Patreon-F96854?logo=patreon&logoColor=white)](https://patreon.com/SeppPennerOpenSourceDevelopment) 17 | [![PayPal](https://img.shields.io/badge/PayPal-00457C?logo=paypal&logoColor=white)](https://paypal.me/th070795) 18 | 19 | 20 | [![All Contributors](https://img.shields.io/badge/all_contributors-2-orange.svg?style=flat-square)](#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 | 80 | 81 | 82 | 83 | 84 | 85 | 86 |
danergo
danergo

⚠️
HansM
HansM

💻 📖 💡 🚧 📆 ⚠️
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 | --------------------------------------------------------------------------------