From 30b3f02ecce13966d98b11e26777707f586e7e84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20=C3=85berg?= Date: Tue, 8 Jul 2025 12:11:40 +0200 Subject: [PATCH] Added sample code --- .../PluginRegistrationManager.cpp.sample | 126 ++++++++++++++++++ .../PluginRegistrationManager.h.sample | 80 +++++++++++ 2 files changed, 206 insertions(+) create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.cpp.sample create mode 100644 apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.h.sample diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.cpp.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.cpp.sample new file mode 100644 index 00000000000..c5a5a52bfa5 --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.cpp.sample @@ -0,0 +1,126 @@ +#include "pch.h" +#include "MainPage.xaml.h" +#include "PluginRegistrationManager.h" +#include + +namespace winrt::PasskeyManager::implementation { + PluginRegistrationManager::PluginRegistrationManager() : + m_pluginRegistered(false), + m_initialized(false), + m_pluginState(EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE::PluginAuthenticatorState_Unknown) + { + Initialize(); + m_webAuthnDll.reset(LoadLibraryExW(L"webauthn.dll", nullptr, LOAD_LIBRARY_SEARCH_SYSTEM32)); + } + + PluginRegistrationManager::~PluginRegistrationManager() + { + } + + HRESULT PluginRegistrationManager::Initialize() + { + HRESULT hr = RefreshPluginState(); + RETURN_HR_IF_EXPECTED(S_OK, RefreshPluginState() == NTE_NOT_FOUND); + RETURN_HR(hr); + } + + HRESULT PluginRegistrationManager::RegisterPlugin() + { + // Get the function pointer of WebAuthNPluginAddAuthenticator + auto webAuthNPluginAddAuthenticator = GetProcAddressByFunctionDeclaration( + m_webAuthnDll.get(), + EXPERIMENTAL_WebAuthNPluginAddAuthenticator); + RETURN_HR_IF_NULL(E_FAIL, webAuthNPluginAddAuthenticator); + + /* + * This section creates a sample authenticatorInfo blob to include in the registration + * request. This blob must CBOR encoded using the format defined + * in https://fidoalliance.org/specs/fido-v2.0-ps-20190130/fido-client-to-authenticator-protocol-v2.0-ps-20190130.html#authenticatorGetInfo + * + * 'AAGUID' maybe used to fetch information about the authenticator from the FIDO Metadata Service and other sources. + * Refer: https://fidoalliance.org/metadata/ + * + * 'extensions' field is used to perform feature detection on the authenticator + * and maybe used to determine if the authenticator is filtered out. + */ + std::string tempAaguidStr{ c_pluginAaguid }; + tempAaguidStr.erase(std::remove(tempAaguidStr.begin(), tempAaguidStr.end(), L'-'), tempAaguidStr.end()); + std::transform(tempAaguidStr.begin(), tempAaguidStr.end(), tempAaguidStr.begin(), [](unsigned char c) { return static_cast(std::toupper(c)); }); + // The following hex strings represent the encoding of + // {1: ["FIDO_2_0", "FIDO_2_1"], 2: ["prf", "hmac-secret"], 3: h'/* AAGUID */', 4: {"rk": true, "up": true, "uv": true}, + // 9: ["internal"], 10: [{"alg": -7, "type": "public-key"}]} + std::string authenticatorInfoStrPart1 = "A60182684649444F5F325F30684649444F5F325F310282637072666B686D61632D7365637265740350"; + std::string authenticatorInfoStrPart2 = "04A362726BF5627570F5627576F5098168696E7465726E616C0A81A263616C672664747970656A7075626C69632D6B6579"; + std::string fullAuthenticatorInfoStr = authenticatorInfoStrPart1 + tempAaguidStr + authenticatorInfoStrPart2; + std::vector authenticatorInfo = hexStringToBytes(fullAuthenticatorInfoStr); + + // Validate that c_pluginClsid is a valid CLSID + CLSID CLSID_ContosoPluginAuthenticator; + RETURN_IF_FAILED(CLSIDFromString(c_pluginClsid, &CLSID_ContosoPluginAuthenticator)); + + EXPERIMENTAL_WEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_OPTIONS addOptions{}; + addOptions.pwszAuthenticatorName = c_pluginName; + addOptions.pwszPluginRpId = c_pluginRpId; + addOptions.pwszPluginClsId = c_pluginClsid; + addOptions.pbAuthenticatorInfo = authenticatorInfo.data(); + addOptions.cbAuthenticatorInfo = static_cast(authenticatorInfo.size()); + + EXPERIMENTAL_PWEBAUTHN_PLUGIN_ADD_AUTHENTICATOR_RESPONSE addResponse; + RETURN_IF_FAILED(webAuthNPluginAddAuthenticator(&addOptions, &addResponse)); + + // The response from plugin contains the public key used to sign plugin operation requests. Stash it for later use. + wil::unique_hkey hKey; + RETURN_IF_WIN32_ERROR(RegCreateKeyEx( + HKEY_CURRENT_USER, + c_pluginRegistryPath, + 0, + nullptr, + REG_OPTION_NON_VOLATILE, + KEY_WRITE, + nullptr, + &hKey, + nullptr)); + + RETURN_IF_WIN32_ERROR(RegSetValueEx( + hKey.get(), + c_windowsPluginRequestSigningKeyRegKeyName, + 0, + REG_BINARY, + addResponse->pbOpSignPubKey, + addResponse->cbOpSignPubKey)); + return S_OK; + } + + HRESULT PluginRegistrationManager::UnregisterPlugin() + { + // Get the function pointer of WebAuthNPluginRemoveAuthenticator + auto webAuthNPluginRemoveAuthenticator = GetProcAddressByFunctionDeclaration( + m_webAuthnDll.get(), + EXPERIMENTAL_WebAuthNPluginRemoveAuthenticator); + RETURN_HR_IF_NULL(E_FAIL, webAuthNPluginRemoveAuthenticator); + + RETURN_HR(webAuthNPluginRemoveAuthenticator(c_pluginClsid)); + } + + HRESULT PluginRegistrationManager::RefreshPluginState() + { + // Reset the plugin state and registration status + m_pluginRegistered = false; + m_pluginState = EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE::PluginAuthenticatorState_Unknown; + + // Get handle to EXPERIMENTAL_WebAuthNPluginGetAuthenticatorState which takes in a GUID and returns EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE + auto webAuthNPluginGetAuthenticatorState = GetProcAddressByFunctionDeclaration( + m_webAuthnDll.get(), + EXPERIMENTAL_WebAuthNPluginGetAuthenticatorState); + RETURN_HR_IF_NULL(E_FAIL, webAuthNPluginGetAuthenticatorState); + + // Get the plugin state + EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE localPluginState; + RETURN_IF_FAILED(webAuthNPluginGetAuthenticatorState(c_pluginClsid, &localPluginState)); + + // If the EXPERIMENTAL_WebAuthNPluginGetAuthenticatorState function succeeded, that indicates the plugin is registered and localPluginState is the valid plugin state + m_pluginRegistered = true; + m_pluginState = localPluginState; + return S_OK; + } +} \ No newline at end of file diff --git a/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.h.sample b/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.h.sample new file mode 100644 index 00000000000..df0d3b6949b --- /dev/null +++ b/apps/desktop/desktop_native/windows_plugin_authenticator/src/samples/PluginRegistrationManager.h.sample @@ -0,0 +1,80 @@ +#pragma once +#include "pch.h" +#include +#include +#include +#include +#include + +constexpr wchar_t c_pluginName[] = L"Contoso Passkey Manager"; +constexpr wchar_t c_pluginRpId[] = L"contoso.com"; + +/* The AAGUID is a unique identifier for the FIDO authenticator model. +*'AAGUID' maybe used to fetch information about the authenticator from the FIDO Metadata Service and other sources. +* Refer: https://fidoalliance.org/metadata/ +*/ +constexpr char c_pluginAaguid[] = "########-####-####-####-############"; +static_assert(c_pluginAaguid[1] != '#', "Please replace the ##### above with your AAGUID or a value you generated by running guidgen"); + +/* Generate a GUID using guidgen and replace below and in Package.appxmanifest file */ +constexpr wchar_t c_pluginClsid[] = L"{########-####-####-####-############}"; +static_assert(c_pluginClsid[1] != '#', "Please replace the ##### above with a GUID you generated by running guidgen"); + + +constexpr wchar_t c_pluginSigningKeyName[] = L"TestAppPluginIdKey"; +constexpr wchar_t c_pluginRegistryPath[] = L"Software\\Contoso\\PasskeyManager"; +constexpr wchar_t c_windowsPluginRequestSigningKeyRegKeyName[] = L"RequestSigningKeyBlob"; +constexpr wchar_t c_windowsPluginVaultLockedRegKeyName[] = L"VaultLocked"; +constexpr wchar_t c_windowsPluginSilentOperationRegKeyName[] = L"SilentOperation"; +constexpr wchar_t c_windowsPluginDBUpdateInd[] = L"SilentOperation"; + +namespace winrt::PasskeyManager::implementation +{ + class PluginRegistrationManager + { + public: + static PluginRegistrationManager& getInstance() + { + static PluginRegistrationManager instance; + return instance; + } + + // Initialize function which calls GetPluginState to check if the plugin is already registered + HRESULT Initialize(); + + HRESULT RegisterPlugin(); + HRESULT UnregisterPlugin(); + + HRESULT RefreshPluginState(); + + bool IsPluginRegistered() const + { + return m_pluginRegistered; + } + + EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE GetPluginState() const + { + return m_pluginState; + } + + private: + EXPERIMENTAL_PLUGIN_AUTHENTICATOR_STATE m_pluginState; + bool m_initialized = false; + bool m_pluginRegistered = false; + wil::unique_hmodule m_webAuthnDll; + + PluginRegistrationManager(); + ~PluginRegistrationManager(); + PluginRegistrationManager(const PluginRegistrationManager&) = delete; + PluginRegistrationManager& operator=(const PluginRegistrationManager&) = delete; + + void UpdatePasskeyOperationStatusText(hstring const& statusText) + { + com_ptr curApp = winrt::Microsoft::UI::Xaml::Application::Current().as(); + curApp->GetDispatcherQueue().TryEnqueue([curApp, statusText]() + { + curApp->m_window.Content().try_as().Content().try_as()->UpdatePasskeyOperationStatusText(statusText); + }); + } + }; +}; \ No newline at end of file